Răsfoiți Sursa

✨ feat(api): 添加多个新实体的CRUD API支持

- 新增区域(areas)、客户(clients)、联系人(contacts)、合同(contracts)、合同续签(contracts-renew)、费用(expenses)和文件(files)实体的API
- 为每个新实体创建对应的CRUD路由、实体类和服务类
- 在数据源代码中注册新实体
- 导入并注册新实体的路由和类型定义

✨ feat(entities): 添加多个业务实体模型

- 实现AreaData(区域)实体及其CRUD服务
- 实现Client(客户)实体及其CRUD服务
- 实现Linkman(联系人)实体及其CRUD服务
- 实现Hetong(合同)和HetongRenew(合同续签)实体及其CRUD服务
- 实现Expense(费用)实体及其CRUD服务
- 实现File(文件)实体及其CRUD服务
- 为所有实体定义Zod验证模式和数据传输对象(DTO)
yourname 8 luni în urmă
părinte
comite
413c91074f

+ 27 - 0
src/server/api.ts

@@ -6,6 +6,15 @@ import rolesRoute from './api/roles/index'
 import customerRoutes from './api/customers/index'
 import opportunityRoutes from './api/opportunities/index'
 import followUpRoutes from './api/follow-ups/index'
+// 新实体路由导入
+import areaRoutes from './api/areas/index'
+import clientRoutes from './api/clients/index'
+import expenseRoutes from './api/expenses/index'
+import fileRoutes from './api/files/index'
+import hetongRoutes from './api/contracts/index'
+import hetongRenewRoutes from './api/contracts-renew/index'
+import linkmanRoutes from './api/contacts/index'
+import logfileRoutes from './api/logs/index'
 import { AuthContext } from './types/context'
 import { AppDataSource } from './data-source'
 
@@ -59,6 +68,15 @@ const roleRoutes = api.route('/api/v1/roles', rolesRoute)
 const customerApiRoutes = api.route('/api/v1/customers', customerRoutes)
 const opportunityApiRoutes = api.route('/api/v1/opportunities', opportunityRoutes)
 const followUpApiRoutes = api.route('/api/v1/follow-ups', followUpRoutes)
+// 新实体路由注册
+const areaApiRoutes = api.route('/api/v1/areas', areaRoutes)
+const clientApiRoutes = api.route('/api/v1/clients', clientRoutes)
+const expenseApiRoutes = api.route('/api/v1/expenses', expenseRoutes)
+const fileApiRoutes = api.route('/api/v1/files', fileRoutes)
+const hetongApiRoutes = api.route('/api/v1/contracts', hetongRoutes)
+const hetongRenewApiRoutes = api.route('/api/v1/contracts-renew', hetongRenewRoutes)
+const linkmanApiRoutes = api.route('/api/v1/contacts', linkmanRoutes)
+const logfileApiRoutes = api.route('/api/v1/logs', logfileRoutes)
 
 export type AuthRoutes = typeof authRoutes
 export type UserRoutes = typeof userRoutes
@@ -66,5 +84,14 @@ export type RoleRoutes = typeof roleRoutes
 export type CustomerRoutes = typeof customerApiRoutes
 export type OpportunityRoutes = typeof opportunityApiRoutes
 export type FollowUpRoutes = typeof followUpApiRoutes
+// 新实体路由类型导出
+export type AreaRoutes = typeof areaApiRoutes
+export type ClientRoutes = typeof clientApiRoutes
+export type ExpenseRoutes = typeof expenseApiRoutes
+export type FileRoutes = typeof fileApiRoutes
+export type HetongRoutes = typeof hetongApiRoutes
+export type HetongRenewRoutes = typeof hetongRenewApiRoutes
+export type LinkmanRoutes = typeof linkmanApiRoutes
+export type LogfileRoutes = typeof logfileApiRoutes
 
 export default api

+ 16 - 0
src/server/api/areas/index.ts

@@ -0,0 +1,16 @@
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { AreaData } from '@/server/modules/areas/area-data.entity';
+import { AreaDataSchema, CreateAreaDataDto, UpdateAreaDataDto } from '@/server/modules/areas/area-data.entity';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const areaRoutes = createCrudRoutes({
+  entity: AreaData,
+  createSchema: CreateAreaDataDto,
+  updateSchema: UpdateAreaDataDto,
+  getSchema: AreaDataSchema,
+  listSchema: AreaDataSchema,
+  searchFields: ['name'],
+  middleware: [authMiddleware]
+});
+
+export default areaRoutes;

+ 16 - 0
src/server/api/clients/index.ts

@@ -0,0 +1,16 @@
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { Client } from '@/server/modules/clients/client.entity';
+import { ClientSchema, CreateClientDto, UpdateClientDto } from '@/server/modules/clients/client.entity';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const clientRoutes = createCrudRoutes({
+  entity: Client,
+  createSchema: CreateClientDto,
+  updateSchema: UpdateClientDto,
+  getSchema: ClientSchema,
+  listSchema: ClientSchema,
+  searchFields: ['companyName', 'contactPerson', 'mobile'],
+  middleware: [authMiddleware]
+});
+
+export default clientRoutes;

+ 16 - 0
src/server/api/contacts/index.ts

@@ -0,0 +1,16 @@
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { Linkman } from '@/server/modules/contacts/linkman.entity';
+import { LinkmanSchema, CreateLinkmanDto, UpdateLinkmanDto } from '@/server/modules/contacts/linkman.entity';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const linkmanRoutes = createCrudRoutes({
+  entity: Linkman,
+  createSchema: CreateLinkmanDto,
+  updateSchema: UpdateLinkmanDto,
+  getSchema: LinkmanSchema,
+  listSchema: LinkmanSchema,
+  searchFields: ['name', 'clientId', 'mobile', 'email'],
+  middleware: [authMiddleware]
+});
+
+export default linkmanRoutes;

+ 16 - 0
src/server/api/contracts-renew/index.ts

@@ -0,0 +1,16 @@
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { HetongRenew } from '@/server/modules/contracts/hetong-renew.entity';
+import { HetongRenewSchema, CreateHetongRenewDto, UpdateHetongRenewDto } from '@/server/modules/contracts/hetong-renew.entity';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const hetongRenewRoutes = createCrudRoutes({
+  entity: HetongRenew,
+  createSchema: CreateHetongRenewDto,
+  updateSchema: UpdateHetongRenewDto,
+  getSchema: HetongRenewSchema,
+  listSchema: HetongRenewSchema,
+  searchFields: ['contractId', 'state', 'auditStatus'],
+  middleware: [authMiddleware]
+});
+
+export default hetongRenewRoutes;

+ 16 - 0
src/server/api/contracts/index.ts

@@ -0,0 +1,16 @@
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { Hetong } from '@/server/modules/contracts/hetong.entity';
+import { HetongSchema, CreateHetongDto, UpdateHetongDto } from '@/server/modules/contracts/hetong.entity';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const hetongRoutes = createCrudRoutes({
+  entity: Hetong,
+  createSchema: CreateHetongDto,
+  updateSchema: UpdateHetongDto,
+  getSchema: HetongSchema,
+  listSchema: HetongSchema,
+  searchFields: ['contractNumber', 'clientId', 'status'],
+  middleware: [authMiddleware]
+});
+
+export default hetongRoutes;

+ 16 - 0
src/server/api/expenses/index.ts

@@ -0,0 +1,16 @@
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { Expense } from '@/server/modules/expenses/expense.entity';
+import { ExpenseSchema, CreateExpenseDto, UpdateExpenseDto } from '@/server/modules/expenses/expense.entity';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const expenseRoutes = createCrudRoutes({
+  entity: Expense,
+  createSchema: CreateExpenseDto,
+  updateSchema: UpdateExpenseDto,
+  getSchema: ExpenseSchema,
+  listSchema: ExpenseSchema,
+  searchFields: ['type', 'status', 'description'],
+  middleware: [authMiddleware]
+});
+
+export default expenseRoutes;

+ 16 - 0
src/server/api/files/index.ts

@@ -0,0 +1,16 @@
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { File } from '@/server/modules/files/file.entity';
+import { FileSchema, CreateFileDto, UpdateFileDto } from '@/server/modules/files/file.entity';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const fileRoutes = createCrudRoutes({
+  entity: File,
+  createSchema: CreateFileDto,
+  updateSchema: UpdateFileDto,
+  getSchema: FileSchema,
+  listSchema: FileSchema,
+  searchFields: ['name', 'type', 'description'],
+  middleware: [authMiddleware]
+});
+
+export default fileRoutes;

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

@@ -8,6 +8,15 @@ import { Role } from "./modules/users/role.entity"
 import { Customer } from "./modules/customers/customer.entity"
 import { Opportunity } from "./modules/opportunities/opportunity.entity"
 import { FollowUp } from "./modules/follow-ups/follow-up.entity"
