yourname hai 5 meses
pai
achega
86d7bcdf22

+ 4 - 0
package.json

@@ -1,4 +1,7 @@
 {
+  "scripts": {
+    "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js"
+  },
   "dependencies": {
     "@hono/zod-openapi": "^0.19.8",
     "mysql2": "^3.14.1",
@@ -9,6 +12,7 @@
   "devDependencies": {
     "@types/node": "^24.0.3",
     "ts-node": "^10.9.2",
+    "tsconfig-paths": "^4.2.0",
     "typescript": "^5.8.3"
   }
 }

+ 1 - 1
src/server/data-source.ts

@@ -13,7 +13,7 @@ import { Campaign } from './modules/campaigns/campaign.entity';
 
 export const AppDataSource = new DataSource({
   type: 'mysql',
-  host: 'localhost',
+  host: '127.0.0.1',
   port: 3306,
   username: 'root',
   password: '',

+ 66 - 0
src/server/migrations/1750605011627-InitialSchema.ts

@@ -0,0 +1,66 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class InitialSchema1750605011627 implements MigrationInterface {
+    name = 'InitialSchema1750605011627'
+
+    public async up(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(`CREATE TABLE \`contacts\` (\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT, \`customer_id\` int UNSIGNED NOT NULL COMMENT '客户ID', \`name\` varchar(50) NOT NULL COMMENT '联系人姓名', \`position\` varchar(50) NULL COMMENT '职位', \`phone\` varchar(20) NULL COMMENT '电话', \`email\` varchar(100) NULL COMMENT '邮箱', \`is_primary\` tinyint NOT NULL COMMENT '是否主要联系人(0-否, 1-是)' DEFAULT '0', \`status\` tinyint NOT NULL COMMENT '状态(0-禁用, 1-启用)' DEFAULT '1', \`is_deleted\` tinyint NOT NULL COMMENT '删除状态(0-未删除, 1-已删除)' DEFAULT '0', \`created_at\` timestamp NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \`updated_at\` timestamp NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \`customerId\` int UNSIGNED NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
+        await queryRunner.query(`CREATE TABLE \`departments\` (\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT, \`name\` varchar(50) NOT NULL COMMENT '部门名称', \`parent_id\` int UNSIGNED NULL COMMENT '父部门ID', \`sort_order\` int NULL COMMENT '排序序号' DEFAULT '0', \`description\` text NULL COMMENT '部门描述', \`status\` tinyint NOT NULL COMMENT '状态(0-禁用, 1-启用)' DEFAULT '1', \`is_deleted\` tinyint NOT NULL COMMENT '删除状态(0-未删除, 1-已删除)' DEFAULT '0', \`created_at\` timestamp NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \`updated_at\` timestamp NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
+        await queryRunner.query(`CREATE TABLE \`roles\` (\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT, \`name\` varchar(50) NOT NULL COMMENT '角色名称', \`code\` varchar(50) NOT NULL COMMENT '角色编码', \`description\` text NULL COMMENT '角色描述', \`permissions\` text NULL COMMENT '权限列表(JSON)', \`status\` tinyint NOT NULL COMMENT '状态(0-禁用, 1-启用)' DEFAULT '1', \`is_deleted\` tinyint NOT NULL COMMENT '删除状态(0-未删除, 1-已删除)' DEFAULT '0', \`created_at\` timestamp NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \`updated_at\` timestamp NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE INDEX \`IDX_648e3f5447f725579d7d4ffdfb\` (\`name\`), UNIQUE INDEX \`IDX_f6d54f95c31b73fb1bdd8e91d0\` (\`code\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
+        await queryRunner.query(`CREATE TABLE \`leads\` (\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT, \`title\` varchar(100) NOT NULL COMMENT '线索标题', \`customer_id\` int UNSIGNED NOT NULL COMMENT '客户ID', \`contact_id\` int UNSIGNED NULL COMMENT '联系人ID', \`source\` varchar(50) NULL COMMENT '线索来源', \`status\` tinyint NOT NULL COMMENT '线索状态(0-新建, 1-跟进中, 2-已转化, 3-已放弃)' DEFAULT '0', \`estimated_value\` decimal(10,2) NULL COMMENT '预估价值', \`assigned_to\` int UNSIGNED NULL COMMENT '负责人ID', \`description\` text NULL COMMENT '线索描述', \`is_deleted\` tinyint NOT NULL COMMENT '删除状态(0-未删除, 1-已删除)' DEFAULT '0', \`created_at\` timestamp NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \`updated_at\` timestamp NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \`customerId\` int UNSIGNED NULL, \`assignedUserId\` int UNSIGNED NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
+        await queryRunner.query(`CREATE TABLE \`contracts\` (\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT, \`contract_no\` varchar(50) NOT NULL COMMENT '合同编号', \`title\` varchar(100) NOT NULL COMMENT '合同标题', \`customer_id\` int UNSIGNED NOT NULL COMMENT '客户ID', \`opportunity_id\` int UNSIGNED NULL COMMENT '销售机会ID', \`amount\` decimal(12,2) NOT NULL COMMENT '合同金额', \`signed_date\` date NOT NULL COMMENT '签约日期', \`start_date\` date NOT NULL COMMENT '开始日期', \`end_date\` date NULL COMMENT '结束日期', \`status\` tinyint NOT NULL COMMENT '合同状态(0-草稿, 1-已签约, 2-执行中, 3-已完成, 4-已终止)' DEFAULT '0', \`payment_terms\` text NULL COMMENT '付款条件', \`signed_by\` int UNSIGNED NULL COMMENT '签约人ID', \`attachment_url\` varchar(255) NULL COMMENT '合同附件URL', \`description\` text NULL COMMENT '合同描述', \`is_deleted\` tinyint NOT NULL COMMENT '删除状态(0-未删除, 1-已删除)' DEFAULT '0', \`created_at\` timestamp NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \`updated_at\` timestamp NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \`customerId\` int UNSIGNED NULL, \`opportunityId\` int UNSIGNED NULL, \`signedUserId\` int UNSIGNED NULL, UNIQUE INDEX \`IDX_793609267045de22b8de569717\` (\`contract_no\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
+        await queryRunner.query(`CREATE TABLE \`tickets\` (\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT, \`ticket_no\` varchar(50) NOT NULL COMMENT '工单编号', \`title\` varchar(100) NOT NULL COMMENT '工单标题', \`customer_id\` int UNSIGNED NOT NULL COMMENT '客户ID', \`contract_id\` int UNSIGNED NULL COMMENT '合同ID', \`contact_id\` int UNSIGNED NULL COMMENT '联系人ID', \`type\` varchar(50) NOT NULL COMMENT '工单类型', \`status\` tinyint NOT NULL COMMENT '工单状态(0-新建, 1-处理中, 2-已解决, 3-已关闭, 4-已拒绝)' DEFAULT '0', \`priority\` tinyint NOT NULL COMMENT '优先级(0-低, 1-中, 2-高, 3-紧急)' DEFAULT '1', \`assigned_to\` int UNSIGNED NULL COMMENT '负责人ID', \`description\` text NOT NULL COMMENT '问题描述', \`solution\` text NULL COMMENT '解决方案', \`resolved_at\` timestamp NULL COMMENT '解决时间', \`closed_at\` timestamp NULL COMMENT '关闭时间', \`is_deleted\` tinyint NOT NULL COMMENT '删除状态(0-未删除, 1-已删除)' DEFAULT '0', \`created_at\` timestamp NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \`updated_at\` timestamp NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \`customerId\` int UNSIGNED NULL, \`contractId\` int UNSIGNED NULL, \`assignedUserId\` int UNSIGNED NULL, UNIQUE INDEX \`IDX_4e3c3787c6138011839f329637\` (\`ticket_no\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
+        await queryRunner.query(`CREATE TABLE \`users\` (\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT, \`username\` varchar(50) NOT NULL COMMENT '用户名', \`password\` varchar(100) NOT NULL COMMENT '密码(加密存储)', \`name\` varchar(50) NOT NULL COMMENT '姓名', \`email\` varchar(100) NULL COMMENT '邮箱', \`phone\` varchar(20) NULL COMMENT '电话', \`department_id\` int UNSIGNED NULL COMMENT '部门ID', \`role_id\` int UNSIGNED NULL COMMENT '角色ID', \`position\` varchar(50) NULL COMMENT '职位', \`avatar_url\` varchar(255) NULL COMMENT '头像URL', \`last_login_at\` timestamp NULL COMMENT '最后登录时间', \`status\` tinyint NOT NULL COMMENT '状态(0-禁用, 1-启用)' DEFAULT '1', \`is_deleted\` tinyint NOT NULL COMMENT '删除状态(0-未删除, 1-已删除)' DEFAULT '0', \`created_at\` timestamp NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \`updated_at\` timestamp NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \`departmentId\` int UNSIGNED NULL, \`roleId\` int UNSIGNED NULL, UNIQUE INDEX \`IDX_fe0bb3f6520ee0469504521e71\` (\`username\`), UNIQUE INDEX \`IDX_97672ac88f789774dd47f7c8be\` (\`email\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
+        await queryRunner.query(`CREATE TABLE \`opportunities\` (\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT, \`title\` varchar(100) NOT NULL COMMENT '机会标题', \`customer_id\` int UNSIGNED NOT NULL COMMENT '客户ID', \`contact_id\` int UNSIGNED NULL COMMENT '联系人ID', \`estimated_amount\` decimal(12,2) NOT NULL COMMENT '预估金额', \`probability\` int NULL COMMENT '赢单概率(%)', \`expected_close_date\` date NULL COMMENT '预计成交日期', \`stage\` varchar(50) NOT NULL COMMENT '销售阶段', \`assigned_to\` int UNSIGNED NULL COMMENT '负责人ID', \`description\` text NULL COMMENT '机会描述', \`is_deleted\` tinyint NOT NULL COMMENT '删除状态(0-未删除, 1-已删除)' DEFAULT '0', \`created_at\` timestamp NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \`updated_at\` timestamp NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \`customerId\` int UNSIGNED NULL, \`assignedUserId\` int UNSIGNED NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
+        await queryRunner.query(`CREATE TABLE \`customers\` (\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT, \`name\` varchar(100) NOT NULL COMMENT '客户名称', \`type\` tinyint NOT NULL COMMENT '客户类型(0-个人, 1-企业)' DEFAULT '0', \`industry\` varchar(50) NULL COMMENT '所属行业', \`scale\` varchar(20) NULL COMMENT '企业规模', \`address\` varchar(255) NULL COMMENT '地址', \`phone\` varchar(20) NULL COMMENT '电话', \`email\` varchar(100) NULL COMMENT '邮箱', \`website\` varchar(100) NULL COMMENT '网站', \`description\` text NULL COMMENT '客户描述', \`status\` tinyint NOT NULL COMMENT '状态(0-禁用, 1-启用)' DEFAULT '1', \`is_deleted\` tinyint NOT NULL COMMENT '删除状态(0-未删除, 1-已删除)' DEFAULT '0', \`created_at\` timestamp NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \`updated_at\` timestamp NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
+        await queryRunner.query(`CREATE TABLE \`campaigns\` (\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT, \`name\` varchar(100) NOT NULL COMMENT '活动名称', \`type\` varchar(50) NOT NULL COMMENT '活动类型', \`status\` tinyint NOT NULL COMMENT '活动状态(0-计划中, 1-进行中, 2-已结束, 3-已取消)' DEFAULT '0', \`start_date\` date NOT NULL COMMENT '开始日期', \`end_date\` date NULL COMMENT '结束日期', \`budget\` decimal(12,2) NULL COMMENT '预算金额', \`actual_cost\` decimal(12,2) NULL COMMENT '实际成本', \`expected_revenue\` decimal(12,2) NULL COMMENT '预计收入', \`description\` text NULL COMMENT '活动描述', \`created_by\` int UNSIGNED NULL COMMENT '创建人ID', \`is_deleted\` tinyint NOT NULL COMMENT '删除状态(0-未删除, 1-已删除)' DEFAULT '0', \`created_at\` timestamp NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \`updated_at\` timestamp NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \`creatorId\` int UNSIGNED NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
+        await queryRunner.query(`ALTER TABLE \`contacts\` ADD CONSTRAINT \`FK_f418137d00d8b5a598400bbf57a\` FOREIGN KEY (\`customerId\`) REFERENCES \`customers\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE \`leads\` ADD CONSTRAINT \`FK_1cc4d3177a1b83286f4382877f5\` FOREIGN KEY (\`customerId\`) REFERENCES \`customers\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE \`leads\` ADD CONSTRAINT \`FK_f7a7e8a6161a77f330a0aa36eec\` FOREIGN KEY (\`assignedUserId\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE \`contracts\` ADD CONSTRAINT \`FK_22c6e574e547c5b5d79e9c84380\` FOREIGN KEY (\`customerId\`) REFERENCES \`customers\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE \`contracts\` ADD CONSTRAINT \`FK_18cbf0097753ef0065d75ec0cb8\` FOREIGN KEY (\`opportunityId\`) REFERENCES \`opportunities\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE \`contracts\` ADD CONSTRAINT \`FK_6238d92bb7e644c592c2eae97fb\` FOREIGN KEY (\`signedUserId\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE \`tickets\` ADD CONSTRAINT \`FK_7a1f978a1c1a6b2b1133014b4b2\` FOREIGN KEY (\`customerId\`) REFERENCES \`customers\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE \`tickets\` ADD CONSTRAINT \`FK_cbe17dea439f414f60ae768f9cb\` FOREIGN KEY (\`contractId\`) REFERENCES \`contracts\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE \`tickets\` ADD CONSTRAINT \`FK_367d43d5d04a904fe04a1d641ab\` FOREIGN KEY (\`assignedUserId\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE \`users\` ADD CONSTRAINT \`FK_554d853741f2083faaa5794d2ae\` FOREIGN KEY (\`departmentId\`) REFERENCES \`departments\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE \`users\` ADD CONSTRAINT \`FK_368e146b785b574f42ae9e53d5e\` FOREIGN KEY (\`roleId\`) REFERENCES \`roles\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE \`opportunities\` ADD CONSTRAINT \`FK_e2c4f44d798310be0090da56567\` FOREIGN KEY (\`customerId\`) REFERENCES \`customers\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE \`opportunities\` ADD CONSTRAINT \`FK_6f759d83449ae8a9eb2327af8df\` FOREIGN KEY (\`assignedUserId\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE \`campaigns\` ADD CONSTRAINT \`FK_19e6402a02a408ea0ce5c2d5e3f\` FOREIGN KEY (\`creatorId\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
+    }
+
+    public async down(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(`ALTER TABLE \`campaigns\` DROP FOREIGN KEY \`FK_19e6402a02a408ea0ce5c2d5e3f\``);
+        await queryRunner.query(`ALTER TABLE \`opportunities\` DROP FOREIGN KEY \`FK_6f759d83449ae8a9eb2327af8df\``);
+        await queryRunner.query(`ALTER TABLE \`opportunities\` DROP FOREIGN KEY \`FK_e2c4f44d798310be0090da56567\``);
+        await queryRunner.query(`ALTER TABLE \`users\` DROP FOREIGN KEY \`FK_368e146b785b574f42ae9e53d5e\``);
+        await queryRunner.query(`ALTER TABLE \`users\` DROP FOREIGN KEY \`FK_554d853741f2083faaa5794d2ae\``);
+        await queryRunner.query(`ALTER TABLE \`tickets\` DROP FOREIGN KEY \`FK_367d43d5d04a904fe04a1d641ab\``);
+        await queryRunner.query(`ALTER TABLE \`tickets\` DROP FOREIGN KEY \`FK_cbe17dea439f414f60ae768f9cb\``);
+        await queryRunner.query(`ALTER TABLE \`tickets\` DROP FOREIGN KEY \`FK_7a1f978a1c1a6b2b1133014b4b2\``);
+        await queryRunner.query(`ALTER TABLE \`contracts\` DROP FOREIGN KEY \`FK_6238d92bb7e644c592c2eae97fb\``);
+        await queryRunner.query(`ALTER TABLE \`contracts\` DROP FOREIGN KEY \`FK_18cbf0097753ef0065d75ec0cb8\``);
+        await queryRunner.query(`ALTER TABLE \`contracts\` DROP FOREIGN KEY \`FK_22c6e574e547c5b5d79e9c84380\``);
+        await queryRunner.query(`ALTER TABLE \`leads\` DROP FOREIGN KEY \`FK_f7a7e8a6161a77f330a0aa36eec\``);
+        await queryRunner.query(`ALTER TABLE \`leads\` DROP FOREIGN KEY \`FK_1cc4d3177a1b83286f4382877f5\``);
+        await queryRunner.query(`ALTER TABLE \`contacts\` DROP FOREIGN KEY \`FK_f418137d00d8b5a598400bbf57a\``);
+        await queryRunner.query(`DROP TABLE \`campaigns\``);
+        await queryRunner.query(`DROP TABLE \`customers\``);
+        await queryRunner.query(`DROP TABLE \`opportunities\``);
+        await queryRunner.query(`DROP INDEX \`IDX_97672ac88f789774dd47f7c8be\` ON \`users\``);
+        await queryRunner.query(`DROP INDEX \`IDX_fe0bb3f6520ee0469504521e71\` ON \`users\``);
+        await queryRunner.query(`DROP TABLE \`users\``);
+        await queryRunner.query(`DROP INDEX \`IDX_4e3c3787c6138011839f329637\` ON \`tickets\``);
+        await queryRunner.query(`DROP TABLE \`tickets\``);
+        await queryRunner.query(`DROP INDEX \`IDX_793609267045de22b8de569717\` ON \`contracts\``);
+        await queryRunner.query(`DROP TABLE \`contracts\``);
+        await queryRunner.query(`DROP TABLE \`leads\``);
+        await queryRunner.query(`DROP INDEX \`IDX_f6d54f95c31b73fb1bdd8e91d0\` ON \`roles\``);
+        await queryRunner.query(`DROP INDEX \`IDX_648e3f5447f725579d7d4ffdfb\` ON \`roles\``);
+        await queryRunner.query(`DROP TABLE \`roles\``);
+        await queryRunner.query(`DROP TABLE \`departments\``);
+        await queryRunner.query(`DROP TABLE \`contacts\``);
+    }
+
+}