yourname 5 hónapja
szülő
commit
4de68daf49

+ 14 - 0
package.json

@@ -0,0 +1,14 @@
+{
+  "dependencies": {
+    "@hono/zod-openapi": "^0.19.8",
+    "mysql2": "^3.14.1",
+    "reflect-metadata": "^0.2.2",
+    "typeorm": "^0.3.25",
+    "zod": "^3.25.67"
+  },
+  "devDependencies": {
+    "@types/node": "^24.0.3",
+    "ts-node": "^10.9.2",
+    "typescript": "^5.8.3"
+  }
+}

+ 35 - 0
src/server/data-source.ts

@@ -0,0 +1,35 @@
+import { DataSource } from 'typeorm';
+import { Customer } from './modules/customers/customer.entity';
+import { Contact } from './modules/contacts/contact.entity';
+import { Lead } from './modules/leads/lead.entity';
+import { Opportunity } from './modules/opportunities/opportunity.entity';
+import { Contract } from './modules/contracts/contract.entity';
+import { Ticket } from './modules/tickets/ticket.entity';
+import { User } from './modules/users/user.entity';
+import { Department } from './modules/departments/department.entity';
+import { Role } from './modules/roles/role.entity';
+import { Campaign } from './modules/campaigns/campaign.entity';
+
+export const AppDataSource = new DataSource({
+  type: 'mysql',
+  host: 'localhost',
+  port: 3306,
+  username: 'root',
+  password: 'password',
+  database: 'crm',
+  entities: [
+    Customer,
+    Contact,
+    Lead,
+    Opportunity,
+    Contract,
+    Ticket,
+    User,
+    Department,
+    Role,
+    Campaign
+  ],
+  migrations: ['src/server/migrations/**/*.ts'],
+  synchronize: false,
+  logging: false,
+});

+ 190 - 0
src/server/modules/campaigns/campaign.entity.ts

@@ -0,0 +1,190 @@
+import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany } from 'typeorm';
+import { z } from 'zod';
+import { User } from '@/server/modules/users/user.entity';
+import { Lead } from '@/server/modules/leads/lead.entity';
+
+@Entity('campaigns')
+export class Campaign {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ 
+    name: 'name', 
+    type: 'varchar', 
+    length: 100, 
+    nullable: false,
+    comment: '活动名称' 
+  })
+  name!: string;
+
+  @Column({ 
+    name: 'type', 
+    type: 'varchar', 
+    length: 50, 
+    nullable: false,
+    comment: '活动类型' 
+  })
+  type!: string;
+
+  @Column({ 
+    name: 'status', 
+    type: 'tinyint', 
+    default: 0,
+    comment: '活动状态(0-计划中, 1-进行中, 2-已结束, 3-已取消)' 
+  })
+  status!: number;
+
+  @Column({ 
+    name: 'start_date', 
+    type: 'date', 
+    nullable: false,
+    comment: '开始日期' 
+  })
+  startDate!: Date;
+
+  @Column({ 
+    name: 'end_date', 
+    type: 'date', 
+    nullable: true,
+    comment: '结束日期' 
+  })
+  endDate!: Date | null;
+
+  @Column({ 
+    name: 'budget', 
+    type: 'decimal', 
+    precision: 12, 
+    scale: 2, 
+    nullable: true,
+    comment: '预算金额' 
+  })
+  budget!: number | null;
+
+  @Column({ 
+    name: 'actual_cost', 
+    type: 'decimal', 
+    precision: 12, 
+    scale: 2, 
+    nullable: true,
+    comment: '实际成本' 
+  })
+  actualCost!: number | null;
+
+  @Column({ 
+    name: 'expected_revenue', 
+    type: 'decimal', 
+    precision: 12, 
+    scale: 2, 
+    nullable: true,
+    comment: '预计收入' 
+  })
+  expectedRevenue!: number | null;
+
+  @Column({ 
+    name: 'description', 
+    type: 'text', 
+    nullable: true,
+    comment: '活动描述' 
+  })
+  description!: string | null;
+
+  @Column({ 
+    name: 'created_by', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: true,
+    comment: '创建人ID' 
+  })
+  createdBy!: number | null;
+
+  @Column({ 
+    name: 'is_deleted', 
+    type: 'tinyint', 
+    default: 0,
+    comment: '删除状态(0-未删除, 1-已删除)' 
+  })
+  isDeleted!: number;
+
+  @Column({ 
+    name: 'created_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    comment: '创建时间' 
+  })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+    comment: '更新时间' 
+  })
+  updatedAt!: Date;
+
+  // 关系定义
+  @ManyToOne(() => User, user => user.id)
+  creator!: User;
+
+  @OneToMany(() => Lead, lead => lead.id)
+  leads!: Lead[];
+}
+
+export const CampaignSchema = z.object({
+  id: z.number().int().positive().openapi({ 
+    description: '活动ID',
+    example: 1 
+  }),
+  name: z.string().min(1).max(100).openapi({ 
+    description: '活动名称',
+    example: '2023年春季促销活动' 
+  }),
+  type: z.string().max(50).openapi({ 
+    description: '活动类型',
+    example: '促销活动' 
+  }),
+  status: z.number().int().min(0).max(3).openapi({ 
+    description: '活动状态(0-计划中, 1-进行中, 2-已结束, 3-已取消)',
+    example: 1 
+  }),
+  startDate: z.date().openapi({ 
+    description: '开始日期',
+    example: '2023-03-01' 
+  }),
+  endDate: z.date().nullable().openapi({ 
+    description: '结束日期',
+    example: '2023-04-30' 
+  }),
+  budget: z.number().nullable().openapi({ 
+    description: '预算金额',
+    example: 50000.00 
+  }),
+  actualCost: z.number().nullable().openapi({ 
+    description: '实际成本',
+    example: 45000.00 
+  }),
+  expectedRevenue: z.number().nullable().openapi({ 
+    description: '预计收入',
+    example: 200000.00 
+  }),
+  description: z.string().nullable().openapi({ 
+    description: '活动描述',
+    example: '春季产品促销活动,提供折扣优惠' 
+  }),
+  createdBy: z.number().int().positive().nullable().openapi({ 
+    description: '创建人ID',
+    example: 1 
+  }),
+  isDeleted: z.number().int().min(0).max(1).openapi({ 
+    description: '删除状态(0-未删除, 1-已删除)',
+    example: 0 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-02-15T00:00:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2023-02-15T00:00:00Z' 
+  })
+});