+// 新实体导入
+import { AreaData } from "./modules/areas/area-data.entity"
+import { Client } from "./modules/clients/client.entity"
+import { Expense } from "./modules/expenses/expense.entity"
+import { File } from "./modules/files/file.entity"
+import { Hetong } from "./modules/contracts/hetong.entity"
+import { HetongRenew } from "./modules/contracts/hetong-renew.entity"
+import { Linkman } from "./modules/contacts/linkman.entity"
+import { Logfile } from "./modules/logs/logfile.entity"
 
 export const AppDataSource = new DataSource({
   type: "mysql",
@@ -17,7 +26,8 @@ export const AppDataSource = new DataSource({
   password: process.env.DB_PASSWORD || "",
   database: process.env.DB_DATABASE || "d8dai",
   entities: [
-    User, Role, Customer, Opportunity, FollowUp
+    User, Role, Customer, Opportunity, FollowUp,
+    AreaData, Client, Expense, File, Hetong, HetongRenew, Linkman, Logfile
   ],
   migrations: [],
   synchronize: process.env.DB_SYNCHRONIZE !== "false",

+ 70 - 0
src/server/modules/areas/area-data.entity.ts

@@ -0,0 +1,70 @@
+import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm';
+import { z } from '@hono/zod-openapi';
+
+@Entity('area_data')
+export class AreaData {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ name: 'parent_id', type: 'int', unsigned: true, nullable: true })
+  parentId?: number;
+
+  @Column({ name: 'name', type: 'varchar', length: 100 })
+  name!: string;
+
+  @Column({ name: 'created_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP', 
+    onUpdate: 'CURRENT_TIMESTAMP' 
+  })
+  updatedAt!: Date;
+}
+
+export const AreaDataSchema = z.object({
+  id: z.number().int().positive().openapi({ 
+    description: '区域ID',
+    example: 1 
+  }),
+  parentId: z.number().int().positive().nullable().openapi({ 
+    description: '父区域ID,自引用',
+    example: 0 
+  }),
+  name: z.string().max(100).openapi({ 
+    description: '区域名称',
+    example: '北京市' 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-01T00:00:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2023-01-01T00:00:00Z' 
+  })
+});
+
+export const CreateAreaDataDto = z.object({
+  parentId: z.number().int().positive().nullable().openapi({ 
+    description: '父区域ID,自引用',
+    example: 0 
+  }),
+  name: z.string().max(100).openapi({ 
+    description: '区域名称',
+    example: '北京市' 
+  })
+});
+
+export const UpdateAreaDataDto = z.object({
+  parentId: z.number().int().positive().nullable().optional().openapi({ 
+    description: '父区域ID,自引用',
+    example: 0 
+  }),
+  name: z.string().max(100).optional().openapi({ 
+    description: '区域名称',
+    example: '北京市' 
+  })
+});

+ 9 - 0
src/server/modules/areas/area-data.service.ts

@@ -0,0 +1,9 @@
+import { GenericCrudService } from '@/server/utils/generic-crud.service';
+import { DataSource } from 'typeorm';
+import { AreaData } from './area-data.entity';
+
+export class AreaDataService extends GenericCrudService<AreaData> {
+  constructor(dataSource: DataSource) {
+    super(dataSource, AreaData);
+  }
+}

+ 535 - 0
src/server/modules/clients/client.entity.ts

@@ -0,0 +1,535 @@
+import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm';
+import { z } from '@hono/zod-openapi';
+
+@Entity('client')
+export class Client {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ name: 'company_name', type: 'varchar', length: 255 })
+  companyName!: string;
+
+  @Column({ name: 'area_id', type: 'int', unsigned: true, nullable: true })
+  areaId?: number;
+
+  @Column({ name: 'square', type: 'varchar', length: 50, nullable: true })
+  square?: string;
+
+  @Column({ name: 'address', type: 'varchar', length: 255, nullable: true })
+  address?: string;
+
+  @Column({ name: 'contact_person', type: 'varchar', length: 100, nullable: true })
+  contactPerson?: string;
+
+  @Column({ name: 'position', type: 'varchar', length: 100, nullable: true })
+  position?: string;
+
+  @Column({ name: 'mobile', type: 'varchar', length: 20, nullable: true })
+  mobile?: string;
+
+  @Column({ name: 'zip_code', type: 'varchar', length: 20, nullable: true })
+  zipCode?: string;
+
+  @Column({ name: 'telephone', type: 'varchar', length: 20, nullable: true })
+  telephone?: string;
+
+  @Column({ name: 'fax', type: 'varchar', length: 20, nullable: true })
+  fax?: string;
+
+  @Column({ name: 'homepage', type: 'varchar', length: 255, nullable: true })
+  homepage?: string;
+
+  @Column({ name: 'email', type: 'varchar', length: 100, nullable: true })
+  email?: string;
+
+  @Column({ name: 'industry', type: 'varchar', length: 100, nullable: true })
+  industry?: string;
+
+  @Column({ name: 'sub_industry', type: 'varchar', length: 100, nullable: true })
+  subIndustry?: string;
+
+  @Column({ name: 'customer_type', type: 'varchar', length: 50, nullable: true })
+  customerType?: string;
+
+  @Column({ name: 'start_date', type: 'date', nullable: true })
+  startDate?: Date;
+
+  @Column({ name: 'source', type: 'varchar', length: 100, nullable: true })
+  source?: string;
+
+  @Column({ name: 'description', type: 'text', nullable: true })
+  description?: string;
+
+  @Column({ name: 'responsible_user_id', type: 'int', unsigned: true, nullable: true })
+  responsibleUserId?: number;
+
+  @Column({ name: 'group_id', type: 'int', unsigned: true, nullable: true })
+  groupId?: number;
+
+  @Column({ name: 'remarks', type: 'text', nullable: true })
+  remarks?: string;
+
+  @Column({ name: 'old_user_id', type: 'int', unsigned: true, nullable: true })
+  oldUserId?: number;
+
+  @Column({ name: 'last_updated', type: 'timestamp', nullable: true })
+  lastUpdated?: Date;
+
+  @Column({ name: 'share_status', type: 'tinyint', default: 0 })
+  shareStatus!: number;
+
+  @Column({ name: 'share_range', type: 'int', nullable: true })
+  shareRange?: number;
+
+  @Column({ name: 'service_level', type: 'int', nullable: true })
+  serviceLevel?: number;
+
+  @Column({ name: 'next_contact_time', type: 'datetime', nullable: true })
+  nextContactTime?: Date;
+
+  @Column({ name: 'oe_date', type: 'date', nullable: true })
+  oeDate?: Date;
+
+  @Column({ name: 'he_date', type: 'date', nullable: true })
+  heDate?: Date;
+
+  @Column({ name: 'zone_id', type: 'int', nullable: true })
+  zoneId?: number;
+
+  @Column({ name: 'is_deleted', type: 'tinyint', default: 0 })
+  isDeleted!: number;
+
+  @Column({ name: 'status', type: 'tinyint', default: 1 })
+  status!: number;
+
+  @Column({ name: 'audit_status', type: 'tinyint', default: 0 })
+  auditStatus!: number;
+
+  @Column({ name: 'created_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP', 
+    onUpdate: 'CURRENT_TIMESTAMP' 
+  })
+  updatedAt!: Date;
+}
+
+export const ClientSchema = z.object({
+  id: z.number().int().positive().openapi({ 
+    description: '客户ID',
+    example: 1 
+  }),
+  companyName: z.string().max(255).openapi({ 
+    description: '客户公司名称',
+    example: '北京科技有限公司' 
+  }),
+  areaId: z.number().int().positive().nullable().openapi({ 
+    description: '所在区域ID,关联area_data表',
+    example: 1 
+  }),
+  square: z.string().max(50).nullable().openapi({ 
+    description: '场地面积信息',
+    example: '1000㎡' 
+  }),
+  address: z.string().max(255).nullable().openapi({ 
+    description: '客户地址',
+    example: '北京市海淀区中关村大街1号' 
+  }),
+  contactPerson: z.string().max(100).nullable().openapi({ 
+    description: '联系人姓名',
+    example: '张三' 
+  }),
+  position: z.string().max(100).nullable().openapi({ 
+    description: '联系人职位',
+    example: '经理' 
+  }),
+  mobile: z.string().max(20).nullable().openapi({ 
+    description: '联系人手机号码',
+    example: '13800138000' 
+  }),
+  zipCode: z.string().max(20).nullable().openapi({ 
+    description: '邮政编码',
+    example: '100080' 
+  }),
+  telephone: z.string().max(20).nullable().openapi({ 
+    description: '联系电话',
+    example: '010-12345678' 
+  }),
+  fax: z.string().max(20).nullable().openapi({ 
+    description: '传真号码',
+    example: '010-12345679' 
+  }),
+  homepage: z.string().max(255).nullable().openapi({ 
+    description: '公司主页网址',
+    example: 'https://www.example.com' 
+  }),
+  email: z.string().max(100).nullable().openapi({ 
+    description: '电子邮箱',
+    example: 'contact@example.com' 
+  }),
+  industry: z.string().max(100).nullable().openapi({ 
+    description: '所属行业',
+    example: '信息技术' 
+  }),
+  subIndustry: z.string().max(100).nullable().openapi({ 
+    description: '所属子行业',
+    example: '软件开发' 
+  }),
+  customerType: z.string().max(50).nullable().openapi({ 
+    description: '客户类型',
+    example: '重要客户' 
+  }),
+  startDate: z.date().nullable().openapi({ 
+    description: '合作起始日期',
+    example: '2023-01-01' 
+  }),
+  source: z.string().max(100).nullable().openapi({ 
+    description: '客户来源',
+    example: '展会' 
+  }),
+  description: z.string().nullable().openapi({ 
+    description: '客户详细信息',
+    example: '专注于人工智能领域的科技公司' 
+  }),
+  responsibleUserId: z.number().int().positive().nullable().openapi({ 
+    description: '负责人用户ID',
+    example: 1 
+  }),
+  groupId: z.number().int().positive().nullable().openapi({ 
+    description: '客户所属群组ID',
+    example: 1 
+  }),
+  remarks: z.string().nullable().openapi({ 
+    description: '客户备注信息',
+    example: '需要定期跟进' 
+  }),
+  oldUserId: z.number().int().positive().nullable().openapi({ 
+    description: '旧负责人用户ID',
+    example: 2 
+  }),
+  lastUpdated: z.date().nullable().openapi({ 
+    description: '最后更新时间',
+    example: '2023-01-10T00:00:00Z' 
+  }),
+  shareStatus: z.number().int().min(0).max(1).openapi({ 
+    description: '分享状态:0-未分享,1-已分享',
+    example: 0 
+  }),
+  shareRange: z.number().int().nullable().openapi({ 
+    description: '分享范围',
+    example: 1 
+  }),
+  serviceLevel: z.number().int().nullable().openapi({ 
+    description: '服务等级',
+    example: 2 
+  }),
+  nextContactTime: z.date().nullable().openapi({ 
+    description: '下次联系时间',
+    example: '2023-02-01T10:00:00Z' 
+  }),
+  oeDate: z.date().nullable().openapi({ 
+    description: 'OE日期',
+    example: '2023-03-01' 
+  }),
+  heDate: z.date().nullable().openapi({ 
+    description: 'HE日期',
+    example: '2023-04-01' 
+  }),
+  zoneId: z.number().int().nullable().openapi({ 
+    description: '区域标识',
+    example: 1 
+  }),
+  isDeleted: z.number().int().min(0).max(1).openapi({ 
+    description: '删除状态:0-未删除,1-已删除',
+    example: 0 
+  }),
+  status: z.number().int().min(0).max(1).openapi({ 
+    description: '客户状态:0-无效,1-有效',
+    example: 1 
+  }),
+  auditStatus: z.number().int().min(0).max(2).openapi({ 
+    description: '审核状态:0-待审核,1-已审核,2-已拒绝',
+    example: 1 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-01T00:00:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2023-01-01T00:00:00Z' 
+  })
+});
+
+export const CreateClientDto = z.object({
+  companyName: z.string().max(255).openapi({ 
+    description: '客户公司名称',
+    example: '北京科技有限公司' 
+  }),
+  areaId: z.number().int().positive().nullable().openapi({ 
+    description: '所在区域ID,关联area_data表',
+    example: 1 
+  }),
+  square: z.string().max(50).nullable().optional().openapi({ 
+    description: '场地面积信息',
+    example: '1000㎡' 
+  }),
+  address: z.string().max(255).nullable().optional().openapi({ 
+    description: '客户地址',
+    example: '北京市海淀区中关村大街1号' 
+  }),
+  contactPerson: z.string().max(100).nullable().optional().openapi({ 
+    description: '联系人姓名',
+    example: '张三' 
+  }),
+  position: z.string().max(100).nullable().optional().openapi({ 
+    description: '联系人职位',
+    example: '经理' 
+  }),
+  mobile: z.string().max(20).nullable().optional().openapi({ 
+    description: '联系人手机号码',
+    example: '13800138000' 
+  }),
+  zipCode: z.string().max(20).nullable().optional().openapi({ 
+    description: '邮政编码',
+    example: '100080' 
+  }),
+  telephone: z.string().max(20).nullable().optional().openapi({ 
+    description: '联系电话',
+    example: '010-12345678' 
+  }),
+  fax: z.string().max(20).nullable().optional().openapi({ 
+    description: '传真号码',
+    example: '010-12345679' 
+  }),
+  homepage: z.string().max(255).nullable().optional().openapi({ 
+    description: '公司主页网址',
+    example: 'https://www.example.com' 
+  }),
+  email: z.string().max(100).nullable().optional().openapi({ 
+    description: '电子邮箱',
+    example: 'contact@example.com' 
+  }),
+  industry: z.string().max(100).nullable().optional().openapi({ 
+    description: '所属行业',
+    example: '信息技术' 
+  }),
+  subIndustry: z.string().max(100).nullable().optional().openapi({ 
+    description: '所属子行业',
+    example: '软件开发' 
+  }),
+  customerType: z.string().max(50).nullable().optional().openapi({ 
+    description: '客户类型',
+    example: '重要客户' 
+  }),
+  startDate: z.coerce.date().nullable().optional().openapi({ 
+    description: '合作起始日期',
+    example: '2023-01-01' 
+  }),
+  source: z.string().max(100).nullable().optional().openapi({ 
+    description: '客户来源',
+    example: '展会' 
+  }),
+  description: z.string().nullable().optional().openapi({ 
+    description: '客户详细信息',
+    example: '专注于人工智能领域的科技公司' 
+  }),
+  responsibleUserId: z.number().int().positive().nullable().optional().openapi({ 
+    description: '负责人用户ID',
+    example: 1 
+  }),
+  groupId: z.number().int().positive().nullable().optional().openapi({ 
+    description: '客户所属群组ID',
+    example: 1 
+  }),
+  remarks: z.string().nullable().optional().openapi({ 
+    description: '客户备注信息',
+    example: '需要定期跟进' 
+  }),
+  oldUserId: z.number().int().positive().nullable().optional().openapi({ 
+    description: '旧负责人用户ID',
+    example: 2 
+  }),
+  lastUpdated: z.coerce.date().nullable().optional().openapi({ 
+    description: '最后更新时间',
+    example: '2023-01-10T00:00:00Z' 
+  }),
+  shareStatus: z.number().int().min(0).max(1).default(0).openapi({ 
+    description: '分享状态:0-未分享,1-已分享',
+    example: 0 
+  }),
+  shareRange: z.number().int().nullable().optional().openapi({ 
+    description: '分享范围',
+    example: 1 
+  }),
+  serviceLevel: z.number().int().nullable().optional().openapi({ 
+    description: '服务等级',
+    example: 2 
+  }),
+  nextContactTime: z.coerce.date().nullable().optional().openapi({ 
+    description: '下次联系时间',
+    example: '2023-02-01T10:00:00Z' 
+  }),
+  oeDate: z.coerce.date().nullable().optional().openapi({ 
+    description: 'OE日期',
+    example: '2023-03-01' 
+  }),
+  heDate: z.coerce.date().nullable().optional().openapi({ 
+    description: 'HE日期',
+    example: '2023-04-01' 
+  }),
+  zoneId: z.number().int().nullable().optional().openapi({ 
+    description: '区域标识',
+    example: 1 
+  }),
+  isDeleted: z.number().int().min(0).max(1).default(0).openapi({ 
+    description: '删除状态:0-未删除,1-已删除',
+    example: 0 
+  }),
+  status: z.number().int().min(0).max(1).default(1).openapi({ 
+    description: '客户状态:0-无效,1-有效',
+    example: 1 
+  }),
+  auditStatus: z.number().int().min(0).max(2).default(0).openapi({ 
+    description: '审核状态:0-待审核,1-已审核,2-已拒绝',
+    example: 0 
+  })
+});
+
+export const UpdateClientDto = z.object({
+  companyName: z.string().max(255).optional().openapi({ 
+    description: '客户公司名称',
+    example: '北京科技有限公司' 
+  }),
+  areaId: z.number().int().positive().nullable().optional().openapi({ 
+    description: '所在区域ID,关联area_data表',
+    example: 1 
+  }),
+  square: z.string().max(50).nullable().optional().openapi({ 
+    description: '场地面积信息',
+    example: '1000㎡' 
+  }),
+  address: z.string().max(255).nullable().optional().openapi({ 
+    description: '客户地址',
+    example: '北京市海淀区中关村大街1号' 
+  }),
+  contactPerson: z.string().max(100).nullable().optional().openapi({ 
+    description: '联系人姓名',
+    example: '张三' 
+  }),
+  position: z.string().max(100).nullable().optional().openapi({ 
+    description: '联系人职位',
+    example: '经理' 
+  }),
+  mobile: z.string().max(20).nullable().optional().openapi({ 
+    description: '联系人手机号码',
+    example: '13800138000' 
+  }),
+  zipCode: z.string().max(20).nullable().optional().openapi({ 
+    description: '邮政编码',
+    example: '100080' 
+  }),
+  telephone: z.string().max(20).nullable().optional().openapi({ 
+    description: '联系电话',
+    example: '010-12345678' 
+  }),
+  fax: z.string().max(20).nullable().optional().openapi({ 
+    description: '传真号码',
+    example: '010-12345679' 
+  }),
+  homepage: z.string().max(255).nullable().optional().openapi({ 
+    description: '公司主页网址',
+    example: 'https://www.example.com' 
+  }),
+  email: z.string().max(100).nullable().optional().openapi({ 
+    description: '电子邮箱',
+    example: 'contact@example.com' 
+  }),
+  industry: z.string().max(100).nullable().optional().openapi({ 
+    description: '所属行业',
+    example: '信息技术' 
+  }),
+  subIndustry: z.string().max(100).nullable().optional().openapi({ 
+    description: '所属子行业',
+    example: '软件开发' 
+  }),
+  customerType: z.string().max(50).nullable().optional().openapi({ 
+    description: '客户类型',
+    example: '重要客户' 
+  }),
+  startDate: z.coerce.date().nullable().optional().openapi({ 
+    description: '合作起始日期',
+    example: '2023-01-01' 
+  }),
+  source: z.string().max(100).nullable().optional().openapi({ 
+    description: '客户来源',
+    example: '展会' 
+  }),
+  description: z.string().nullable().optional().openapi({ 
+    description: '客户详细信息',
+    example: '专注于人工智能领域的科技公司' 
+  }),
+  responsibleUserId: z.number().int().positive().nullable().optional().openapi({ 
+    description: '负责人用户ID',
+    example: 1 
+  }),
+  groupId: z.number().int().positive().nullable().optional().openapi({ 
+    description: '客户所属群组ID',
+    example: 1 
+  }),
+  remarks: z.string().nullable().optional().openapi({ 
+    description: '客户备注信息',
+    example: '需要定期跟进' 
+  }),
+  oldUserId: z.number().int().positive().nullable().optional().openapi({ 
+    description: '旧负责人用户ID',
+    example: 2 
+  }),
+  lastUpdated: z.coerce.date().nullable().optional().openapi({ 
+    description: '最后更新时间',
+    example: '2023-01-10T00:00:00Z' 
+  }),
+  shareStatus: z.number().int().min(0).max(1).optional().openapi({ 
+    description: '分享状态:0-未分享,1-已分享',
+    example: 0 
+  }),
+  shareRange: z.number().int().nullable().optional().openapi({ 
+    description: '分享范围',
+    example: 1 
+  }),
+  serviceLevel: z.number().int().nullable().optional().openapi({ 
+    description: '服务等级',
+    example: 2 
+  }),
+  nextContactTime: z.coerce.date().nullable().optional().openapi({ 
+    description: '下次联系时间',
+    example: '2023-02-01T10:00:00Z' 
+  }),
+  oeDate: z.coerce.date().nullable().optional().openapi({ 
+    description: 'OE日期',
+    example: '2023-03-01' 
+  }),
+  heDate: z.coerce.date().nullable().optional().openapi({ 
+    description: 'HE日期',
+    example: '2023-04-01' 
+  }),
+  zoneId: z.number().int().nullable().optional().openapi({ 
+    description: '区域标识',
+    example: 1 
+  }),
+  isDeleted: z.number().int().min(0).max(1).optional().openapi({ 
+    description: '删除状态:0-未删除,1-已删除',
+    example: 0 
+  }),
+  status: z.number().int().min(0).max(1).optional().openapi({ 
+    description: '客户状态:0-无效,1-有效',
+    example: 1 
+  }),
+  auditStatus: z.number().int().min(0).max(2).optional().openapi({ 
+    description: '审核状态:0-待审核,1-已审核,2-已拒绝',
+    example: 0 
+  })
+});

