yourname 5 miesięcy temu
rodzic
commit
6eed69a445

+ 122 - 0
src/server/api/contracts/[id]/delete.ts

@@ -0,0 +1,122 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@/server/data-source';
+import { ContractService } from '@/server/modules/contracts/contract.service';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { authMiddleware } from '@/server/middleware/auth';
+import { AuthContext } from '@/server/types/context';
+import { logger } from '@/server/utils/logger';
+
+// 初始化服务
+const contractService = new ContractService(AppDataSource);
+
+// 路径参数Schema
+const DeleteParamsSchema = z.object({
+  id: z.coerce.number().int().positive().openapi({
+    param: { name: 'id', in: 'path' },
+    description: '合同ID',
+    example: 1
+  })
+});
+
+// 响应Schema
+const DeleteContractResponse = z.object({
+  code: z.number().int().openapi({
+    description: '状态码',
+    example: 200
+  }),
+  message: z.string().openapi({
+    description: '消息',
+    example: '合同删除成功'
+  }),
+  data: z.object({}).openapi({
+    description: '空对象'
+  })
+});
+
+// 路由定义
+const route = createRoute({
+  method: 'delete',
+  path: '/{id}',
+  middleware: [authMiddleware],
+  request: {
+    params: DeleteParamsSchema
+  },
+  responses: {
+    200: {
+      description: '合同删除成功',
+      content: {
+        'application/json': {
+          schema: DeleteContractResponse
+        }
+      }
+    },
+    404: {
+      description: '合同不存在',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    },
+    400: {
+      description: '请求参数错误或合同状态不允许删除',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    }
+  },
+  tags: ['contracts']
+});
+
+// 创建路由实例
+const app = new OpenAPIHono<AuthContext>().openapi(route, async (c) => {
+  const { id } = c.req.valid('param');
+  try {
+    // 删除合同
+    const result = await contractService.deleteContract(id);
+    
+    if (!result) {
+      logger.api(`Contract with ID ${id} not found`);
+      return c.json({
+        code: 404,
+        message: '合同不存在'
+      } as const, 404);
+    }
+    
+    logger.api(`Contract deleted with ID: ${id}`);
+    
+    return c.json({
+      code: 200,
+      message: '合同删除成功',
+      data: {}
+    } as const, 200);
+  } catch (error) {
+    logger.error(`Error deleting contract with ID ${id}:`, error);
+    
+    // 特定错误处理 - 非草稿状态合同不能删除
+    if (error instanceof Error && error.message === '只有草稿状态的合同可以删除') {
+      return c.json({
+        code: 400,
+        message: error.message
+      } as const, 400);
+    }
+    
+    return c.json({
+      code: 500,
+      message: error instanceof Error ? error.message : '删除合同失败'
+    } as const, 500);
+  }
+});
+
+export default app;

+ 107 - 0
src/server/api/contracts/[id]/get.ts