+ 146 - 0
src/server/modules/contacts/contact.entity.ts

@@ -0,0 +1,146 @@
+import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
+import { z } from 'zod';
+import { Customer } from '@/server/modules/customers/customer.entity';
+
+@Entity('contacts')
+export class Contact {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ 
+    name: 'customer_id', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: false,
+    comment: '客户ID' 
+  })
+  customerId!: number;
+
+  @Column({ 
+    name: 'name', 
+    type: 'varchar', 
+    length: 50, 
+    nullable: false,
+    comment: '联系人姓名' 
+  })
+  name!: string;
+
+  @Column({ 
+    name: 'position', 
+    type: 'varchar', 
+    length: 50, 
+    nullable: true,
+    comment: '职位' 
+  })
+  position!: string | null;
+
+  @Column({ 
+    name: 'phone', 
+    type: 'varchar', 
+    length: 20, 
+    nullable: true,
+    comment: '电话' 
+  })
+  phone!: string | null;
+
+  @Column({ 
+    name: 'email', 
+    type: 'varchar', 
+    length: 100, 
+    nullable: true,
+    comment: '邮箱' 
+  })
+  email!: string | null;
+
+  @Column({ 
+    name: 'is_primary', 
+    type: 'tinyint', 
+    default: 0,
+    comment: '是否主要联系人(0-否, 1-是)' 
+  })
+  isPrimary!: number;
+
+  @Column({ 
+    name: 'status', 
+    type: 'tinyint', 
+    default: 1,
+    comment: '状态(0-禁用, 1-启用)' 
+  })
+  status!: number;
+
+  @Column({ 
+    name: 'is_deleted', 
+    type: 'tinyint', 
+    default: 0,
+    comment: '删除状态(0-未删除, 1-已删除)' 
+  })
+  isDeleted!: number;
+
+  @Column({ 
+    name: 'created_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    comment: '创建时间' 
+  })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+    comment: '更新时间' 
+  })
+  updatedAt!: Date;
+
+  // 关系定义
+  @ManyToOne(() => Customer, customer => customer.contacts)
+  customer!: Customer;
+}
+
+export const ContactSchema = z.object({
+  id: z.number().int().positive().openapi({ 
+    description: '联系人ID',
+    example: 1 
+  }),
+  customerId: z.number().int().positive().openapi({ 
+    description: '客户ID',
+    example: 1 
+  }),
+  name: z.string().min(1).max(50).openapi({ 
+    description: '联系人姓名',
+    example: '张三' 
+  }),
+  position: z.string().max(50).nullable().openapi({ 
+    description: '职位',
+    example: '技术总监' 
+  }),
+  phone: z.string().max(20).nullable().openapi({ 
+    description: '电话',
+    example: '13800138000' 
+  }),
+  email: z.string().email().max(100).nullable().openapi({ 
+    description: '邮箱',
+    example: 'zhangsan@abc.com' 
+  }),
+  isPrimary: z.number().int().min(0).max(1).openapi({ 
+    description: '是否主要联系人(0-否, 1-是)',
+    example: 1 
+  }),
+  status: z.number().int().min(0).max(1).openapi({ 
+    description: '状态(0-禁用, 1-启用)',
+    example: 1 
+  }),
+  isDeleted: z.number().int().min(0).max(1).openapi({ 
+    description: '删除状态(0-未删除, 1-已删除)',
+    example: 0 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-01T00:00:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2023-01-01T00:00:00Z' 
+  })
+});

+ 230 - 0
src/server/modules/contracts/contract.entity.ts