+ 9 - 0
src/server/modules/clients/client.service.ts

@@ -0,0 +1,9 @@
+import { GenericCrudService } from '@/server/utils/generic-crud.service';
+import { DataSource } from 'typeorm';
+import { Client } from './client.entity';
+
+export class ClientService extends GenericCrudService<Client> {
+  constructor(dataSource: DataSource) {
+    super(dataSource, Client);
+  }
+}

+ 256 - 0
src/server/modules/contacts/linkman.entity.ts

@@ -0,0 +1,256 @@
+import { Entity, PrimaryColumn, Column, Index, ForeignKey } from 'typeorm';
+import { z } from '@hono/zod-openapi';
+import { Client } from '../clients/client.entity';
+
+@Entity('linkmans')
+export class Linkman {
+  @PrimaryColumn({ name: 'id', type: 'varchar', length: 50 })
+  id!: string;
+
+  @Column({ name: 'client_id', type: 'varchar', length: 50 })
+  @ForeignKey(() => Client)
+  clientId!: string;
+
+  @Column({ name: 'name', type: 'varchar', length: 100 })
+  name!: string;
+
+  @Column({ name: 'gender', type: 'varchar', length: 10, nullable: true })
+  gender?: string;
+
+  @Column({ name: 'position', type: 'varchar', length: 100, nullable: true })
+  position?: string;
+
+  @Column({ name: 'mobile', type: 'varchar', length: 20, nullable: true })
+  mobile?: string;
+
+  @Column({ name: 'qq', type: 'varchar', length: 20, nullable: true })
+  qq?: string;
+
+  @Column({ name: 'telephone', type: 'varchar', length: 20, nullable: true })
+  telephone?: string;
+
+  @Column({ name: 'email', type: 'varchar', length: 100, nullable: true })
+  email?: string;
+
+  @Column({ name: 'msn', type: 'varchar', length: 100, nullable: true })
+  msn?: string;
+
+  @Column({ name: 'alww', type: 'varchar', length: 100, nullable: true })
+  alww?: string;
+
+  @Column({ name: 'birthday', type: 'date', nullable: true })
+  birthday?: Date;
+
+  @Column({ name: 'description', type: 'text', nullable: true })
+  description?: string;
+
+  @Column({ name: 'created_user_id', type: 'varchar', length: 50, nullable: true })
+  createdUserId?: string;
+
+  @Column({ name: 'created_time', type: 'datetime' })
+  createdTime!: Date;
+
+  @Column({ name: 'created_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP', 
+    onUpdate: 'CURRENT_TIMESTAMP' 
+  })
+  updatedAt!: Date;
+}
+
+export const LinkmanSchema = z.object({
+  id: z.string().max(50).openapi({ 
+    description: '联系人ID',
+    example: 'LM20230001' 
+  }),
+  clientId: z.string().max(50).openapi({ 
+    description: '客户ID',
+    example: 'C2001' 
+  }),
+  name: z.string().max(100).openapi({ 
+    description: '姓名',
+    example: '张三' 
+  }),
+  gender: z.string().max(10).nullable().openapi({ 
+    description: '性别',
+    example: '男' 
+  }),
+  position: z.string().max(100).nullable().openapi({ 
+    description: '职位',
+    example: '技术总监' 
+  }),
+  mobile: z.string().max(20).nullable().openapi({ 
+    description: '手机号码',
+    example: '13800138000' 
+  }),
+  qq: z.string().max(20).nullable().openapi({ 
+    description: 'QQ号码',
+    example: '123456789' 
+  }),
+  telephone: z.string().max(20).nullable().openapi({ 
+    description: '联系电话',
+    example: '010-12345678' 
+  }),
+  email: z.string().max(100).nullable().openapi({ 
+    description: '电子邮箱',
+    example: 'zhang.san@example.com' 
+  }),
+  msn: z.string().max(100).nullable().openapi({ 
+    description: 'MSN账号',
+    example: 'zhang.san@msn.com' 
+  }),
+  alww: z.string().max(100).nullable().openapi({ 
+    description: '其他网络账号',
+    example: 'zhangsan_wechat' 
+  }),
+  birthday: z.date().nullable().openapi({ 
+    description: '出生日期',
+    example: '1980-01-15' 
+  }),
+  description: z.string().nullable().openapi({ 
+    description: '详细信息',
+    example: '负责技术选型与合作洽谈' 
+  }),
+  createdUserId: z.string().max(50).nullable().openapi({ 
+    description: '创建用户ID',
+    example: 'U1001' 
+  }),
+  createdTime: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-15T10:30:00Z' 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-15T10:30:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2023-01-15T10:30:00Z' 
+  })
+});
+
+export const CreateLinkmanDto = z.object({
+  id: z.string().max(50).openapi({ 
+    description: '联系人ID',
+    example: 'LM20230001' 
+  }),
+  clientId: z.string().max(50).openapi({ 
+    description: '客户ID',
+    example: 'C2001' 
+  }),
+  name: z.string().max(100).openapi({ 
+    description: '姓名',
+    example: '张三' 
+  }),
+  gender: z.string().max(10).nullable().optional().openapi({ 
+    description: '性别',
+    example: '男' 
+  }),
+  position: z.string().max(100).nullable().optional().openapi({ 
+    description: '职位',
+    example: '技术总监' 
+  }),
+  mobile: z.string().max(20).nullable().optional().openapi({ 
+    description: '手机号码',
+    example: '13800138000' 
+  }),
+  qq: z.string().max(20).nullable().optional().openapi({ 
+    description: 'QQ号码',
+    example: '123456789' 
+  }),
+  telephone: z.string().max(20).nullable().optional().openapi({ 
+    description: '联系电话',
+    example: '010-12345678' 
+  }),
+  email: z.string().max(100).nullable().optional().openapi({ 
+    description: '电子邮箱',
+    example: 'zhang.san@example.com' 
+  }),
+  msn: z.string().max(100).nullable().optional().openapi({ 
+    description: 'MSN账号',
+    example: 'zhang.san@msn.com' 
+  }),
+  alww: z.string().max(100).nullable().optional().openapi({ 
+    description: '其他网络账号',
+    example: 'zhangsan_wechat' 
+  }),
+  birthday: z.coerce.date().nullable().optional().openapi({ 
+    description: '出生日期',
+    example: '1980-01-15' 
+  }),
+  description: z.string().nullable().optional().openapi({ 
+    description: '详细信息',
+    example: '负责技术选型与合作洽谈' 
+  }),
+  createdUserId: z.string().max(50).nullable().optional().openapi({ 
+    description: '创建用户ID',
+    example: 'U1001' 
+  }),
+  createdTime: z.coerce.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-15T10:30:00Z' 
+  })
+});
+
+export const UpdateLinkmanDto = z.object({
+  clientId: z.string().max(50).optional().openapi({ 
+    description: '客户ID',
+    example: 'C2001' 
+  }),
+  name: z.string().max(100).optional().openapi({ 
+    description: '姓名',
+    example: '张三' 
+  }),
+  gender: z.string().max(10).nullable().optional().openapi({ 
+    description: '性别',
+    example: '男' 
+  }),
+  position: z.string().max(100).nullable().optional().openapi({ 
+    description: '职位',
+    example: '技术总监' 
+  }),
+  mobile: z.string().max(20).nullable().optional().openapi({ 
+    description: '手机号码',
+    example: '13800138000' 
+  }),
+  qq: z.string().max(20).nullable().optional().openapi({ 
+    description: 'QQ号码',
+    example: '123456789' 
+  }),
+  telephone: z.string().max(20).nullable().optional().openapi({ 
+    description: '联系电话',
+    example: '010-12345678' 
+  }),
+  email: z.string().max(100).nullable().optional().openapi({ 
+    description: '电子邮箱',
+    example: 'zhang.san@example.com' 
+  }),
+  msn: z.string().max(100).nullable().optional().openapi({ 
+    description: 'MSN账号',
+    example: 'zhang.san@msn.com' 
+  }),
+  alww: z.string().max(100).nullable().optional().openapi({ 
+    description: '其他网络账号',
+    example: 'zhangsan_wechat' 
+  }),
+  birthday: z.coerce.date().nullable().optional().openapi({ 
+    description: '出生日期',
+    example: '1980-01-15' 
+  }),
+  description: z.string().nullable().optional().openapi({ 
+    description: '详细信息',
+    example: '负责技术选型与合作洽谈' 
+  }),
+  createdUserId: z.string().max(50).nullable().optional().openapi({ 
+    description: '创建用户ID',
+    example: 'U1001' 
+  }),
+  createdTime: z.coerce.date().optional().openapi({ 
+    description: '创建时间',
+    example: '2023-01-15T10:30:00Z' 
+  })
+});