@@ -0,0 +1,107 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@/server/data-source';
+import { ContractService } from '@/server/modules/contracts/contract.service';
+import { ContractSchema } from '@/server/modules/contracts/contract.entity';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { authMiddleware } from '@/server/middleware/auth';
+import { AuthContext } from '@/server/types/context';
+import { logger } from '@/server/utils/logger';
+
+// 初始化服务
+const contractService = new ContractService(AppDataSource);
+
+// 路径参数Schema
+const GetParamsSchema = z.object({
+  id: z.coerce.number().int().positive().openapi({
+    param: { name: 'id', in: 'path' },
+    description: '合同ID',
+    example: 1
+  })
+});
+
+// 响应Schema
+const GetContractResponse = z.object({
+  code: z.number().int().openapi({
+    description: '状态码',
+    example: 200
+  }),
+  message: z.string().openapi({
+    description: '消息',
+    example: 'success'
+  }),
+  data: ContractSchema.openapi({
+    description: '合同详情'
+  })
+});
+
+// 路由定义
+const route = createRoute({
+  method: 'get',
+  path: '/{id}',
+  middleware: [authMiddleware],
+  request: {
+    params: GetParamsSchema
+  },
+  responses: {
+    200: {
+      description: '获取合同详情成功',
+      content: {
+        'application/json': {
+          schema: GetContractResponse
+        }
+      }
+    },
+    404: {
+      description: '合同不存在',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    }
+  },
+  tags: ['contracts']
+});
+
+// 创建路由实例
+const app = new OpenAPIHono<AuthContext>().openapi(route, async (c) => {
+  const { id } = c.req.valid('param');
+  try {
+    
+    // 获取合同详情
+    const contract = await contractService.getContractById(id);
+    
+    if (!contract) {
+      logger.api(`Contract with ID ${id} not found`);
+      return c.json({
+        code: 404,
+        message: '合同不存在'
+      }, 404);
+    }
+    
+    logger.api(`Fetched contract details for ID: ${id}`);
+    
+    return c.json({
+      code: 200,
+      message: 'success',
+      data: contract
+    }, 200);
+  } catch (error) {
+    logger.error(`Error fetching contract with ID ${id}:`, error);
+    return c.json({
+      code: 500,
+      message: error instanceof Error ? error.message : '获取合同详情失败'
+    }, 500);
+  }
+});
+
+export default app;

+ 171 - 0
src/server/api/contracts/[id]/put.ts