@@ -0,0 +1,230 @@
+import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
+import { z } from 'zod';
+import { Customer } from '@/server/modules/customers/customer.entity';
+import { Opportunity } from '@/server/modules/opportunities/opportunity.entity';
+import { User } from '@/server/modules/users/user.entity';
+
+@Entity('contracts')
+export class Contract {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ 
+    name: 'contract_no', 
+    type: 'varchar', 
+    length: 50, 
+    nullable: false,
+    unique: true,
+    comment: '合同编号' 
+  })
+  contractNo!: string;
+
+  @Column({ 
+    name: 'title', 
+    type: 'varchar', 
+    length: 100, 
+    nullable: false,
+    comment: '合同标题' 
+  })
+  title!: string;
+
+  @Column({ 
+    name: 'customer_id', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: false,
+    comment: '客户ID' 
+  })
+  customerId!: number;
+
+  @Column({ 
+    name: 'opportunity_id', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: true,
+    comment: '销售机会ID' 
+  })
+  opportunityId!: number | null;
+
+  @Column({ 
+    name: 'amount', 
+    type: 'decimal', 
+    precision: 12, 
+    scale: 2, 
+    nullable: false,
+    comment: '合同金额' 
+  })
+  amount!: number;
+
+  @Column({ 
+    name: 'signed_date', 
+    type: 'date', 
+    nullable: false,
+    comment: '签约日期' 
+  })
+  signedDate!: Date;
+
+  @Column({ 
+    name: 'start_date', 
+    type: 'date', 
+    nullable: false,
+    comment: '开始日期' 
+  })
+  startDate!: Date;
+
+  @Column({ 
+    name: 'end_date', 
+    type: 'date', 
+    nullable: true,
+    comment: '结束日期' 
+  })
+  endDate!: Date | null;
+
+  @Column({ 
+    name: 'status', 
+    type: 'tinyint', 
+    default: 0,
+    comment: '合同状态(0-草稿, 1-已签约, 2-执行中, 3-已完成, 4-已终止)' 
+  })
+  status!: number;
+
+  @Column({ 
+    name: 'payment_terms', 
+    type: 'text', 
+    nullable: true,
+    comment: '付款条件' 
+  })
+  paymentTerms!: string | null;
+
+  @Column({ 
+    name: 'signed_by', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: true,
+    comment: '签约人ID' 
+  })
+  signedBy!: number | null;
+
+  @Column({ 
+    name: 'attachment_url', 
+    type: 'varchar', 
+    length: 255, 
+    nullable: true,
+    comment: '合同附件URL' 
+  })
+  attachmentUrl!: string | null;
+
+  @Column({ 
+    name: 'description', 
+    type: 'text', 
+    nullable: true,
+    comment: '合同描述' 
+  })
+  description!: string | null;
+
+  @Column({ 
+    name: 'is_deleted', 
+    type: 'tinyint', 
+    default: 0,
+    comment: '删除状态(0-未删除, 1-已删除)' 
+  })
+  isDeleted!: number;
+
+  @Column({ 
+    name: 'created_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    comment: '创建时间' 
+  })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+    comment: '更新时间' 
+  })
+  updatedAt!: Date;
+
+  // 关系定义
+  @ManyToOne(() => Customer, customer => customer.contracts)
+  customer!: Customer;
+
+  @ManyToOne(() => Opportunity, opportunity => opportunity.id)
+  opportunity!: Opportunity;
+
+  @ManyToOne(() => User, user => user.id)
+  signedUser!: User;
+}
+
+export const ContractSchema = z.object({
+  id: z.number().int().positive().openapi({ 
+    description: '合同ID',
+    example: 1 
+  }),
+  contractNo: z.string().min(1).max(50).openapi({ 
+    description: '合同编号',
+    example: 'HT-20230001' 
+  }),
+  title: z.string().min(1).max(100).openapi({ 
+    description: '合同标题',
+    example: 'ERP系统升级服务合同' 
+  }),
+  customerId: z.number().int().positive().openapi({ 
+    description: '客户ID',
+    example: 1 
+  }),
+  opportunityId: z.number().int().positive().nullable().openapi({ 
+    description: '销售机会ID',
+    example: 1 
+  }),
+  amount: z.number().openapi({ 
+    description: '合同金额',
+    example: 150000.00 
+  }),
+  signedDate: z.date().openapi({ 
+    description: '签约日期',
+    example: '2023-01-15' 
+  }),
+  startDate: z.date().openapi({ 
+    description: '开始日期',
+    example: '2023-02-01' 
+  }),
+  endDate: z.date().nullable().openapi({ 
+    description: '结束日期',
+    example: '2023-12-31' 
+  }),
+  status: z.number().int().min(0).max(4).openapi({ 
+    description: '合同状态(0-草稿, 1-已签约, 2-执行中, 3-已完成, 4-已终止)',
+    example: 2 
+  }),
+  paymentTerms: z.string().nullable().openapi({ 
+    description: '付款条件',
+    example: '首付50%,验收后付40%,质保金10%' 
+  }),
+  signedBy: z.number().int().positive().nullable().openapi({ 
+    description: '签约人ID',
+    example: 1 
+  }),
+  attachmentUrl: z.string().url().max(255).nullable().openapi({ 
+    description: '合同附件URL',
+    example: 'https://example.com/contracts/HT-20230001.pdf' 
+  }),
+  description: z.string().nullable().openapi({ 
+    description: '合同描述',
+    example: '为客户提供ERP系统升级服务' 
+  }),
+  isDeleted: z.number().int().min(0).max(1).openapi({ 
+    description: '删除状态(0-未删除, 1-已删除)',
+    example: 0 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-01T00:00:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2023-01-01T00:00:00Z' 
+  })
+});

+ 197 - 0
src/server/modules/customers/customer.entity.ts