+ 9 - 0
src/server/modules/contacts/linkman.service.ts

@@ -0,0 +1,9 @@
+import { GenericCrudService } from '@/server/utils/generic-crud.service';
+import { DataSource } from 'typeorm';
+import { Linkman } from './linkman.entity';
+
+export class LinkmanService extends GenericCrudService<Linkman> {
+  constructor(dataSource: DataSource) {
+    super(dataSource, Linkman);
+  }
+}

+ 211 - 0
src/server/modules/contracts/hetong-renew.entity.ts

@@ -0,0 +1,211 @@
+import { Entity, PrimaryColumn, Column, Index, ForeignKey } from 'typeorm';
+import { z } from '@hono/zod-openapi';
+import { Hetong } from './hetong.entity';
+
+@Entity('hetong_renew')
+export class HetongRenew {
+  @PrimaryColumn({ name: 'id', type: 'varchar', length: 50 })
+  id!: string;
+
+  @Column({ name: 'contract_id', type: 'varchar', length: 50 })
+  @ForeignKey(() => Hetong)
+  contractId!: string;
+
+  @Column({ name: 'amount', type: 'varchar', length: 50, nullable: true })
+  amount?: string;
+
+  @Column({ name: 'revenue', type: 'varchar', length: 50, nullable: true })
+  revenue?: string;
+
+  @Column({ name: 'end_date', type: 'date', nullable: true })
+  endDate?: Date;
+
+  @Column({ name: 'state', type: 'varchar', length: 50, nullable: true })
+  state?: string;
+
+  @Column({ name: 'content', type: 'text', nullable: true })
+  content?: string;
+
+  @Column({ name: 'audit_status', type: 'varchar', length: 50, nullable: true })
+  auditStatus?: string;
+
+  @Column({ name: 'audit_time', type: 'datetime', nullable: true })
+  auditTime?: Date;
+
+  @Column({ name: 'audit_reasons', type: 'text', nullable: true })
+  auditReasons?: string;
+
+  @Column({ name: 'user_id', type: 'varchar', length: 50, nullable: true })
+  userId?: string;
+
+  @Column({ name: 'created_time', type: 'datetime' })
+  createdTime!: Date;
+
+  @Column({ name: 'created_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP', 
+    onUpdate: 'CURRENT_TIMESTAMP' 
+  })
+  updatedAt!: Date;
+}
+
+export const HetongRenewSchema = z.object({
+  id: z.string().max(50).openapi({ 
+    description: '续签记录ID',
+    example: 'HTR20230001' 
+  }),
+  contractId: z.string().max(50).openapi({ 
+    description: '原合同ID',
+    example: 'HT20230001' 
+  }),
+  amount: z.string().max(50).nullable().openapi({ 
+    description: '续签金额',
+    example: '160000.00' 
+  }),
+  revenue: z.string().max(50).nullable().openapi({ 
+    description: '续签收入',
+    example: '160000.00' 
+  }),
+  endDate: z.date().nullable().openapi({ 
+    description: '续签结束日期',
+    example: '2025-01-31' 
+  }),
+  state: z.string().max(50).nullable().openapi({ 
+    description: '续签状态:如待审批、已生效等',
+    example: '已生效' 
+  }),
+  content: z.string().nullable().openapi({ 
+    description: '续签内容描述',
+    example: '合同自动续签一年' 
+  }),
+  auditStatus: z.string().max(50).nullable().openapi({ 
+    description: '审批情况',
+    example: 'approved' 
+  }),
+  auditTime: z.date().nullable().openapi({ 
+    description: '审批时间',
+    example: '2024-01-15T10:30:00Z' 
+  }),
+  auditReasons: z.string().nullable().openapi({ 
+    description: '审批原因',
+    example: '符合续签条件' 
+  }),
+  userId: z.string().max(50).nullable().openapi({ 
+    description: '处理用户ID',
+    example: 'U1001' 
+  }),
+  createdTime: z.date().openapi({ 
+    description: '记录创建时间',
+    example: '2024-01-10T09:00:00Z' 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2024-01-10T09:00:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2024-01-15T10:30:00Z' 
+  })
+});
+
+export const CreateHetongRenewDto = z.object({
+  id: z.string().max(50).openapi({ 
+    description: '续签记录ID',
+    example: 'HTR20230001' 
+  }),
+  contractId: z.string().max(50).openapi({ 
+    description: '原合同ID',
+    example: 'HT20230001' 
+  }),
+  amount: z.string().max(50).nullable().optional().openapi({ 
+    description: '续签金额',
+    example: '160000.00' 
+  }),
+  revenue: z.string().max(50).nullable().optional().openapi({ 
+    description: '续签收入',
+    example: '160000.00' 
+  }),
+  endDate: z.coerce.date().nullable().optional().openapi({ 
+    description: '续签结束日期',
+    example: '2025-01-31' 
+  }),
+  state: z.string().max(50).nullable().optional().openapi({ 
+    description: '续签状态:如待审批、已生效等',
+    example: '待审批' 
+  }),
+  content: z.string().nullable().optional().openapi({ 
+    description: '续签内容描述',
+    example: '合同自动续签一年' 
+  }),
+  auditStatus: z.string().max(50).nullable().optional().openapi({ 
+    description: '审批情况',
+    example: 'pending' 
+  }),
+  auditTime: z.coerce.date().nullable().optional().openapi({ 
+    description: '审批时间',
+    example: '2024-01-15T10:30:00Z' 
+  }),
+  auditReasons: z.string().nullable().optional().openapi({ 
+    description: '审批原因',
+    example: '' 
+  }),
+  userId: z.string().max(50).nullable().optional().openapi({ 
+    description: '处理用户ID',
+    example: 'U1001' 
+  }),
+  createdTime: z.coerce.date().openapi({ 
+    description: '记录创建时间',
+    example: '2024-01-10T09:00:00Z' 
+  })
+});
+
+export const UpdateHetongRenewDto = z.object({
+  contractId: z.string().max(50).optional().openapi({ 
+    description: '原合同ID',
+    example: 'HT20230001' 
+  }),
+  amount: z.string().max(50).nullable().optional().openapi({ 
+    description: '续签金额',
+    example: '160000.00' 
+  }),
+  revenue: z.string().max(50).nullable().optional().openapi({ 
+    description: '续签收入',
+    example: '160000.00' 
+  }),
+  endDate: z.coerce.date().nullable().optional().openapi({ 
+    description: '续签结束日期',
+    example: '2025-01-31' 
+  }),
+  state: z.string().max(50).nullable().optional().openapi({ 
+    description: '续签状态:如待审批、已生效等',
+    example: '已生效' 
+  }),
+  content: z.string().nullable().optional().openapi({ 
+    description: '续签内容描述',
+    example: '合同自动续签一年' 
+  }),
+  auditStatus: z.string().max(50).nullable().optional().openapi({ 
+    description: '审批情况',
+    example: 'approved' 
+  }),
+  auditTime: z.coerce.date().nullable().optional().openapi({ 
+    description: '审批时间',
+    example: '2024-01-15T10:30:00Z' 
+  }),
+  auditReasons: z.string().nullable().optional().openapi({ 
+    description: '审批原因',
+    example: '符合续签条件' 
+  }),
+  userId: z.string().max(50).nullable().optional().openapi({ 
+    description: '处理用户ID',
+    example: 'U1001' 
+  }),
+  createdTime: z.coerce.date().optional().openapi({ 
+    description: '记录创建时间',
+    example: '2024-01-10T09:00:00Z' 
+  })
+});