@@ -0,0 +1,171 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@/server/data-source';
+import { ContractService, ContractStatus } from '@/server/modules/contracts/contract.service';
+import { ContractSchema } from '@/server/modules/contracts/contract.entity';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { authMiddleware } from '@/server/middleware/auth';
+import { AuthContext } from '@/server/types/context';
+import { logger } from '@/server/utils/logger';
+
+// 初始化服务
+const contractService = new ContractService(AppDataSource);
+
+// 路径参数Schema
+const UpdateParamsSchema = z.object({
+  id: z.coerce.number().int().positive().openapi({
+    param: { name: 'id', in: 'path' },
+    description: '合同ID',
+    example: 1
+  })
+});
+
+// 更新合同请求Schema
+const UpdateContractSchema = z.object({
+  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().positive().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%'
+  }),
+  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系统升级服务'
+  })
+});
+
+// 响应Schema
+const UpdateContractResponse = z.object({
+  code: z.number().int().openapi({
+    description: '状态码',
+    example: 200
+  }),
+  message: z.string().openapi({
+    description: '消息',
+    example: '合同更新成功'
+  }),
+  data: ContractSchema.openapi({
+    description: '更新后的合同信息'
+  })
+});
+
+// 路由定义
+const route = createRoute({
+  method: 'put',
+  path: '/{id}',
+  middleware: [authMiddleware],
+  request: {
+    params: UpdateParamsSchema,
+    body: {
+      content: {
+        'application/json': {
+          schema: UpdateContractSchema
+        }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '合同更新成功',
+      content: {
+        'application/json': {
+          schema: UpdateContractResponse
+        }
+      }
+    },
+    400: {
+      description: '请求参数错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    },
+    404: {
+      description: '合同不存在',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    }
+  },
+  tags: ['contracts']
+});
+
+// 创建路由实例
+const app = new OpenAPIHono<AuthContext>().openapi(route, async (c) => {
+  const { id } = c.req.valid('param');
+  try {
+    const contractData = c.req.valid('json');
+    
+    // 更新合同
+    const updatedContract = await contractService.updateContract(id, contractData);
+    
+    if (!updatedContract) {
+      logger.api(`Contract with ID ${id} not found`);
+      return c.json({
+        code: 404,
+        message: '合同不存在'
+      }, 404);
+    }
+    
+    logger.api(`Contract updated with ID: ${id}`);
+    
+    return c.json({
+      code: 200,
+      message: '合同更新成功',
+      data: updatedContract
+    }, 200);
+  } catch (error) {
+    logger.error(`Error updating contract with ID ${id}:`, error);
+    return c.json({
+      code: 500,
+      message: error instanceof Error ? error.message : '更新合同失败'
+    }, 500);
+  }
+});
+
+export default app;

+ 149 - 0
src/server/api/contracts/[id]/status.ts

@@ -0,0 +1,149 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@/server/data-source';
+import { ContractService, ContractStatus } from '@/server/modules/contracts/contract.service';
+import { ContractSchema } from '@/server/modules/contracts/contract.entity';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { authMiddleware } from '@/server/middleware/auth';
+import { AuthContext } from '@/server/types/context';
+import { logger } from '@/server/utils/logger';
+
+// 初始化服务
+const contractService = new ContractService(AppDataSource);
+
+// 路径参数Schema
+const StatusParamsSchema = z.object({
+  id: z.coerce.number().int().positive().openapi({
+    param: { name: 'id', in: 'path' },
+    description: '合同ID',
+    example: 1
+  })
+});
+
+// 状态变更请求Schema
+const ChangeStatusSchema = z.object({
+  status: z.number().int().min(0).max(4).openapi({
+    description: '新合同状态(0-草稿, 1-已签约, 2-执行中, 3-已完成, 4-已终止)',
+    example: 2
+  })
+});
+
+// 响应Schema
+const ChangeStatusResponse = z.object({
+  code: z.number().int().openapi({
+    description: '状态码',
+    example: 200
+  }),
+  message: z.string().openapi({
+    description: '消息',
+    example: '合同状态更新成功'
+  }),
+  data: ContractSchema.openapi({
+    description: '更新后的合同信息'
+  })
+});
+
+// 路由定义
+const route = createRoute({
+  method: 'patch',
+  path: '/{id}/status',
+  middleware: [authMiddleware],
+  request: {
+    params: StatusParamsSchema,
+    body: {
+      content: {
+        'application/json': {
+          schema: ChangeStatusSchema
+        }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '合同状态更新成功',
+      content: {
+        'application/json': {
+          schema: ChangeStatusResponse
+        }
+      }
+    },
+    400: {
+      description: '请求参数错误或状态变更不合法',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    },
+    404: {
+      description: '合同不存在',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    }
+  },
+  tags: ['contracts']
+});
+
+// 创建路由实例
+const app = new OpenAPIHono<AuthContext>().openapi(route, async (c) => {
+  const { id } = c.req.valid('param');
+  try {
+    const { status } = c.req.valid('json');
+    
+    // 验证状态值是否有效
+    if (!Object.values(ContractStatus).includes(status)) {
+      logger.error(`Invalid contract status: ${status}`);
+      return c.json({
+        code: 400,
+        message: '无效的合同状态值'
+      } as const, 400);
+    }
+    
+    // 变更合同状态
+    const updatedContract = await contractService.changeContractStatus(id, status);
+    
+    if (!updatedContract) {
+      logger.api(`Contract with ID ${id} not found`);
+      return c.json({
+        code: 404,
+        message: '合同不存在'
+      } as const, 404);
+    }
+    
+    logger.api(`Contract status updated for ID ${id}: ${status}`);
+    
+    return c.json({
+      code: 200,
+      message: '合同状态更新成功',
+      data: updatedContract
+    } as const, 200);
+  } catch (error) {
+    logger.error(`Error updating contract status for ID ${id}:`, error);
+    
+    // 处理状态变更不合法的错误
+    if (error instanceof Error && error.message.includes('不允许从')) {
+      return c.json({
+        code: 400,
+        message: error.message
+      } as const, 400);
+    }
+    
+    return c.json({
+      code: 500,
+      message: error instanceof Error ? error.message : '更新合同状态失败'
+    } as const, 500);
+  }
+});
+
+export default app;

+ 170 - 0
src/server/api/contracts/get.ts

@@ -0,0 +1,170 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@/server/data-source';
+import { ContractService, ContractStatus } from '@/server/modules/contracts/contract.service';
+import { ContractSchema } from '@/server/modules/contracts/contract.entity';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { authMiddleware } from '@/server/middleware/auth';
+import { AuthContext } from '@/server/types/context';
+import { logger } from '@/server/utils/logger';
+
+// 初始化服务
+const contractService = new ContractService(AppDataSource);
+
+// 查询参数Schema
+const GetQuerySchema = z.object({
+  page: z.coerce.number().int().positive().default(1).openapi({
+    description: '页码',
+    example: 1
+  }),
+  pageSize: z.coerce.number().int().positive().default(10).openapi({
+    description: '每页数量',
+    example: 10
+  }),
+  status: z.coerce.number().int().min(0).max(4).optional().openapi({
+    description: '合同状态(0-草稿, 1-已签约, 2-执行中, 3-已完成, 4-已终止)',
+    example: 2
+  }),
+  customerId: z.coerce.number().int().positive().optional().openapi({
+    description: '客户ID',
+    example: 1
+  }),
+  keyword: z.string().optional().openapi({
+    description: '搜索关键词(标题或合同编号)',
+    example: 'ERP'
+  })
+});
+
+// 响应Schema
+const ContractListResponse = z.object({
+  code: z.number().int().openapi({
+    description: '状态码',
+    example: 200
+  }),
+  message: z.string().openapi({
+    description: '消息',
+    example: 'success'
+  }),
+  data: z.object({
+    list: z.array(ContractSchema).openapi({
+      description: '合同列表'
+    }),
+    pagination: z.object({
+      total: z.number().int().openapi({
+        description: '总记录数',
+        example: 100
+      }),
+      current: z.number().int().openapi({
+        description: '当前页码',
+        example: 1
+      }),
+      pageSize: z.number().int().openapi({
+        description: '每页数量',
+        example: 10
+      }),
+      totalPages: z.number().int().openapi({
+        description: '总页数',
+        example: 10
+      })
+    }).openapi({
+      description: '分页信息'
+    })
+  }).openapi({
+    description: '返回数据'
+  })
+});
+
+// 路由定义
+const route = createRoute({
+  method: 'get',
+  path: '/',
+  middleware: [authMiddleware],
+  request: {
+    query: GetQuerySchema
+  },
+  responses: {
+    200: {
+      description: '获取合同列表成功',
+      content: {
+        'application/json': {
+          schema: ContractListResponse
+        }
+      }
+    },
+    400: {
+      description: '请求参数错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    }
+  },
+  tags: ['contracts']
+});
+
+// 创建路由实例
+const app = new OpenAPIHono<AuthContext>().openapi(route, async (c) => {
+  try {
+    const { page, pageSize, status, customerId, keyword } = c.req.valid('query');
+    
+    // 构建查询条件
+    const filters: Record<string, any> = {};
+    if (status !== undefined) {
+      filters.status = status;
+    }
+    if (customerId) {
+      filters.customerId = customerId;
+    }
+    // 逻辑删除状态过滤
+    filters.isDeleted = 0;
+    
+    // 查询合同列表
+    const result = await contractService.getContracts(page, pageSize, filters);
+    
+    // 处理关键词搜索(如果提供)
+    let contracts = result.data;
+    if (keyword) {
+      const keywordLower = keyword.toLowerCase();
+      contracts = contracts.filter(contract => 
+        contract.title.toLowerCase().includes(keywordLower) || 
+        contract.contractNo.toLowerCase().includes(keywordLower)
+      );
+    }
+    
+    // 计算总页数
+    const totalPages = Math.ceil(result.total / pageSize);
+    
+    logger.api(`Fetched ${contracts.length} contracts, total: ${result.total}, page: ${page}/${totalPages}`);
+    
+    return c.json({
+      code: 200,
+      message: 'success',
+      data: {
+        list: contracts,
+        pagination: {
+          total: result.total,
+          current: page,
+          pageSize,
+          totalPages
+        }
+      }
+    }, 200);
+  } catch (error) {
+    logger.error('Error fetching contracts:', error);
+    return c.json({
+      code: 500,
+      message: error instanceof Error ? error.message : '获取合同列表失败'
+    }, 500);
+  }
+});
+
+export default app;

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

@@ -0,0 +1,17 @@
+import { OpenAPIHono } from '@hono/zod-openapi';
+import listRoute from './get';
+import createRoute from './post';
+import getByIdRoute from './[id]/get';
+import updateRoute from './[id]/put';
+import deleteRoute from './[id]/delete';
+import statusRoute from './[id]/status';
+
+const app = new OpenAPIHono()
+  .route('/', listRoute)       // GET /contracts
+  .route('/', createRoute)     // POST /contracts
+  .route('/', getByIdRoute)    // GET /contracts/{id}
+  .route('/', updateRoute)     // PUT /contracts/{id}
+  .route('/', deleteRoute)     // DELETE /contracts/{id}
+  .route('/', statusRoute);    // PATCH /contracts/{id}/status
+
+export default app;

+ 143 - 0
src/server/api/contracts/post.ts

@@ -0,0 +1,143 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@/server/data-source';
+import { ContractService } from '@/server/modules/contracts/contract.service';
+import { ContractSchema } from '@/server/modules/contracts/contract.entity';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { authMiddleware } from '@/server/middleware/auth';
+import { AuthContext } from '@/server/types/context';
+import { logger } from '@/server/utils/logger';
+
+// 初始化服务
+const contractService = new ContractService(AppDataSource);
+
+// 创建合同请求Schema
+const CreateContractSchema = z.object({
+  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().positive().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'
+  }),
+  paymentTerms: z.string().nullable().openapi({
+    description: '付款条件',
+    example: '首付50%,验收后付40%,质保金10%'
+  }),
+  description: z.string().nullable().openapi({
+    description: '合同描述',
+    example: '为客户提供ERP系统升级服务'
+  })
+});
+
+// 响应Schema
+const CreateContractResponse = z.object({
+  code: z.number().int().openapi({
+    description: '状态码',
+    example: 200
+  }),
+  message: z.string().openapi({
+    description: '消息',
+    example: '合同创建成功'
+  }),
+  data: ContractSchema.openapi({
+    description: '创建的合同信息'
+  })
+});
+
+// 路由定义
+const route = createRoute({
+  method: 'post',
+  path: '/',
+  middleware: [authMiddleware],
+  request: {
+    body: {
+      content: {
+        'application/json': {
+          schema: CreateContractSchema
+        }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '合同创建成功',
+      content: {
+        'application/json': {
+          schema: CreateContractResponse
+        }
+      }
+    },
+    400: {
+      description: '请求参数错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    }
+  },
+  tags: ['contracts']
+});
+
+// 创建路由实例
+const app = new OpenAPIHono<AuthContext>().openapi(route, async (c) => {
+  try {
+    const contractData = c.req.valid('json');
+    const user = c.get('user');
+    
+    // 添加创建者信息
+    const contractToCreate = {
+      ...contractData,
+      signedBy: user.id // 从认证上下文获取签约人ID
+    };
+    
+    // 创建合同
+    const newContract = await contractService.createContract(contractToCreate);
+    
+    logger.api(`Contract created with ID: ${newContract.id}, number: ${newContract.contractNo}`);
+    
+    return c.json({
+      code: 200,
+      message: '合同创建成功',
+      data: newContract
+    }, 200);
+  } catch (error) {
+    logger.error('Error creating contract:', error);
+    return c.json({
+      code: 500,
+      message: error instanceof Error ? error.message : '创建合同失败'
+    }, 500);
+  }
+});
+
+export default app;

+ 246 - 0
src/server/modules/contracts/contract.service.ts

@@ -0,0 +1,246 @@
+import { DataSource, Repository, FindOptionsWhere } from 'typeorm';
+import { Contract, ContractStatus as EntityContractStatus } from './contract.entity';
+import { logger } from '@/server/utils/logger';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { z } from 'zod';
+
+// 合同状态枚举
+// 扩展实体中定义的合同状态枚举
+export enum ContractStatus {
+  DRAFT = EntityContractStatus.DRAFT,
+  REVIEWING = EntityContractStatus.REVIEWING,
+  ACTIVE = EntityContractStatus.ACTIVE,
+  TERMINATED = EntityContractStatus.TERMINATED,
+  EXPIRED = EntityContractStatus.EXPIRED
+}
+
+// 合同状态流转规则
+const STATUS_TRANSITIONS: Record<ContractStatus, ContractStatus[]> = {
+  [ContractStatus.DRAFT]: [ContractStatus.REVIEWING, ContractStatus.TERMINATED],
+  [ContractStatus.REVIEWING]: [ContractStatus.ACTIVE, ContractStatus.DRAFT, ContractStatus.TERMINATED],
+  [ContractStatus.ACTIVE]: [ContractStatus.TERMINATED, ContractStatus.EXPIRED],
+  [ContractStatus.TERMINATED]: [],
+  [ContractStatus.EXPIRED]: []
+};
+
+export class ContractService {
+  private contractRepository: Repository<Contract>;
+
+  constructor(private dataSource: DataSource) {
+    this.contractRepository = dataSource.getRepository(Contract);
+  }
+
+  /**
+   * 生成合同编号
+   * 格式: CONTRACT-{年份}{月份}{日期}-{4位随机数}
+   */
+  private async generateContractNumber(): Promise<string> {
+    const date = new Date();
+    const year = date.getFullYear().toString().slice(-2);
+    const month = (date.getMonth() + 1).toString().padStart(2, '0');
+    const day = date.getDate().toString().padStart(2, '0');
+    const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
+    
+    // 确保合同编号唯一
+    let contractNumber = `CONTRACT-${year}${month}${day}-${random}`;
+    const existingContract = await this.contractRepository.findOne({
+      where: { contract_number: contractNumber } as FindOptionsWhere<Contract>
+    });
+    
+    if (existingContract) {
+      // 如果编号已存在,递归生成新编号
+      return this.generateContractNumber();
+    }
+    
+    return contractNumber;
+  }
+
+  /**
+   * 检查合同状态变更是否合法
+   */
+  private isValidStatusTransition(currentStatus: ContractStatus, newStatus: ContractStatus): boolean {
+    return STATUS_TRANSITIONS[currentStatus].includes(newStatus);
+  }
+
+  /**
+   * 创建合同
+   */
+  async createContract(data: Partial<Contract>): Promise<Contract> {
+    logger.api('Creating new contract');
+    
+    try {
+      const contract = new Contract();
+      Object.assign(contract, data);
+      
+      // 生成合同编号
+      contract.contract_number = await this.generateContractNumber();
+      
+      // 默认状态为草稿
+      contract.status = ContractStatus.DRAFT;
+      
+      // 设置创建时间和更新时间
+      contract.createdAt = new Date();
+      contract.updatedAt = new Date();
+      
+      const savedContract = await this.contractRepository.save(contract);
+      logger.api(`Contract created with ID: ${savedContract.id}`);
+      return savedContract;
+    } catch (error) {
+      logger.error('Error creating contract:', error);
+      throw new Error('创建合同失败');
+    }
+  }
+
+  /**
+   * 获取合同列表(支持分页和筛选)
+   */
+  async getContracts(
+    page: number = 1, 
+    pageSize: number = 10, 
+    filters: Partial<Contract> = {}
+  ): Promise<{ data: Contract[], total: number, current: number, pageSize: number }> {
+    logger.api(`Fetching contracts with filters: ${JSON.stringify(filters)}, page: ${page}, pageSize: ${pageSize}`);
+    
+    try {
+      const [data, total] = await this.contractRepository.findAndCount({
+        where: filters as FindOptionsWhere<Contract>,
+        skip: (page - 1) * pageSize,
+        take: pageSize,
+        order: { createdAt: 'DESC' }
+      });
+      
+      return {
+        data,
+        total,
+        current: page,
+        pageSize
+      };
+    } catch (error) {
+      logger.error('Error fetching contracts:', error);
+      throw new Error('获取合同列表失败');
+    }
+  }
+
+  /**
+   * 获取合同详情
+   */
+  async getContractById(id: number): Promise<Contract | null> {
+    logger.api(`Fetching contract with ID: ${id}`);
+    
+    try {
+      const contract = await this.contractRepository.findOne({
+        where: { id }
+      });
+      
+      if (!contract) {
+        logger.api(`Contract with ID ${id} not found`);
+        return null;
+      }
+      
+      return contract;
+    } catch (error) {
+      logger.error(`Error fetching contract with ID ${id}:`, error);
+      throw new Error('获取合同详情失败');
+    }
+  }
+
+  /**
+   * 更新合同
+   */
+  async updateContract(id: number, data: Partial<Contract>): Promise<Contract | null> {
+    logger.api(`Updating contract with ID: ${id}`);
+    
+    try {
+      const contract = await this.contractRepository.findOne({
+        where: { id }
+      });
+      
+      if (!contract) {
+        logger.api(`Contract with ID ${id} not found`);
+        return null;
+      }
+      
+      // 如果尝试更新状态,需要验证状态流转是否合法
+      if (data.status && data.status !== contract.status) {
+        if (data.status !== undefined && !this.isValidStatusTransition(contract.status, data.status)) {
+          logger.error(`Invalid status transition from ${contract.status} to ${data.status}`);
+          throw new Error(`不允许从${contract.status}状态变更为${data.status}状态`);
+        }
+      }
+      
+      Object.assign(contract, data);
+      contract.updatedAt = new Date();
+      
+      const updatedContract = await this.contractRepository.save(contract);
+      logger.api(`Contract with ID ${id} updated successfully`);
+      return updatedContract;
+    } catch (error) {
+      logger.error(`Error updating contract with ID ${id}:`, error);
+      throw new Error('更新合同失败');
+    }
+  }
+
+  /**
+   * 删除合同
+   */
+  async deleteContract(id: number): Promise<boolean> {
+    logger.api(`Deleting contract with ID: ${id}`);
+    
+    try {
+      const contract = await this.contractRepository.findOne({
+        where: { id }
+      });
+      
+      if (!contract) {
+        logger.api(`Contract with ID ${id} not found`);
+        return false;
+      }
+      
+      // 检查合同状态,如果不是草稿状态,不允许删除
+      if (contract.status !== ContractStatus.DRAFT) {
+        logger.error(`Cannot delete contract with ID ${id} because it's not in draft status`);
+        throw new Error('只有草稿状态的合同可以删除');
+      }
+      
+      await this.contractRepository.remove(contract);
+      logger.api(`Contract with ID ${id} deleted successfully`);
+      return true;
+    } catch (error) {
+      logger.error(`Error deleting contract with ID ${id}:`, error);
+      throw new Error('删除合同失败');
+    }
+  }
+
+  /**
+   * 变更合同状态
+   */
+  async changeContractStatus(id: number, newStatus: ContractStatus): Promise<Contract | null> {
+    logger.api(`Changing contract status for ID ${id} to ${newStatus}`);
+    
+    try {
+      const contract = await this.contractRepository.findOne({
+        where: { id }
+      });
+      
+      if (!contract) {
+        logger.api(`Contract with ID ${id} not found`);
+        return null;
+      }
+      
+      if (!this.isValidStatusTransition(contract.status, newStatus)) {
+        logger.error(`Invalid status transition from ${contract.status} to ${newStatus}`);
+        throw new Error(`不允许从${contract.status}状态变更为${newStatus}状态`);
+      }
+      
+      contract.status = newStatus;
+      contract.updatedAt = new Date();
+      
+      const updatedContract = await this.contractRepository.save(contract);
+      logger.api(`Contract status for ID ${id} changed to ${newStatus} successfully`);
+      return updatedContract;
+    } catch (error) {
+      logger.error(`Error changing contract status for ID ${id}:`, error);
+      throw new Error('变更合同状态失败');
+    }
+  }
+}