@@ -0,0 +1,197 @@
+import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
+import { z } from '@hono/zod-openapi';
+import { Contact } from '@/server/modules/contacts/contact.entity';
+import { Opportunity } from '@/server/modules/opportunities/opportunity.entity';
+import { Contract } from '@/server/modules/contracts/contract.entity';
+import { Ticket } from '@/server/modules/tickets/ticket.entity';
+
+@Entity('customers')
+export class Customer {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ 
+    name: 'name', 
+    type: 'varchar', 
+    length: 100, 
+    nullable: false,
+    comment: '客户名称' 
+  })
+  name!: string;
+
+  @Column({ 
+    name: 'type', 
+    type: 'tinyint', 
+    nullable: false,
+    default: 0,
+    comment: '客户类型(0-个人, 1-企业)' 
+  })
+  type!: number;
+
+  @Column({ 
+    name: 'industry', 
+    type: 'varchar', 
+    length: 50, 
+    nullable: true,
+    comment: '所属行业' 
+  })
+  industry!: string | null;
+
+  @Column({ 
+    name: 'scale', 
+    type: 'varchar', 
+    length: 20, 
+    nullable: true,
+    comment: '企业规模' 
+  })
+  scale!: string | null;
+
+  @Column({ 
+    name: 'address', 
+    type: 'varchar', 
+    length: 255, 
+    nullable: true,
+    comment: '地址' 
+  })
+  address!: string | null;
+
+  @Column({ 
+    name: 'phone', 
+    type: 'varchar', 
+    length: 20, 
+    nullable: true,
+    comment: '电话' 
+  })
+  phone!: string | null;
+
+  @Column({ 
+    name: 'email', 
+    type: 'varchar', 
+    length: 100, 
+    nullable: true,
+    comment: '邮箱' 
+  })
+  email!: string | null;
+
+  @Column({ 
+    name: 'website', 
+    type: 'varchar', 
+    length: 100, 
+    nullable: true,
+    comment: '网站' 
+  })
+  website!: string | null;
+
+  @Column({ 
+    name: 'description', 
+    type: 'text', 
+    nullable: true,
+    comment: '客户描述' 
+  })
+  description!: string | null;
+
+  @Column({ 
+    name: 'status', 
+    type: 'tinyint', 
+    default: 1,
+    comment: '状态(0-禁用, 1-启用)' 
+  })
+  status!: number;
+
+  @Column({ 
+    name: 'is_deleted', 
+    type: 'tinyint', 
+    default: 0,
+    comment: '删除状态(0-未删除, 1-已删除)' 
+  })
+  isDeleted!: number;
+
+  @Column({ 
+    name: 'created_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    comment: '创建时间' 
+  })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+    comment: '更新时间' 
+  })
+  updatedAt!: Date;
+
+  // 关系定义
+  @OneToMany(() => Contact, contact => contact.customer)
+  contacts!: Contact[];
+
+  @OneToMany(() => Opportunity, opportunity => opportunity.customer)
+  opportunities!: Opportunity[];
+
+  @OneToMany(() => Contract, contract => contract.customer)
+  contracts!: Contract[];
+
+  @OneToMany(() => Ticket, ticket => ticket.customer)
+  tickets!: Ticket[];
+}
+
+export const CustomerSchema = z.object({
+  id: z.number().int().positive().openapi({ 
+    description: '客户ID',
+    example: 1 
+  }),
+  name: z.string().min(1).max(100).openapi({ 
+    description: '客户名称',
+    example: 'ABC公司' 
+  }),
+  type: z.number().int().min(0).max(1).openapi({ 
+    description: '客户类型(0-个人, 1-企业)',
+    example: 1 
+  }),
+  industry: z.string().max(50).nullable().openapi({ 
+    description: '所属行业',
+    example: '信息技术' 
+  }),
+  scale: z.string().max(20).nullable().openapi({ 
+    description: '企业规模',
+    example: '100-500人' 
+  }),
+  address: z.string().max(255).nullable().openapi({ 
+    description: '地址',
+    example: '北京市海淀区中关村' 
+  }),
+  phone: z.string().max(20).nullable().openapi({ 
+    description: '电话',
+    example: '010-12345678' 
+  }),
+  email: z.string().email().max(100).nullable().openapi({ 
+    description: '邮箱',
+    example: 'contact@abc.com' 
+  }),
+  website: z.string().url().max(100).nullable().openapi({ 
+    description: '网站',
+    example: 'https://www.abc.com' 
+  }),
+  description: z.string().nullable().openapi({ 
+    description: '客户描述',
+    example: '这是一家高科技企业' 
+  }),
+  status: z.number().int().min(0).max(1).openapi({ 
+    description: '状态(0-禁用, 1-启用)',
+    example: 1 
+  }),
+  isDeleted: z.number().int().min(0).max(1).openapi({ 
+    description: '删除状态(0-未删除, 1-已删除)',
+    example: 0 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-01T00:00:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2023-01-01T00:00:00Z' 
+  })
+});

+ 120 - 0
src/server/modules/departments/department.entity.ts

@@ -0,0 +1,120 @@
+import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
+import { z } from 'zod';
+import { User } from '@/server/modules/users/user.entity';
+
+@Entity('departments')
+export class Department {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ 
+    name: 'name', 
+    type: 'varchar', 
+    length: 50, 
+    nullable: false,
+    comment: '部门名称' 
+  })
+  name!: string;
+
+  @Column({ 
+    name: 'parent_id', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: true,
+    comment: '父部门ID' 
+  })
+  parentId!: number | null;
+
+  @Column({ 
+    name: 'sort_order', 
+    type: 'int', 
+    nullable: true,
+    default: 0,
+    comment: '排序序号' 
+  })
+  sortOrder!: number | null;
+
+  @Column({ 
+    name: 'description', 
+    type: 'text', 
+    nullable: true,
+    comment: '部门描述' 
+  })
+  description!: string | null;
+
+  @Column({ 
+    name: 'status', 
+    type: 'tinyint', 
+    default: 1,
+    comment: '状态(0-禁用, 1-启用)' 
+  })
+  status!: number;
+
+  @Column({ 
+    name: 'is_deleted', 
+    type: 'tinyint', 
+    default: 0,
+    comment: '删除状态(0-未删除, 1-已删除)' 
+  })
+  isDeleted!: number;
+
+  @Column({ 
+    name: 'created_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    comment: '创建时间' 
+  })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+    comment: '更新时间' 
+  })
+  updatedAt!: Date;
+
+  // 关系定义
+  @OneToMany(() => User, user => user.department)
+  users!: User[];
+}
+
+export const DepartmentSchema = z.object({
+  id: z.number().int().positive().openapi({ 
+    description: '部门ID',
+    example: 1 
+  }),
+  name: z.string().min(1).max(50).openapi({ 
+    description: '部门名称',
+    example: '销售部' 
+  }),
+  parentId: z.number().int().positive().nullable().openapi({ 
+    description: '父部门ID',
+    example: null 
+  }),
+  sortOrder: z.number().int().nullable().openapi({ 
+    description: '排序序号',
+    example: 1 
+  }),
+  description: z.string().nullable().openapi({ 
+    description: '部门描述',
+    example: '负责公司产品销售业务' 
+  }),
+  status: z.number().int().min(0).max(1).openapi({ 
+    description: '状态(0-禁用, 1-启用)',
+    example: 1 
+  }),
+  isDeleted: z.number().int().min(0).max(1).openapi({ 
+    description: '删除状态(0-未删除, 1-已删除)',
+    example: 0 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-01T00:00:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2023-01-01T00:00:00Z' 
+  })
+});