+ 9 - 0
src/server/modules/contracts/hetong-renew.service.ts

@@ -0,0 +1,9 @@
+import { GenericCrudService } from '@/server/utils/generic-crud.service';
+import { DataSource } from 'typeorm';
+import { HetongRenew } from './hetong-renew.entity';
+
+export class HetongRenewService extends GenericCrudService<HetongRenew> {
+  constructor(dataSource: DataSource) {
+    super(dataSource, HetongRenew);
+  }
+}

+ 269 - 0
src/server/modules/contracts/hetong.entity.ts

@@ -0,0 +1,269 @@
+import { Entity, PrimaryColumn, Column, Index } from 'typeorm';
+import { z } from '@hono/zod-openapi';
+
+@Entity('hetong')
+export class Hetong {
+  @PrimaryColumn({ name: 'id', type: 'varchar', length: 50 })
+  id!: string;
+
+  @Column({ name: 'contract_date', type: 'date' })
+  contractDate!: Date;
+
+  @Column({ name: 'user_id', type: 'varchar', length: 50 })
+  userId!: string;
+
+  @Column({ name: 'client_id', type: 'varchar', length: 50 })
+  clientId!: string;
+
+  @Column({ name: 'project_id', type: 'varchar', length: 50, nullable: true })
+  projectId?: string;
+
+  @Column({ name: 'amount', type: 'decimal', precision: 15, scale: 2 })
+  amount!: number;
+
+  @Column({ name: 'type', type: 'varchar', length: 50 })
+  type!: string;
+
+  @Column({ name: 'status', type: 'varchar', length: 50 })
+  status!: string;
+
+  @Column({ name: 'start_date', type: 'date' })
+  startDate!: Date;
+
+  @Column({ name: 'end_date', type: 'date' })
+  endDate!: Date;
+
+  @Column({ name: 'description', type: 'text', nullable: true })
+  description?: string;
+
+  @Column({ name: 'contract_number', type: 'varchar', length: 100 })
+  contractNumber!: string;
+
+  @Column({ name: 'currency', type: 'varchar', length: 20, default: 'CNY' })
+  currency!: string;
+
+  @Column({ name: 'exchange_rate', type: 'decimal', precision: 10, scale: 4, default: 1 })
+  exchangeRate!: number;
+
+  @Column({ name: 'foreign_amount', type: 'decimal', precision: 15, scale: 2, nullable: true })
+  foreignAmount?: number;
+
+  @Column({ name: 'file_path', type: 'varchar', length: 512, nullable: true })
+  filePath?: string;
+
+  @Column({ name: 'created_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP', 
+    onUpdate: 'CURRENT_TIMESTAMP' 
+  })
+  updatedAt!: Date;
+}
+
+export const HetongSchema = z.object({
+  id: z.string().max(50).openapi({ 
+    description: '合同ID',
+    example: 'HT20230001' 
+  }),
+  contractDate: z.date().openapi({ 
+    description: '合同日期',
+    example: '2023-01-15' 
+  }),
+  userId: z.string().max(50).openapi({ 
+    description: '关联用户ID',
+    example: 'U1001' 
+  }),
+  clientId: z.string().max(50).openapi({ 
+    description: '客户ID',
+    example: 'C2001' 
+  }),
+  projectId: z.string().max(50).nullable().openapi({ 
+    description: '项目ID',
+    example: 'P3001' 
+  }),
+  amount: z.number().multipleOf(0.01).openapi({ 
+    description: '合同金额',
+    example: 150000.00 
+  }),
+  type: z.string().max(50).openapi({ 
+    description: '合同类型',
+    example: '服务合同' 
+  }),
+  status: z.string().max(50).openapi({ 
+    description: '合同状态:如生效中、已结束等',
+    example: '生效中' 
+  }),
+  startDate: z.date().openapi({ 
+    description: '开始日期',
+    example: '2023-02-01' 
+  }),
+  endDate: z.date().openapi({ 
+    description: '结束日期',
+    example: '2024-01-31' 
+  }),
+  description: z.string().nullable().openapi({ 
+    description: '合同描述',
+    example: '年度技术服务合同' 
+  }),
+  contractNumber: z.string().max(100).openapi({ 
+    description: '合同编号',
+    example: 'HT-2023-0001' 
+  }),
+  currency: z.string().max(20).openapi({ 
+    description: '货币类型',
+    example: 'CNY' 
+  }),
+  exchangeRate: z.number().multipleOf(0.0001).openapi({ 
+    description: '汇率',
+    example: 1.0000 
+  }),
+  foreignAmount: z.number().multipleOf(0.01).nullable().openapi({ 
+    description: '外币金额',
+    example: null 
+  }),
+  filePath: z.string().max(512).nullable().openapi({ 
+    description: '合同文件路径',
+    example: '/uploads/contracts/2023/HT-2023-0001.pdf' 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-15T00:00:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2023-01-15T00:00:00Z' 
+  })
+});
+
+export const CreateHetongDto = z.object({
+  id: z.string().max(50).openapi({ 
+    description: '合同ID',
+    example: 'HT20230001' 
+  }),
+  contractDate: z.coerce.date().openapi({ 
+    description: '合同日期',
+    example: '2023-01-15' 
+  }),
+  userId: z.string().max(50).openapi({ 
+    description: '关联用户ID',
+    example: 'U1001' 
+  }),
+  clientId: z.string().max(50).openapi({ 
+    description: '客户ID',
+    example: 'C2001' 
+  }),
+  projectId: z.string().max(50).nullable().optional().openapi({ 
+    description: '项目ID',
+    example: 'P3001' 
+  }),
+  amount: z.coerce.number().multipleOf(0.01).openapi({ 
+    description: '合同金额',
+    example: 150000.00 
+  }),
+  type: z.string().max(50).openapi({ 
+    description: '合同类型',
+    example: '服务合同' 
+  }),
+  status: z.string().max(50).openapi({ 
+    description: '合同状态:如生效中、已结束等',
+    example: '生效中' 
+  }),
+  startDate: z.coerce.date().openapi({ 
+    description: '开始日期',
+    example: '2023-02-01' 
+  }),
+  endDate: z.coerce.date().openapi({ 
+    description: '结束日期',
+    example: '2024-01-31' 
+  }),
+  description: z.string().nullable().optional().openapi({ 
+    description: '合同描述',
+    example: '年度技术服务合同' 
+  }),
+  contractNumber: z.string().max(100).openapi({ 
+    description: '合同编号',
+    example: 'HT-2023-0001' 
+  }),
+  currency: z.string().max(20).default('CNY').openapi({ 
+    description: '货币类型',
+    example: 'CNY' 
+  }),
+  exchangeRate: z.coerce.number().multipleOf(0.0001).default(1).openapi({ 
+    description: '汇率',
+    example: 1.0000 
+  }),
+  foreignAmount: z.coerce.number().multipleOf(0.01).nullable().optional().openapi({ 
+    description: '外币金额',
+    example: null 
+  }),
+  filePath: z.string().max(512).nullable().optional().openapi({ 
+    description: '合同文件路径',
+    example: '/uploads/contracts/2023/HT-2023-0001.pdf' 
+  })
+});
+
+export const UpdateHetongDto = z.object({
+  contractDate: z.coerce.date().optional().openapi({ 
+    description: '合同日期',
+    example: '2023-01-15' 
+  }),
+  userId: z.string().max(50).optional().openapi({ 
+    description: '关联用户ID',
+    example: 'U1001' 
+  }),
+  clientId: z.string().max(50).optional().openapi({ 
+    description: '客户ID',
+    example: 'C2001' 
+  }),
+  projectId: z.string().max(50).nullable().optional().openapi({ 
+    description: '项目ID',
+    example: 'P3001' 
+  }),
+  amount: z.coerce.number().multipleOf(0.01).optional().openapi({ 
+    description: '合同金额',
+    example: 150000.00 
+  }),
+  type: z.string().max(50).optional().openapi({ 
+    description: '合同类型',
+    example: '服务合同' 
+  }),
+  status: z.string().max(50).optional().openapi({ 
+    description: '合同状态:如生效中、已结束等',
+    example: '生效中' 
+  }),
+  startDate: z.coerce.date().optional().openapi({ 
+    description: '开始日期',
+    example: '2023-02-01' 
+  }),
+  endDate: z.coerce.date().optional().openapi({ 
+    description: '结束日期',
+    example: '2024-01-31' 
+  }),
+  description: z.string().nullable().optional().openapi({ 
+    description: '合同描述',
+    example: '年度技术服务合同' 
+  }),
+  contractNumber: z.string().max(100).optional().openapi({ 
+    description: '合同编号',
+    example: 'HT-2023-0001' 
+  }),
+  currency: z.string().max(20).optional().openapi({ 
+    description: '货币类型',
+    example: 'CNY' 
+  }),
+  exchangeRate: z.coerce.number().multipleOf(0.0001).optional().openapi({ 
+    description: '汇率',
+    example: 1.0000 
+  }),
+  foreignAmount: z.coerce.number().multipleOf(0.01).nullable().optional().openapi({ 
+    description: '外币金额',
+    example: null 
+  }),
+  filePath: z.string().max(512).nullable().optional().openapi({ 
+    description: '合同文件路径',
+    example: '/uploads/contracts/2023/HT-2023-0001.pdf' 
+  })
+});

