Просмотр исходного кода

✨ feat(contract): 合同管理功能优化

- 合同金额输入框增加限制:最小值0,最大值999999999999.99,精确到小数点后两位
- 移除合同表单中的"关联用户ID"必填项
- 合同ID类型从string改为number,优化类型一致性

♻️ refactor(contract): 重构合同数据模型和API

- 更新editingKey状态类型从string改为number
- 优化updateContract方法,id参数直接使用number类型
- 移除数据权限控制配置,调整用户追踪字段为createdBy和updatedBy
- 合同实体增加createdBy和updatedBy字段记录创建人和更新人ID
- userId字段改为可空,优化数据模型灵活性

📝 docs(contract): 更新合同API文档

- 调整API文档中id字段类型为number,更新示例值
- 移除API文档中userId必填项说明
- 更新创建人和更新人字段文档说明
yourname 7 месяцев назад
Родитель
Сommit
ee10a8a56e

+ 10 - 16
src/client/admin/pages/Contracts.tsx

@@ -16,7 +16,7 @@ const Contracts: React.FC = () => {
   const {message} = App.useApp();
   const [form] = Form.useForm();
   const [modalVisible, setModalVisible] = useState(false);
-  const [editingKey, setEditingKey] = useState<string | null>(null);
+  const [editingKey, setEditingKey] = useState<number | null>(null);
   const [searchText, setSearchText] = useState('');
   const [pagination, setPagination] = useState({
     current: 1,
@@ -101,8 +101,8 @@ const Contracts: React.FC = () => {
   
   // 更新合同记录
   const updateContract = useMutation({
-    mutationFn: async ({ id, data }: { id: string; data: any }) => {
-      const response = await hetongClient[':id'].$put({ param: { id: parseInt(id, 10) }, json: data });
+    mutationFn: async ({ id, data }: { id: number; data: any }) => {
+      const response = await hetongClient[':id'].$put({ param: { id }, json: data });
       if (!response.ok) throw new Error('Failed to update contract');
       return response.json();
     },
@@ -321,11 +321,13 @@ const Contracts: React.FC = () => {
               label="合同金额"
               rules={[{ required: true, message: '请输入合同金额' }]}
             >
-              <InputNumber 
-                style={{ width: '100%' }} 
-                formatter={value => `¥ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
-                parser={value => value!.replace(/\¥\s?|(,*)/g, '')}
-                placeholder="请输入合同金额"
+              <InputNumber
+                style={{ width: '100%' }}
+                min={0}
+                max={999999999999.99}
+                precision={2}
+                step={0.01}
+                placeholder={`请输入合同金额 (最大 ¥${(999999999999.99).toLocaleString()})`}
               />
             </Form.Item>
             
@@ -365,14 +367,6 @@ const Contracts: React.FC = () => {
             >
               <Input placeholder="请输入合同状态:如生效中、已结束等" />
             </Form.Item>
-            
-            <Form.Item
-              name="userId"
-              label="关联用户ID"
-              rules={[{ required: true, message: '请输入关联用户ID' }]}
-            >
-              <Input placeholder="请输入关联用户ID" />
-            </Form.Item>
           </div>
           
           <Form.Item

+ 2 - 6
src/server/api/contracts/index.ts

@@ -12,13 +12,9 @@ const hetongRoutes = createCrudRoutes({
   relations: ['client'],
   searchFields: ['contractNumber', 'clientId', 'status'],
   middleware: [authMiddleware],
-  dataPermission: {
-    entity: 'Hetong',
-    userIdField: 'userId'
-  },
   userTracking: {
-    createdByField: 'userId',
-    updatedByField: 'userId'
+    createdByField: 'createdBy',
+    updatedByField: 'updatedBy'
   }
 });
 

+ 102 - 97
src/server/modules/contracts/hetong.entity.ts

@@ -11,8 +11,14 @@ export class Hetong {
   @Column({ name: 'contract_date', type: 'date' })
   contractDate!: Date;
 
-  @Column({ name: 'user_id', type: 'varchar', length: 50 })
-  userId!: string;
+  @Column({ name: 'user_id', type: 'varchar', length: 50, nullable: true })
+  userId?: string;
+
+  @Column({ name: 'created_by', type: 'int', nullable: true, comment: '创建人ID' })
+  createdBy?: number;
+
+  @Column({ name: 'updated_by', type: 'int', nullable: true, comment: '更新人ID' })
+  updatedBy?: number;
 
   @Column({ name: 'client_id', type: 'int', unsigned: true })
   clientId!: number;
@@ -73,203 +79,202 @@ export class Hetong {
 }
 
 export const HetongSchema = z.object({
-  id: z.string().max(50).openapi({ 
+  id: z.number().int().positive().openapi({
     description: '合同ID',
-    example: 'HT20230001' 
+    example: 1
   }),
-  contractDate: z.date().openapi({ 
+  contractDate: z.date().openapi({
     description: '合同日期',
-    example: '2023-01-15' 
+    example: '2023-01-15'
   }),
-  userId: z.string().max(50).openapi({ 
+  userId: z.string().max(50).nullable().openapi({
     description: '关联用户ID',
-    example: 'U1001' 
+    example: 'U1001'
+  }),
+  createdBy: z.number().int().positive().nullable().openapi({
+    description: '创建人ID',
+    example: 1001
+  }),
+  updatedBy: z.number().int().positive().nullable().openapi({
+    description: '更新人ID',
+    example: 1001
   }),
   clientId: z.number().int().positive().openapi({
     description: '客户ID',
     example: 2001
   }),
-  projectId: z.string().max(50).nullable().openapi({ 
+  projectId: z.string().max(50).nullable().openapi({
     description: '项目ID',
-    example: 'P3001' 
+    example: 'P3001'
   }),
-  amount: z.number().multipleOf(0.01).openapi({ 
+  amount: z.number().multipleOf(0.01).openapi({
     description: '合同金额',
-    example: 150000.00 
+    example: 150000.00
   }),
-  type: z.string().max(50).openapi({ 
+  type: z.string().max(50).openapi({
     description: '合同类型',
-    example: '服务合同' 
+    example: '服务合同'
   }),
-  status: z.string().max(50).openapi({ 
+  status: z.string().max(50).openapi({
     description: '合同状态:如生效中、已结束等',
-    example: '生效中' 
+    example: '生效中'
   }),
-  startDate: z.date().openapi({ 
+  startDate: z.date().openapi({
     description: '开始日期',
-    example: '2023-02-01' 
+    example: '2023-02-01'
   }),
-  endDate: z.date().openapi({ 
+  endDate: z.date().openapi({
     description: '结束日期',
-    example: '2024-01-31' 
+    example: '2024-01-31'
   }),
-  description: z.string().nullable().openapi({ 
+  description: z.string().nullable().openapi({
     description: '合同描述',
-    example: '年度技术服务合同' 
+    example: '年度技术服务合同'
   }),
-  contractNumber: z.string().max(100).openapi({ 
+  contractNumber: z.string().max(100).openapi({
     description: '合同编号',
-    example: 'HT-2023-0001' 
+    example: 'HT-2023-0001'
   }),
-  currency: z.string().max(20).openapi({ 
+  currency: z.string().max(20).openapi({
     description: '货币类型',
-    example: 'CNY' 
+    example: 'CNY'
   }),
-  exchangeRate: z.number().multipleOf(0.0001).openapi({ 
+  exchangeRate: z.number().multipleOf(0.0001).openapi({
     description: '汇率',
-    example: 1.0000 
+    example: 1.0000
   }),
-  foreignAmount: z.number().multipleOf(0.01).nullable().openapi({ 
+  foreignAmount: z.number().multipleOf(0.01).nullable().openapi({
     description: '外币金额',
-    example: null 
+    example: null
   }),
-  filePath: z.string().max(512).nullable().openapi({ 
+  filePath: z.string().max(512).nullable().openapi({
     description: '合同文件路径',
-    example: '/uploads/contracts/2023/HT-2023-0001.pdf' 
+    example: '/uploads/contracts/2023/HT-2023-0001.pdf'
   }),
-  createdAt: z.date().openapi({ 
+  createdAt: z.date().openapi({
     description: '创建时间',
-    example: '2023-01-15T00:00:00Z' 
+    example: '2023-01-15T00:00:00Z'
   }),
-  updatedAt: z.date().openapi({ 
+  updatedAt: z.date().openapi({
     description: '更新时间',
-    example: '2023-01-15T00:00:00Z' 
+    example: '2023-01-15T00:00:00Z'
   })
 });
 
 export const CreateHetongDto = z.object({
-  
-  contractDate: z.coerce.date().openapi({ 
+  contractDate: z.coerce.date().openapi({
     description: '合同日期',
-    example: '2023-01-15' 
-  }),
-  userId: z.string().max(50).openapi({ 
-    description: '关联用户ID',
-    example: 'U1001' 
+    example: '2023-01-15'
   }),
   clientId: z.coerce.number().int().positive().openapi({
     description: '客户ID',
     example: 2001
   }),
-  projectId: z.string().max(50).nullable().optional().openapi({ 
+  projectId: z.string().max(50).nullable().optional().openapi({
     description: '项目ID',
-    example: 'P3001' 
+    example: 'P3001'
   }),
-  amount: z.coerce.number().multipleOf(0.01).openapi({ 
+  amount: z.coerce.number().multipleOf(0.01).openapi({
     description: '合同金额',
-    example: 150000.00 
+    example: 150000.00
   }),
-  type: z.string().max(50).openapi({ 
+  type: z.string().max(50).openapi({
     description: '合同类型',
-    example: '服务合同' 
+    example: '服务合同'
   }),
-  status: z.string().max(50).openapi({ 
+  status: z.string().max(50).openapi({
     description: '合同状态:如生效中、已结束等',
-    example: '生效中' 
+    example: '生效中'
   }),
-  startDate: z.coerce.date().openapi({ 
+  startDate: z.coerce.date().openapi({
     description: '开始日期',
-    example: '2023-02-01' 
+    example: '2023-02-01'
   }),
-  endDate: z.coerce.date().openapi({ 
+  endDate: z.coerce.date().openapi({
     description: '结束日期',
-    example: '2024-01-31' 
+    example: '2024-01-31'
   }),
-  description: z.string().nullable().optional().openapi({ 
+  description: z.string().nullable().optional().openapi({
     description: '合同描述',
-    example: '年度技术服务合同' 
+    example: '年度技术服务合同'
   }),
-  contractNumber: z.string().max(100).openapi({ 
+  contractNumber: z.string().max(100).openapi({
     description: '合同编号',
-    example: 'HT-2023-0001' 
+    example: 'HT-2023-0001'
   }),
-  currency: z.string().max(20).default('CNY').openapi({ 
+  currency: z.string().max(20).default('CNY').openapi({
     description: '货币类型',
-    example: 'CNY' 
+    example: 'CNY'
   }),
-  exchangeRate: z.coerce.number().multipleOf(0.0001).default(1).openapi({ 
+  exchangeRate: z.coerce.number().multipleOf(0.0001).default(1).openapi({
     description: '汇率',
-    example: 1.0000 
+    example: 1.0000
   }),
-  foreignAmount: z.coerce.number().multipleOf(0.01).nullable().optional().openapi({ 
+  foreignAmount: z.coerce.number().multipleOf(0.01).nullable().optional().openapi({
     description: '外币金额',
-    example: null 
+    example: null
   }),
-  filePath: z.string().max(512).nullable().optional().openapi({ 
+  filePath: z.string().max(512).nullable().optional().openapi({
     description: '合同文件路径',
-    example: '/uploads/contracts/2023/HT-2023-0001.pdf' 
+    example: '/uploads/contracts/2023/HT-2023-0001.pdf'
   })
 });
 
 export const UpdateHetongDto = z.object({
-  contractDate: z.coerce.date().optional().openapi({ 
+  contractDate: z.coerce.date().optional().openapi({
     description: '合同日期',
-    example: '2023-01-15' 
-  }),
-  userId: z.string().max(50).optional().openapi({ 
-    description: '关联用户ID',
-    example: 'U1001' 
+    example: '2023-01-15'
   }),
   clientId: z.coerce.number().int().positive().optional().openapi({
     description: '客户ID',
     example: 2001
   }),
-  projectId: z.string().max(50).nullable().optional().openapi({ 
+  projectId: z.string().max(50).nullable().optional().openapi({
     description: '项目ID',
-    example: 'P3001' 
+    example: 'P3001'
   }),
-  amount: z.coerce.number().multipleOf(0.01).optional().openapi({ 
+  amount: z.coerce.number().multipleOf(0.01).optional().openapi({
     description: '合同金额',
-    example: 150000.00 
+    example: 150000.00
   }),
-  type: z.string().max(50).optional().openapi({ 
+  type: z.string().max(50).optional().openapi({
     description: '合同类型',
-    example: '服务合同' 
+    example: '服务合同'
   }),
-  status: z.string().max(50).optional().openapi({ 
+  status: z.string().max(50).optional().openapi({
     description: '合同状态:如生效中、已结束等',
-    example: '生效中' 
+    example: '生效中'
   }),
-  startDate: z.coerce.date().optional().openapi({ 
+  startDate: z.coerce.date().optional().openapi({
     description: '开始日期',
-    example: '2023-02-01' 
+    example: '2023-02-01'
   }),
-  endDate: z.coerce.date().optional().openapi({ 
+  endDate: z.coerce.date().optional().openapi({
     description: '结束日期',
-    example: '2024-01-31' 
+    example: '2024-01-31'
   }),
-  description: z.string().nullable().optional().openapi({ 
+  description: z.string().nullable().optional().openapi({
     description: '合同描述',
-    example: '年度技术服务合同' 
+    example: '年度技术服务合同'
   }),
-  contractNumber: z.string().max(100).optional().openapi({ 
+  contractNumber: z.string().max(100).optional().openapi({
     description: '合同编号',
-    example: 'HT-2023-0001' 
+    example: 'HT-2023-0001'
   }),
-  currency: z.string().max(20).optional().openapi({ 
+  currency: z.string().max(20).optional().openapi({
     description: '货币类型',
-    example: 'CNY' 
+    example: 'CNY'
   }),
-  exchangeRate: z.coerce.number().multipleOf(0.0001).optional().openapi({ 
+  exchangeRate: z.coerce.number().multipleOf(0.0001).optional().openapi({
     description: '汇率',
-    example: 1.0000 
+    example: 1.0000
   }),
-  foreignAmount: z.coerce.number().multipleOf(0.01).nullable().optional().openapi({ 
+  foreignAmount: z.coerce.number().multipleOf(0.01).nullable().optional().openapi({
     description: '外币金额',
-    example: null 
+    example: null
   }),
-  filePath: z.string().max(512).nullable().optional().openapi({ 
+  filePath: z.string().max(512).nullable().optional().openapi({
     description: '合同文件路径',
-    example: '/uploads/contracts/2023/HT-2023-0001.pdf' 
+    example: '/uploads/contracts/2023/HT-2023-0001.pdf'
   })
 });