+ 164 - 0
src/server/modules/leads/lead.entity.ts

@@ -0,0 +1,164 @@
+import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
+import { z } from 'zod';
+import { Customer } from '@/server/modules/customers/customer.entity';
+import { User } from '@/server/modules/users/user.entity';
+
+@Entity('leads')
+export class Lead {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ 
+    name: 'title', 
+    type: 'varchar', 
+    length: 100, 
+    nullable: false,
+    comment: '线索标题' 
+  })
+  title!: string;
+
+  @Column({ 
+    name: 'customer_id', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: false,
+    comment: '客户ID' 
+  })
+  customerId!: number;
+
+  @Column({ 
+    name: 'contact_id', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: true,
+    comment: '联系人ID' 
+  })
+  contactId!: number | null;
+
+  @Column({ 
+    name: 'source', 
+    type: 'varchar', 
+    length: 50, 
+    nullable: true,
+    comment: '线索来源' 
+  })
+  source!: string | null;
+
+  @Column({ 
+    name: 'status', 
+    type: 'tinyint', 
+    default: 0,
+    comment: '线索状态(0-新建, 1-跟进中, 2-已转化, 3-已放弃)' 
+  })
+  status!: number;
+
+  @Column({ 
+    name: 'estimated_value', 
+    type: 'decimal', 
+    precision: 10, 
+    scale: 2, 
+    nullable: true,
+    comment: '预估价值' 
+  })
+  estimatedValue!: number | null;
+
+  @Column({ 
+    name: 'assigned_to', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: true,
+    comment: '负责人ID' 
+  })
+  assignedTo!: number | null;
+
+  @Column({ 
+    name: 'description', 
+    type: 'text', 
+    nullable: true,
+    comment: '线索描述' 
+  })
+  description!: string | null;
+
+  @Column({ 
+    name: 'is_deleted', 
+    type: 'tinyint', 
+    default: 0,
+    comment: '删除状态(0-未删除, 1-已删除)' 
+  })
+  isDeleted!: number;
+
+  @Column({ 
+    name: 'created_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    comment: '创建时间' 
+  })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+    comment: '更新时间' 
+  })
+  updatedAt!: Date;
+
+  // 关系定义
+  @ManyToOne(() => Customer, customer => customer.id)
+  customer!: Customer;
+
+  @ManyToOne(() => User, user => user.id)
+  assignedUser!: User;
+}
+
+export const LeadSchema = z.object({
+  id: z.number().int().positive().openapi({ 
+    description: '线索ID',
+    example: 1 
+  }),
+  title: z.string().min(1).max(100).openapi({ 
+    description: '线索标题',
+    example: '企业软件升级需求' 
+  }),
+  customerId: z.number().int().positive().openapi({ 
+    description: '客户ID',
+    example: 1 
+  }),
+  contactId: z.number().int().positive().nullable().openapi({ 
+    description: '联系人ID',
+    example: 1 
+  }),
+  source: z.string().max(50).nullable().openapi({ 
+    description: '线索来源',
+    example: '网站咨询' 
+  }),
+  status: z.number().int().min(0).max(3).openapi({ 
+    description: '线索状态(0-新建, 1-跟进中, 2-已转化, 3-已放弃)',
+    example: 1 
+  }),
+  estimatedValue: z.number().nullable().openapi({ 
+    description: '预估价值',
+    example: 50000.00 
+  }),
+  assignedTo: z.number().int().positive().nullable().openapi({ 
+    description: '负责人ID',
+    example: 1 
+  }),
+  description: z.string().nullable().openapi({ 
+    description: '线索描述',
+    example: '客户需要升级现有ERP系统' 
+  }),
+  isDeleted: z.number().int().min(0).max(1).openapi({ 
+    description: '删除状态(0-未删除, 1-已删除)',
+    example: 0 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-01T00:00:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2023-01-01T00:00:00Z' 
+  })
+});

+ 176 - 0
src/server/modules/opportunities/opportunity.entity.ts