+ 9 - 0
src/server/modules/contracts/hetong.service.ts

@@ -0,0 +1,9 @@
+import { GenericCrudService } from '@/server/utils/generic-crud.service';
+import { DataSource } from 'typeorm';
+import { Hetong } from './hetong.entity';
+
+export class HetongService extends GenericCrudService<Hetong> {
+  constructor(dataSource: DataSource) {
+    super(dataSource, Hetong);
+  }
+}

+ 299 - 0
src/server/modules/expenses/expense.entity.ts

@@ -0,0 +1,299 @@
+import { Entity, PrimaryColumn, Column, Index } from 'typeorm';
+import { z } from '@hono/zod-openapi';
+
+@Entity('expense')
+export class Expense {
+  @PrimaryColumn({ name: 'id', type: 'varchar', length: 50 })
+  id!: string;
+
+  @Column({ name: 'expense_date', type: 'date' })
+  expenseDate!: Date;
+
+  @Column({ name: 'user_id', type: 'varchar', length: 50 })
+  userId!: string;
+
+  @Column({ name: 'type', type: 'varchar', length: 50 })
+  type!: string;
+
+  @Column({ name: 'amount', type: 'decimal', precision: 10, scale: 2 })
+  amount!: number;
+
+  @Column({ name: 'client_id', type: 'varchar', length: 50, nullable: true })
+  clientId?: string;
+
+  @Column({ name: 'project_id', type: 'varchar', length: 50, nullable: true })
+  projectId?: string;
+
+  @Column({ name: 'department', type: 'varchar', length: 100, nullable: true })
+  department?: string;
+
+  @Column({ name: 'description', type: 'text', nullable: true })
+  description?: string;
+
+  @Column({ name: 'status', type: 'varchar', length: 50 })
+  status!: string;
+
+  @Column({ name: 'approver', type: 'varchar', length: 50, nullable: true })
+  approver?: string;
+
+  @Column({ name: 'approve_date', type: 'date', nullable: true })
+  approveDate?: Date;
+
+  @Column({ name: 'reimbursement_date', type: 'date', nullable: true })
+  reimbursementDate?: Date;
+
+  @Column({ name: 'payment_method', type: 'varchar', length: 50, nullable: true })
+  paymentMethod?: string;
+
+  @Column({ name: 'invoice_number', type: 'varchar', length: 100, nullable: true })
+  invoiceNumber?: string;
+
+  @Column({ name: 'currency', type: 'varchar', length: 20, default: 'CNY' })
+  currency!: string;
+
+  @Column({ name: 'exchange_rate', type: 'decimal', precision: 10, scale: 4, default: 1 })
+  exchangeRate!: number;
+
+  @Column({ name: 'foreign_amount', type: 'decimal', precision: 10, scale: 2, nullable: true })
+  foreignAmount?: number;
+
+  @Column({ name: 'created_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP', 
+    onUpdate: 'CURRENT_TIMESTAMP' 
+  })
+  updatedAt!: Date;
+}
+
+export const ExpenseSchema = z.object({
+  id: z.string().max(50).openapi({ 
+    description: '费用记录ID',
+    example: 'EXP20230001' 
+  }),
+  expenseDate: z.date().openapi({ 
+    description: '费用发生日期',
+    example: '2023-01-15' 
+  }),
+  userId: z.string().max(50).openapi({ 
+    description: '产生费用的用户ID',
+    example: 'U1001' 
+  }),
+  type: z.string().max(50).openapi({ 
+    description: '费用类型',
+    example: '差旅费' 
+  }),
+  amount: z.number().multipleOf(0.01).openapi({ 
+    description: '费用金额',
+    example: 1500.50 
+  }),
+  clientId: z.string().max(50).nullable().openapi({ 
+    description: '关联客户ID',
+    example: 'C2001' 
+  }),
+  projectId: z.string().max(50).nullable().openapi({ 
+    description: '关联项目ID',
+    example: 'P3001' 
+  }),
+  department: z.string().max(100).nullable().openapi({ 
+    description: '产生费用的部门',
+    example: '研发部' 
+  }),
+  description: z.string().nullable().openapi({ 
+    description: '费用描述',
+    example: '参加技术研讨会差旅费' 
+  }),
+  status: z.string().max(50).openapi({ 
+    description: '费用状态:如审批中、已报销等',
+    example: '已报销' 
+  }),
+  approver: z.string().max(50).nullable().openapi({ 
+    description: '费用审批人ID',
+    example: 'U1002' 
+  }),
+  approveDate: z.date().nullable().openapi({ 
+    description: '费用审批日期',
+    example: '2023-01-20' 
+  }),
+  reimbursementDate: z.date().nullable().openapi({ 
+    description: '费用报销日期',
+    example: '2023-01-25' 
+  }),
+  paymentMethod: z.string().max(50).nullable().openapi({ 
+    description: '支付方式',
+    example: '银行卡' 
+  }),
+  invoiceNumber: z.string().max(100).nullable().openapi({ 
+    description: '发票号码',
+    example: 'INV20230125001' 
+  }),
+  currency: z.string().max(20).openapi({ 
+    description: '货币类型',
+    example: 'CNY' 
+  }),
+  exchangeRate: z.number().multipleOf(0.0001).openapi({ 
+    description: '汇率',
+    example: 1.0000 
+  }),
+  foreignAmount: z.number().multipleOf(0.01).nullable().openapi({ 
+    description: '外币金额',
+    example: null 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-15T00:00:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2023-01-25T00:00:00Z' 
+  })
+});
+
+export const CreateExpenseDto = z.object({
+  id: z.string().max(50).openapi({ 
+    description: '费用记录ID',
+    example: 'EXP20230001' 
+  }),
+  expenseDate: z.coerce.date().openapi({ 
+    description: '费用发生日期',
+    example: '2023-01-15' 
+  }),
+  userId: z.string().max(50).openapi({ 
+    description: '产生费用的用户ID',
+    example: 'U1001' 
+  }),
+  type: z.string().max(50).openapi({ 
+    description: '费用类型',
+    example: '差旅费' 
+  }),
+  amount: z.coerce.number().multipleOf(0.01).openapi({ 
+    description: '费用金额',
+    example: 1500.50 
+  }),
+  clientId: z.string().max(50).nullable().optional().openapi({ 
+    description: '关联客户ID',
+    example: 'C2001' 
+  }),
+  projectId: z.string().max(50).nullable().optional().openapi({ 
+    description: '关联项目ID',
+    example: 'P3001' 
+  }),
+  department: z.string().max(100).nullable().optional().openapi({ 
+    description: '产生费用的部门',
+    example: '研发部' 
+  }),
+  description: z.string().nullable().optional().openapi({ 
+    description: '费用描述',
+    example: '参加技术研讨会差旅费' 
+  }),
+  status: z.string().max(50).openapi({ 
+    description: '费用状态:如审批中、已报销等',
+    example: '审批中' 
+  }),
+  approver: z.string().max(50).nullable().optional().openapi({ 
+    description: '费用审批人ID',
+    example: 'U1002' 
+  }),
+  approveDate: z.coerce.date().nullable().optional().openapi({ 
+    description: '费用审批日期',
+    example: '2023-01-20' 
+  }),
+  reimbursementDate: z.coerce.date().nullable().optional().openapi({ 
+    description: '费用报销日期',
+    example: '2023-01-25' 
+  }),
+  paymentMethod: z.string().max(50).nullable().optional().openapi({ 
+    description: '支付方式',
+    example: '银行卡' 
+  }),
+  invoiceNumber: z.string().max(100).nullable().optional().openapi({ 
+    description: '发票号码',
+    example: 'INV20230125001' 
+  }),
+  currency: z.string().max(20).default('CNY').openapi({ 
+    description: '货币类型',
+    example: 'CNY' 
+  }),
+  exchangeRate: z.coerce.number().multipleOf(0.0001).default(1).openapi({ 
+    description: '汇率',
+    example: 1.0000 
+  }),
+  foreignAmount: z.coerce.number().multipleOf(0.01).nullable().optional().openapi({ 
+    description: '外币金额',
+    example: null 
+  })
+});
+
+export const UpdateExpenseDto = z.object({
+  expenseDate: z.coerce.date().optional().openapi({ 
+    description: '费用发生日期',
+    example: '2023-01-15' 
+  }),
+  userId: z.string().max(50).optional().openapi({ 
+    description: '产生费用的用户ID',
+    example: 'U1001' 
+  }),
+  type: z.string().max(50).optional().openapi({ 
+    description: '费用类型',
+    example: '差旅费' 
+  }),
+  amount: z.coerce.number().multipleOf(0.01).optional().openapi({ 
+    description: '费用金额',
+    example: 1500.50 
+  }),
+  clientId: z.string().max(50).nullable().optional().openapi({ 
+    description: '关联客户ID',
+    example: 'C2001' 
+  }),
+  projectId: z.string().max(50).nullable().optional().openapi({ 
+    description: '关联项目ID',
+    example: 'P3001' 
+  }),
+  department: z.string().max(100).nullable().optional().openapi({ 
+    description: '产生费用的部门',
+    example: '研发部' 
+  }),
+  description: z.string().nullable().optional().openapi({ 
+    description: '费用描述',
+    example: '参加技术研讨会差旅费' 
+  }),
+  status: z.string().max(50).optional().openapi({ 
+    description: '费用状态:如审批中、已报销等',
+    example: '已报销' 
+  }),
+  approver: z.string().max(50).nullable().optional().openapi({ 
+    description: '费用审批人ID',
+    example: 'U1002' 
+  }),
+  approveDate: z.coerce.date().nullable().optional().openapi({ 
+    description: '费用审批日期',
+    example: '2023-01-20' 
+  }),
+  reimbursementDate: z.coerce.date().nullable().optional().openapi({ 
+    description: '费用报销日期',
+    example: '2023-01-25' 
+  }),
+  paymentMethod: z.string().max(50).nullable().optional().openapi({ 
+    description: '支付方式',
+    example: '银行卡' 
+  }),
+  invoiceNumber: z.string().max(100).nullable().optional().openapi({ 
+    description: '发票号码',
+    example: 'INV20230125001' 
+  }),
+  currency: z.string().max(20).optional().openapi({ 
+    description: '货币类型',
+    example: 'CNY' 
+  }),
+  exchangeRate: z.coerce.number().multipleOf(0.0001).optional().openapi({ 
+    description: '汇率',
+    example: 1.0000 
+  }),
+  foreignAmount: z.coerce.number().multipleOf(0.01).nullable().optional().openapi({ 
+    description: '外币金额',
+    example: null 
+  })
+});