@@ -0,0 +1,176 @@
+import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
+import { z } from 'zod';
+import { Customer } from '@/server/modules/customers/customer.entity';
+import { User } from '@/server/modules/users/user.entity';
+
+@Entity('opportunities')
+export class Opportunity {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ 
+    name: 'title', 
+    type: 'varchar', 
+    length: 100, 
+    nullable: false,
+    comment: '机会标题' 
+  })
+  title!: string;
+
+  @Column({ 
+    name: 'customer_id', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: false,
+    comment: '客户ID' 
+  })
+  customerId!: number;
+
+  @Column({ 
+    name: 'contact_id', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: true,
+    comment: '联系人ID' 
+  })
+  contactId!: number | null;
+
+  @Column({ 
+    name: 'estimated_amount', 
+    type: 'decimal', 
+    precision: 12, 
+    scale: 2, 
+    nullable: false,
+    comment: '预估金额' 
+  })
+  estimatedAmount!: number;
+
+  @Column({ 
+    name: 'probability', 
+    type: 'int', 
+    nullable: true,
+    comment: '赢单概率(%)' 
+  })
+  probability!: number | null;
+
+  @Column({ 
+    name: 'expected_close_date', 
+    type: 'date', 
+    nullable: true,
+    comment: '预计成交日期' 
+  })
+  expectedCloseDate!: Date | null;
+
+  @Column({ 
+    name: 'stage', 
+    type: 'varchar', 
+    length: 50, 
+    nullable: false,
+    comment: '销售阶段' 
+  })
+  stage!: string;
+
+  @Column({ 
+    name: 'assigned_to', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: true,
+    comment: '负责人ID' 
+  })
+  assignedTo!: number | null;
+
+  @Column({ 
+    name: 'description', 
+    type: 'text', 
+    nullable: true,
+    comment: '机会描述' 
+  })
+  description!: string | null;
+
+  @Column({ 
+    name: 'is_deleted', 
+    type: 'tinyint', 
+    default: 0,
+    comment: '删除状态(0-未删除, 1-已删除)' 
+  })
+  isDeleted!: number;
+
+  @Column({ 
+    name: 'created_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    comment: '创建时间' 
+  })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+    comment: '更新时间' 
+  })
+  updatedAt!: Date;
+
+  // 关系定义
+  @ManyToOne(() => Customer, customer => customer.opportunities)
+  customer!: Customer;
+
+  @ManyToOne(() => User, user => user.id)
+  assignedUser!: User;
+}
+
+export const OpportunitySchema = z.object({
+  id: z.number().int().positive().openapi({ 
+    description: '机会ID',
+    example: 1 
+  }),
+  title: z.string().min(1).max(100).openapi({ 
+    description: '机会标题',
+    example: 'ERP系统升级项目' 
+  }),
+  customerId: z.number().int().positive().openapi({ 
+    description: '客户ID',
+    example: 1 
+  }),
+  contactId: z.number().int().positive().nullable().openapi({ 
+    description: '联系人ID',
+    example: 1 
+  }),
+  estimatedAmount: z.number().openapi({ 
+    description: '预估金额',
+    example: 150000.00 
+  }),
+  probability: z.number().int().min(0).max(100).nullable().openapi({ 
+    description: '赢单概率(%)',
+    example: 60 
+  }),
+  expectedCloseDate: z.date().nullable().openapi({ 
+    description: '预计成交日期',
+    example: '2023-06-30' 
+  }),
+  stage: z.string().max(50).openapi({ 
+    description: '销售阶段',
+    example: '需求确认' 
+  }),
+  assignedTo: z.number().int().positive().nullable().openapi({ 
+    description: '负责人ID',
+    example: 1 
+  }),
+  description: z.string().nullable().openapi({ 
+    description: '机会描述',
+    example: '客户计划升级现有ERP系统,预算约15万元' 
+  }),
+  isDeleted: z.number().int().min(0).max(1).openapi({ 
+    description: '删除状态(0-未删除, 1-已删除)',
+    example: 0 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-01T00:00:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2023-01-01T00:00:00Z' 
+  })
+});

+ 121 - 0
src/server/modules/roles/role.entity.ts

@@ -0,0 +1,121 @@
+import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
+import { z } from 'zod';
+import { User } from '@/server/modules/users/user.entity';
+
+@Entity('roles')
+export class Role {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ 
+    name: 'name', 
+    type: 'varchar', 
+    length: 50, 
+    nullable: false,
+    unique: true,
+    comment: '角色名称' 
+  })
+  name!: string;
+
+  @Column({ 
+    name: 'code', 
+    type: 'varchar', 
+    length: 50, 
+    nullable: false,
+    unique: true,
+    comment: '角色编码' 
+  })
+  code!: string;
+
+  @Column({ 
+    name: 'description', 
+    type: 'text', 
+    nullable: true,
+    comment: '角色描述' 
+  })
+  description!: string | null;
+
+  @Column({ 
+    name: 'permissions', 
+    type: 'text', 
+    nullable: true,
+    comment: '权限列表(JSON)' 
+  })
+  permissions!: string | null;
+
+  @Column({ 
+    name: 'status', 
+    type: 'tinyint', 
+    default: 1,
+    comment: '状态(0-禁用, 1-启用)' 
+  })
+  status!: number;
+
+  @Column({ 
+    name: 'is_deleted', 
+    type: 'tinyint', 
+    default: 0,
+    comment: '删除状态(0-未删除, 1-已删除)' 
+  })
+  isDeleted!: number;
+
+  @Column({ 
+    name: 'created_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    comment: '创建时间' 
+  })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+    comment: '更新时间' 
+  })
+  updatedAt!: Date;
+
+  // 关系定义
+  @OneToMany(() => User, user => user.role)
+  users!: User[];
+}
+
+export const RoleSchema = z.object({
+  id: z.number().int().positive().openapi({ 
+    description: '角色ID',
+    example: 1 
+  }),
+  name: z.string().min(1).max(50).openapi({ 
+    description: '角色名称',
+    example: '管理员' 
+  }),
+  code: z.string().min(1).max(50).openapi({ 
+    description: '角色编码',
+    example: 'admin' 
+  }),
+  description: z.string().nullable().openapi({ 
+    description: '角色描述',
+    example: '系统管理员,拥有所有权限' 
+  }),
+  permissions: z.string().nullable().openapi({ 
+    description: '权限列表(JSON)',
+    example: '["user:read", "user:write", "role:manage"]' 
+  }),
+  status: z.number().int().min(0).max(1).openapi({ 
+    description: '状态(0-禁用, 1-启用)',
+    example: 1 
+  }),
+  isDeleted: z.number().int().min(0).max(1).openapi({ 
+    description: '删除状态(0-未删除, 1-已删除)',
+    example: 0 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-01T00:00:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2023-01-01T00:00:00Z' 
+  })
+});

+ 229 - 0
src/server/modules/tickets/ticket.entity.ts

@@ -0,0 +1,229 @@
+import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
+import { z } from 'zod';
+import { Customer } from '@/server/modules/customers/customer.entity';
+import { Contract } from '@/server/modules/contracts/contract.entity';
+import { User } from '@/server/modules/users/user.entity';
+
+@Entity('tickets')
+export class Ticket {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ 
+    name: 'ticket_no', 
+    type: 'varchar', 
+    length: 50, 
+    nullable: false,
+    unique: true,
+    comment: '工单编号' 
+  })
+  ticketNo!: string;
+
+  @Column({ 
+    name: 'title', 
+    type: 'varchar', 
+    length: 100, 
+    nullable: false,
+    comment: '工单标题' 
+  })
+  title!: string;
+
+  @Column({ 
+    name: 'customer_id', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: false,
+    comment: '客户ID' 
+  })
+  customerId!: number;
+
+  @Column({ 
+    name: 'contract_id', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: true,
+    comment: '合同ID' 
+  })
+  contractId!: number | null;
+
+  @Column({ 
+    name: 'contact_id', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: true,
+    comment: '联系人ID' 
+  })
+  contactId!: number | null;
+
+  @Column({ 
+    name: 'type', 
+    type: 'varchar', 
+    length: 50, 
+    nullable: false,
+    comment: '工单类型' 
+  })
+  type!: string;
+
+  @Column({ 
+    name: 'status', 
+    type: 'tinyint', 
+    default: 0,
+    comment: '工单状态(0-新建, 1-处理中, 2-已解决, 3-已关闭, 4-已拒绝)' 
+  })
+  status!: number;
+
+  @Column({ 
+    name: 'priority', 
+    type: 'tinyint', 
+    default: 1,
+    comment: '优先级(0-低, 1-中, 2-高, 3-紧急)' 
+  })
+  priority!: number;
+
+  @Column({ 
+    name: 'assigned_to', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: true,
+    comment: '负责人ID' 
+  })
+  assignedTo!: number | null;
+
+  @Column({ 
+    name: 'description', 
+    type: 'text', 
+    nullable: false,
+    comment: '问题描述' 
+  })
+  description!: string;
+
+  @Column({ 
+    name: 'solution', 
+    type: 'text', 
+    nullable: true,
+    comment: '解决方案' 
+  })
+  solution!: string | null;
+
+  @Column({ 
+    name: 'resolved_at', 
+    type: 'timestamp', 
+    nullable: true,
+    comment: '解决时间' 
+  })
+  resolvedAt!: Date | null;
+
+  @Column({ 
+    name: 'closed_at', 
+    type: 'timestamp', 
+    nullable: true,
+    comment: '关闭时间' 
+  })
+  closedAt!: Date | null;
+
+  @Column({ 
+    name: 'is_deleted', 
+    type: 'tinyint', 
+    default: 0,
+    comment: '删除状态(0-未删除, 1-已删除)' 
+  })
+  isDeleted!: number;
+
+  @Column({ 
+    name: 'created_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    comment: '创建时间' 
+  })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+    comment: '更新时间' 
+  })
+  updatedAt!: Date;
+
+  // 关系定义
+  @ManyToOne(() => Customer, customer => customer.tickets)
+  customer!: Customer;
+
+  @ManyToOne(() => Contract, contract => contract.id)
+  contract!: Contract;
+
+  @ManyToOne(() => User, user => user.id)
+  assignedUser!: User;
+}
+
+export const TicketSchema = z.object({
+  id: z.number().int().positive().openapi({ 
+    description: '工单ID',
+    example: 1 
+  }),
+  ticketNo: z.string().min(1).max(50).openapi({ 
+    description: '工单编号',
+    example: 'TK-20230001' 
+  }),
+  title: z.string().min(1).max(100).openapi({ 
+    description: '工单标题',
+    example: '系统登录异常' 
+  }),
+  customerId: z.number().int().positive().openapi({ 
+    description: '客户ID',
+    example: 1 
+  }),
+  contractId: z.number().int().positive().nullable().openapi({ 
+    description: '合同ID',
+    example: 1 
+  }),
+  contactId: z.number().int().positive().nullable().openapi({ 
+    description: '联系人ID',
+    example: 1 
+  }),
+  type: z.string().max(50).openapi({ 
+    description: '工单类型',
+    example: '技术支持' 
+  }),
+  status: z.number().int().min(0).max(4).openapi({ 
+    description: '工单状态(0-新建, 1-处理中, 2-已解决, 3-已关闭, 4-已拒绝)',
+    example: 1 
+  }),
+  priority: z.number().int().min(0).max(3).openapi({ 
+    description: '优先级(0-低, 1-中, 2-高, 3-紧急)',
+    example: 2 
+  }),
+  assignedTo: z.number().int().positive().nullable().openapi({ 
+    description: '负责人ID',
+    example: 1 
+  }),
+  description: z.string().openapi({ 
+    description: '问题描述',
+    example: '用户反馈无法登录系统,提示密码错误' 
+  }),
+  solution: z.string().nullable().openapi({ 
+    description: '解决方案',
+    example: '已重置用户密码并指导用户完成登录' 
+  }),
+  resolvedAt: z.date().nullable().openapi({ 
+    description: '解决时间',
+    example: '2023-01-16T10:30:00Z' 
+  }),
+  closedAt: z.date().nullable().openapi({ 
+    description: '关闭时间',
+    example: '2023-01-16T14:00:00Z' 
+  }),
+  isDeleted: z.number().int().min(0).max(1).openapi({ 
+    description: '删除状态(0-未删除, 1-已删除)',
+    example: 0 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-16T09:00:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2023-01-16T14:00:00Z' 
+  })
+});

+ 220 - 0
src/server/modules/users/user.entity.ts