+ 9 - 0
src/server/modules/expenses/expense.service.ts

@@ -0,0 +1,9 @@
+import { GenericCrudService } from '@/server/utils/generic-crud.service';
+import { DataSource } from 'typeorm';
+import { Expense } from './expense.entity';
+
+export class ExpenseService extends GenericCrudService<Expense> {
+  constructor(dataSource: DataSource) {
+    super(dataSource, Expense);
+  }
+}

+ 164 - 0
src/server/modules/files/file.entity.ts

@@ -0,0 +1,164 @@
+import { Entity, PrimaryColumn, Column, Index } from 'typeorm';
+import { z } from '@hono/zod-openapi';
+
+@Entity('file')
+export class File {
+  @PrimaryColumn({ name: 'id', type: 'varchar', length: 50 })
+  id!: string;
+
+  @Column({ name: 'name', type: 'varchar', length: 255 })
+  name!: string;
+
+  @Column({ name: 'type', type: 'varchar', length: 50, nullable: true })
+  type?: string;
+
+  @Column({ name: 'size', type: 'int', unsigned: true, nullable: true })
+  size?: number;
+
+  @Column({ name: 'path', type: 'varchar', length: 512 })
+  path!: string;
+
+  @Column({ name: 'description', type: 'text', nullable: true })
+  description?: string;
+
+  @Column({ name: 'upload_user_id', type: 'varchar', length: 50 })
+  uploadUserId!: string;
+
+  @Column({ name: 'upload_time', type: 'datetime' })
+  uploadTime!: Date;
+
+  @Column({ name: 'last_updated', type: 'datetime', nullable: true })
+  lastUpdated?: Date;
+
+  @Column({ name: 'created_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
+  createdAt!: Date;
+
+  @Column({ 
+    name: 'updated_at', 
+    type: 'timestamp', 
+    default: () => 'CURRENT_TIMESTAMP', 
+    onUpdate: 'CURRENT_TIMESTAMP' 
+  })
+  updatedAt!: Date;
+}
+
+export const FileSchema = z.object({
+  id: z.string().max(50).openapi({ 
+    description: '文件ID',
+    example: 'FILE20230001' 
+  }),
+  name: z.string().max(255).openapi({ 
+    description: '文件名称',
+    example: '项目计划书.pdf' 
+  }),
+  type: z.string().max(50).nullable().openapi({ 
+    description: '文件类型',
+    example: 'application/pdf' 
+  }),
+  size: z.number().int().positive().nullable().openapi({ 
+    description: '文件大小,单位字节',
+    example: 102400 
+  }),
+  path: z.string().max(512).openapi({ 
+    description: '文件存储路径',
+    example: '/uploads/documents/2023/project-plan.pdf' 
+  }),
+  description: z.string().nullable().openapi({ 
+    description: '文件描述',
+    example: '2023年度项目计划书' 
+  }),
+  uploadUserId: z.string().max(50).openapi({ 
+    description: '上传用户ID',
+    example: 'U1001' 
+  }),
+  uploadTime: z.date().openapi({ 
+    description: '上传时间',
+    example: '2023-01-15T10:30:00Z' 
+  }),
+  lastUpdated: z.date().nullable().openapi({ 
+    description: '最后更新时间',
+    example: '2023-01-16T14:20:00Z' 
+  }),
+  createdAt: z.date().openapi({ 
+    description: '创建时间',
+    example: '2023-01-15T10:30:00Z' 
+  }),
+  updatedAt: z.date().openapi({ 
+    description: '更新时间',
+    example: '2023-01-16T14:20:00Z' 
+  })
+});
+
+export const CreateFileDto = z.object({
+  id: z.string().max(50).openapi({ 
+    description: '文件ID',
+    example: 'FILE20230001' 
+  }),
+  name: z.string().max(255).openapi({ 
+    description: '文件名称',
+    example: '项目计划书.pdf' 
+  }),
+  type: z.string().max(50).nullable().optional().openapi({ 
+    description: '文件类型',
+    example: 'application/pdf' 
+  }),
+  size: z.coerce.number().int().positive().nullable().optional().openapi({ 
+    description: '文件大小,单位字节',
+    example: 102400 
+  }),
+  path: z.string().max(512).openapi({ 
+    description: '文件存储路径',
+    example: '/uploads/documents/2023/project-plan.pdf' 
+  }),
+  description: z.string().nullable().optional().openapi({ 
+    description: '文件描述',
+    example: '2023年度项目计划书' 
+  }),
+  uploadUserId: z.string().max(50).openapi({ 
+    description: '上传用户ID',
+    example: 'U1001' 
+  }),
+  uploadTime: z.coerce.date().openapi({ 
+    description: '上传时间',
+    example: '2023-01-15T10:30:00Z' 
+  }),
+  lastUpdated: z.coerce.date().nullable().optional().openapi({ 
+    description: '最后更新时间',
+    example: '2023-01-16T14:20:00Z' 
+  })
+});
+
+export const UpdateFileDto = z.object({
+  name: z.string().max(255).optional().openapi({ 
+    description: '文件名称',
+    example: '项目计划书_v2.pdf' 
+  }),
+  type: z.string().max(50).nullable().optional().openapi({ 
+    description: '文件类型',
+    example: 'application/pdf' 
+  }),
+  size: z.coerce.number().int().positive().nullable().optional().openapi({ 
+    description: '文件大小,单位字节',
+    example: 153600 
+  }),
+  path: z.string().max(512).optional().openapi({ 
+    description: '文件存储路径',
+    example: '/uploads/documents/2023/project-plan_v2.pdf' 
+  }),
+  description: z.string().nullable().optional().openapi({ 
+    description: '文件描述',
+    example: '2023年度项目计划书(修订版)' 
+  }),
+  uploadUserId: z.string().max(50).optional().openapi({ 
+    description: '上传用户ID',
+    example: 'U1001' 
+  }),
+  uploadTime: z.coerce.date().optional().openapi({ 
+    description: '上传时间',
+    example: '2023-01-15T10:30:00Z' 
+  }),
+  lastUpdated: z.coerce.date().nullable().optional().openapi({ 
+    description: '最后更新时间',
+    example: '2023-01-16T14:20:00Z' 
+  })
+});

+ 9 - 0
src/server/modules/files/file.service.ts

@@ -0,0 +1,9 @@
+import { GenericCrudService } from '@/server/utils/generic-crud.service';
+import { DataSource } from 'typeorm';
+import { File } from './file.entity';
+
+export class FileService extends GenericCrudService<File> {
+  constructor(dataSource: DataSource) {
+    super(dataSource, File);
+  }
+}