@@ -0,0 +1,220 @@
+import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany } from 'typeorm';
+import { z } from 'zod';
+import { Department } from '@/server/modules/departments/department.entity';
+import { Role } from '@/server/modules/roles/role.entity';
+import { Lead } from '@/server/modules/leads/lead.entity';
+import { Opportunity } from '@/server/modules/opportunities/opportunity.entity';
+import { Contract } from '@/server/modules/contracts/contract.entity';
+import { Ticket } from '@/server/modules/tickets/ticket.entity';
+
+@Entity('users')
+export class User {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ 
+    name: 'username', 
+    type: 'varchar', 
+    length: 50, 
+    nullable: false,
+    unique: true,
+    comment: '用户名' 
+  })
+  username!: string;
+
+  @Column({ 
+    name: 'password', 
+    type: 'varchar', 
+    length: 100, 
+    nullable: false,
+    comment: '密码(加密存储)' 
+  })
+  password!: string;
+
+  @Column({ 
+    name: 'name', 
+    type: 'varchar', 
+    length: 50, 
+    nullable: false,
+    comment: '姓名' 
+  })
+  name!: string;
+
+  @Column({ 
+    name: 'email', 
+    type: 'varchar', 
+    length: 100, 
+    nullable: true,
+    unique: true,
+    comment: '邮箱' 
+  })
+  email!: string | null;
+
+  @Column({ 
+    name: 'phone', 
+    type: 'varchar', 
+    length: 20, 
+    nullable: true,
+    comment: '电话' 
+  })
+  phone!: string | null;
+
+  @Column({ 
+    name: 'department_id', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: true,
+    comment: '部门ID' 
+  })
+  departmentId!: number | null;
+
+  @Column({ 
+    name: 'role_id', 
+    type: 'int', 
+    unsigned: true, 
+    nullable: true,
+    comment: '角色ID' 
+  })
+  roleId!: number | null;
+
+  @Column({ 
+    name: 'position', 
+    type: 'varchar', 
+    length: 50, 
+    nullable: true,
+    comment: '职位' 
+  })
+  position!: string | null;
+
+  @Column({ 
+    name: 'avatar_url', 
+    type: 'varchar', 
+    length: 255, 
+    nullable: true,
+    comment: '头像URL' 
+  })
+  avatarUrl!: string | null;
+
+  @Column({ 
+    name: 'last_login_at', 
+    type: 'timestamp', 
+    nullable: true,
+    comment: '最后登录时间' 
+  })
+  lastLoginAt!: Date | null;
+
+  @Column({ 
+    name: 'status', 
+    type: 'tinyint', 
+    default: 1,
+    comment: '状态(0-禁用, 1-启用)' 
+  })
+  status!: number;
+
+  @Column({ 
+    name: 'is_deleted', 
+    type: 'tinyint', 
+    default: 0,
+    comment: '删除状态(0-未删除, 1-已删除)' 
+  })
+  isDeleted!: number;
+
+  @Column({ 
+    name: 'created_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    comment: '创建时间' 
+  })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+    comment: '更新时间' 
+  })
+  updatedAt!: Date;
+
+  // 关系定义
+  @ManyToOne(() => Department, department => department.id)
+  department!: Department;
+
+  @ManyToOne(() => Role, role => role.id)
+  role!: Role;
+
+  @OneToMany(() => Lead, lead => lead.assignedUser)
+  assignedLeads!: Lead[];
+
+  @OneToMany(() => Opportunity, opportunity => opportunity.assignedUser)
+  assignedOpportunities!: Opportunity[];
+
+  @OneToMany(() => Contract, contract => contract.signedUser)
+  signedContracts!: Contract[];
+
+  @OneToMany(() => Ticket, ticket => ticket.assignedUser)
+  assignedTickets!: Ticket[];
+}
+
+export const UserSchema = z.object({
+  id: z.number().int().positive().openapi({ 
+    description: '用户ID',
+    example: 1 
+  }),
+  username: z.string().min(1).max(50).openapi({ 
+    description: '用户名',
+    example: 'admin' 
+  }),
+  password: z.string().min(1).max(100).openapi({ 
+    description: '密码(加密存储)',
+    example: '$2a$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' 
+  }),
+  name: z.string().min(1).max(50).openapi({ 
+    description: '姓名',
+    example: '管理员' 
+  }),
+  email: z.string().email().max(100).nullable().openapi({ 
+    description: '邮箱',
+    example: 'admin@example.com' 
+  }),
+  phone: z.string().max(20).nullable().openapi({ 
+    description: '电话',
+    example: '13800138000' 
+  }),
+  departmentId: z.number().int().positive().nullable().openapi({ 
+    description: '部门ID',
+    example: 1 
+  }),
+  roleId: z.number().int().positive().nullable().openapi({ 
+    description: '角色ID',
+    example: 1 
+  }),
+  position: z.string().max(50).nullable().openapi({ 
+    description: '职位',
+    example: '系统管理员' 
+  }),
+  avatarUrl: z.string().url().max(255).nullable().openapi({ 
+    description: '头像URL',
+    example: 'https://example.com/avatars/admin.png' 
+  }),
+  lastLoginAt: z.date().nullable().openapi({ 
+    description: '最后登录时间',
+    example: '2023-01-10T08:30:00Z' 
+  }),
+  status: z.number().int().min(0).max(1).openapi({ 
+    description: '状态(0-禁用, 1-启用)',
+    example: 1 
+  }),
+  isDeleted: z.number().int().min(0).max(1).openapi({ 
+    description: '删除状态(0-未删除, 1-已删除)',
+    example: 0 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-01T00:00:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2023-01-01T00:00:00Z' 
+  })
+});

+ 21 - 0
tsconfig.json

@@ -0,0 +1,21 @@
+{
+  "compilerOptions": {
+    "target": "ES2020",
+    "module": "CommonJS",
+    "moduleResolution": "Node",
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "strict": true,
+    "esModuleInterop": true,
+    "skipLibCheck": true,
+    "forceConsistentCasingInFileNames": true,
+    "experimentalDecorators": true,
+    "emitDecoratorMetadata": true,
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["src/*"]
+    }
+  },
+  "include": ["src/**/*"],
+  "exclude": ["node_modules"]
+}