Browse Source

feat: 移植残疾人管理模块到 @d8d/allin-disability-module

- 创建残疾人管理模块目录结构
- 完成实体转换:DisabledPerson, DisabledBankCard, DisabledPhoto, DisabledRemark, DisabledVisit
- 集成文件模块:DisabledPhoto实体使用fileId引用FileEntity
- 完成服务层转换:DisabledPersonService继承GenericCrudService,添加身份证号唯一性检查
- 完成路由层转换:Hono路由,包含自定义路由、CRUD路由、聚合路由
- 完成验证系统转换:Zod Schema替代class-validator
- 配置package.json依赖管理
- 编写API集成测试,包含文件集成测试

🤖 Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 5 days ago
parent
commit
bc1d93aad7
23 changed files with 3722 additions and 58 deletions
  1. 80 0
      allin-packages/disability-module/package.json
  2. 79 0
      allin-packages/disability-module/src/entities/disabled-bank-card.entity.ts
  3. 214 0
      allin-packages/disability-module/src/entities/disabled-person.entity.ts
  4. 66 0
      allin-packages/disability-module/src/entities/disabled-photo.entity.ts
  5. 59 0
      allin-packages/disability-module/src/entities/disabled-remark.entity.ts
  6. 84 0
      allin-packages/disability-module/src/entities/disabled-visit.entity.ts
  7. 5 0
      allin-packages/disability-module/src/entities/index.ts
  8. 11 0
      allin-packages/disability-module/src/index.ts
  9. 239 0
      allin-packages/disability-module/src/routes/aggregated.routes.ts
  10. 20 0
      allin-packages/disability-module/src/routes/disabled-person-crud.routes.ts
  11. 520 0
      allin-packages/disability-module/src/routes/disabled-person-custom.routes.ts
  12. 14 0
      allin-packages/disability-module/src/routes/disabled-person.routes.ts
  13. 4 0
      allin-packages/disability-module/src/routes/index.ts
  14. 525 0
      allin-packages/disability-module/src/schemas/disabled-person.schema.ts
  15. 1 0
      allin-packages/disability-module/src/schemas/index.ts
  16. 326 0
      allin-packages/disability-module/src/services/aggregated.service.ts
  17. 322 0
      allin-packages/disability-module/src/services/disabled-person.service.ts
  18. 2 0
      allin-packages/disability-module/src/services/index.ts
  19. 847 0
      allin-packages/disability-module/tests/integration/disability.integration.test.ts
  20. 16 0
      allin-packages/disability-module/tsconfig.json
  21. 21 0
      allin-packages/disability-module/vitest.config.ts
  22. 81 58
      docs/stories/007.004.transplant-disability-management-module.story.md
  23. 186 0
      pnpm-lock.yaml

+ 80 - 0
allin-packages/disability-module/package.json

@@ -0,0 +1,80 @@
+{
+  "name": "@d8d/allin-disability-module",
+  "version": "1.0.0",
+  "description": "残疾人管理模块 - 提供残疾人信息的完整CRUD功能,包含文件模块集成",
+  "type": "module",
+  "main": "src/index.ts",
+  "types": "src/index.ts",
+  "exports": {
+    ".": {
+      "types": "./src/index.ts",
+      "import": "./src/index.ts",
+      "require": "./src/index.ts"
+    },
+    "./services": {
+      "types": "./src/services/index.ts",
+      "import": "./src/services/index.ts",
+      "require": "./src/services/index.ts"
+    },
+    "./schemas": {
+      "types": "./src/schemas/index.ts",
+      "import": "./src/schemas/index.ts",
+      "require": "./src/schemas/index.ts"
+    },
+    "./routes": {
+      "types": "./src/routes/index.ts",
+      "import": "./src/routes/index.ts",
+      "require": "./src/routes/index.ts"
+    },
+    "./entities": {
+      "types": "./src/entities/index.ts",
+      "import": "./src/entities/index.ts",
+      "require": "./src/entities/index.ts"
+    }
+  },
+  "files": [
+    "src"
+  ],
+  "scripts": {
+    "build": "tsc",
+    "dev": "tsc --watch",
+    "test": "vitest run",
+    "test:watch": "vitest",
+    "test:integration": "vitest run tests/integration",
+    "test:coverage": "vitest run --coverage",
+    "lint": "eslint src --ext .ts,.tsx",
+    "typecheck": "tsc --noEmit"
+  },
+  "dependencies": {
+    "@d8d/shared-types": "workspace:*",
+    "@d8d/shared-utils": "workspace:*",
+    "@d8d/shared-crud": "workspace:*",
+    "@d8d/auth-module": "workspace:*",
+    "@d8d/user-module": "workspace:*",
+    "@d8d/file-module": "workspace:*",
+    "@hono/zod-openapi": "^1.0.2",
+    "typeorm": "^0.3.20",
+    "zod": "^4.1.12"
+  },
+  "devDependencies": {
+    "@types/node": "^22.10.2",
+    "typescript": "^5.8.3",
+    "vitest": "^3.2.4",
+    "@d8d/shared-test-util": "workspace:*",
+    "@typescript-eslint/eslint-plugin": "^8.18.1",
+    "@typescript-eslint/parser": "^8.18.1",
+    "eslint": "^9.17.0"
+  },
+  "peerDependencies": {
+    "hono": "^4.8.5"
+  },
+  "keywords": [
+    "disability",
+    "management",
+    "crud",
+    "api",
+    "file-integration"
+  ],
+  "author": "D8D Team",
+  "license": "MIT"
+}

+ 79 - 0
allin-packages/disability-module/src/entities/disabled-bank-card.entity.ts

@@ -0,0 +1,79 @@
+import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, JoinColumn } from 'typeorm';
+import { DisabledPerson } from './disabled-person.entity';
+
+@Entity('disabled_bank_card')
+export class DisabledBankCard {
+  @PrimaryGeneratedColumn({
+    name: 'card_id',
+    type: 'int',
+    comment: '银行卡ID'
+  })
+  id!: number;
+
+  @Column({
+    name: 'person_id',
+    type: 'int',
+    nullable: false,
+    comment: '残疾人ID'
+  })
+  personId!: number;
+
+  @Column({
+    name: 'sub_bank_name',
+    type: 'varchar',
+    length: 100,
+    nullable: false,
+    comment: '发卡支行'
+  })
+  subBankName!: string;
+
+  @Column({
+    name: 'bank_name',
+    type: 'varchar',
+    length: 50,
+    nullable: false,
+    comment: '银行名称'
+  })
+  bankName!: string;
+
+  @Column({
+    name: 'card_number',
+    type: 'varchar',
+    length: 50,
+    nullable: false,
+    comment: '卡号'
+  })
+  cardNumber!: string;
+
+  @Column({
+    name: 'cardholder_name',
+    type: 'varchar',
+    length: 50,
+    nullable: false,
+    comment: '持卡人姓名'
+  })
+  cardholderName!: string;
+
+  @Column({
+    name: 'card_photo_url',
+    type: 'varchar',
+    length: 255,
+    nullable: false,
+    comment: '银行卡照片URL'
+  })
+  cardPhotoUrl!: string;
+
+  @Column({
+    name: 'is_default',
+    type: 'tinyint',
+    nullable: false,
+    default: 0,
+    comment: '是否默认:1-是,0-否'
+  })
+  isDefault!: number;
+
+  // 关系定义
+  @ManyToOne(() => DisabledPerson, person => person.bankCards, { onDelete: 'CASCADE' })
+  @JoinColumn({ name: 'person_id' })
+  person!: DisabledPerson;
+}

+ 214 - 0
allin-packages/disability-module/src/entities/disabled-person.entity.ts

@@ -0,0 +1,214 @@
+import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany, Index } from 'typeorm';
+import { DisabledBankCard } from './disabled-bank-card.entity';
+import { DisabledPhoto } from './disabled-photo.entity';
+import { DisabledRemark } from './disabled-remark.entity';
+import { DisabledVisit } from './disabled-visit.entity';
+
+@Entity('disabled_person')
+export class DisabledPerson {
+  @PrimaryGeneratedColumn({
+    name: 'person_id',
+    type: 'int',
+    comment: '残疾人ID'
+  })
+  id!: number;
+
+  @Column({
+    name: 'name',
+    type: 'varchar',
+    length: 50,
+    nullable: false,
+    comment: '姓名'
+  })
+  name!: string;
+
+  @Column({
+    name: 'gender',
+    type: 'char',
+    length: 1,
+    nullable: false,
+    comment: '性别:男/女'
+  })
+  gender!: string;
+
+  @Column({
+    name: 'id_card',
+    type: 'varchar',
+    length: 20,
+    nullable: false,
+    comment: '身份证号'
+  })
+  @Index('idx_id_card', { unique: true })
+  idCard!: string;
+
+  @Column({
+    name: 'disability_id',
+    type: 'varchar',
+    length: 50,
+    nullable: false,
+    comment: '残疾证号'
+  })
+  @Index('idx_disability_id', { unique: true })
+  disabilityId!: string;
+
+  @Column({
+    name: 'disability_type',
+    type: 'varchar',
+    length: 50,
+    nullable: false,
+    comment: '残疾类型'
+  })
+  disabilityType!: string;
+
+  @Column({
+    name: 'disability_level',
+    type: 'varchar',
+    length: 20,
+    nullable: false,
+    comment: '残疾等级'
+  })
+  disabilityLevel!: string;
+
+  @Column({
+    name: 'id_address',
+    type: 'varchar',
+    length: 200,
+    nullable: false,
+    comment: '身份证地址'
+  })
+  idAddress!: string;
+
+  @Column({
+    name: 'id_valid_date',
+    type: 'date',
+    nullable: true,
+    comment: '身份证有效期'
+  })
+  idValidDate?: Date;
+
+  @Column({
+    name: 'disability_valid_date',
+    type: 'date',
+    nullable: true,
+    comment: '残疾证有效期'
+  })
+  disabilityValidDate?: Date;
+
+  @Column({
+    name: 'phone',
+    type: 'varchar',
+    length: 20,
+    nullable: false,
+    comment: '联系方式'
+  })
+  phone!: string;
+
+  @Column({
+    name: 'can_direct_contact',
+    type: 'tinyint',
+    nullable: false,
+    default: 1,
+    comment: '是否可直接联系:1-是,0-否'
+  })
+  canDirectContact!: number;
+
+  @Column({
+    name: 'is_married',
+    type: 'tinyint',
+    nullable: true,
+    comment: '是否已婚:1-是,0-否'
+  })
+  isMarried?: number;
+
+  @Column({
+    name: 'nation',
+    type: 'varchar',
+    length: 20,
+    nullable: true,
+    comment: '民族'
+  })
+  nation?: string;
+
+  @Column({
+    name: 'province',
+    type: 'varchar',
+    length: 50,
+    nullable: false,
+    comment: '省级'
+  })
+  province!: string;
+
+  @Column({
+    name: 'city',
+    type: 'varchar',
+    length: 50,
+    nullable: false,
+    comment: '市级'
+  })
+  city!: string;
+
+  @Column({
+    name: 'district',
+    type: 'varchar',
+    length: 50,
+    nullable: true,
+    comment: '区县级'
+  })
+  district?: string;
+
+  @Column({
+    name: 'detailed_address',
+    type: 'varchar',
+    length: 200,
+    nullable: true,
+    comment: '详细地址'
+  })
+  detailedAddress?: string;
+
+  @Column({
+    name: 'is_in_black_list',
+    type: 'tinyint',
+    nullable: false,
+    default: 0,
+    comment: '是否在黑名单中:1-是,0-否'
+  })
+  isInBlackList!: number;
+
+  @Column({
+    name: 'job_status',
+    type: 'tinyint',
+    nullable: false,
+    default: 0,
+    comment: '在职状态:0-未在职,1-已在职'
+  })
+  jobStatus!: number;
+
+  @CreateDateColumn({
+    name: 'create_time',
+    type: 'datetime',
+    nullable: false,
+    comment: '创建时间'
+  })
+  createTime!: Date;
+
+  @UpdateDateColumn({
+    name: 'update_time',
+    type: 'datetime',
+    nullable: false,
+    comment: '更新时间'
+  })
+  updateTime!: Date;
+
+  // 关系定义
+  @OneToMany(() => DisabledBankCard, (bankCard: DisabledBankCard) => bankCard.person)
+  bankCards!: DisabledBankCard[];
+
+  @OneToMany(() => DisabledPhoto, (photo: DisabledPhoto) => photo.person)
+  photos!: DisabledPhoto[];
+
+  @OneToMany(() => DisabledRemark, (remark: DisabledRemark) => remark.person)
+  remarks!: DisabledRemark[];
+
+  @OneToMany(() => DisabledVisit, (visit: DisabledVisit) => visit.person)
+  visits!: DisabledVisit[];
+}

+ 66 - 0
allin-packages/disability-module/src/entities/disabled-photo.entity.ts

@@ -0,0 +1,66 @@
+import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, JoinColumn, CreateDateColumn } from 'typeorm';
+import { DisabledPerson } from './disabled-person.entity';
+import { File } from '@d8d/file-module';
+
+@Entity('disabled_photo')
+export class DisabledPhoto {
+  @PrimaryGeneratedColumn({
+    name: 'photo_id',
+    type: 'int',
+    comment: '照片ID'
+  })
+  id!: number;
+
+  @Column({
+    name: 'person_id',
+    type: 'int',
+    nullable: false,
+    comment: '残疾人ID'
+  })
+  personId!: number;
+
+  @Column({
+    name: 'photo_type',
+    type: 'varchar',
+    length: 50,
+    nullable: false,
+    comment: '照片类型'
+  })
+  photoType!: string;
+
+  @Column({
+    name: 'file_id',
+    type: 'int',
+    nullable: false,
+    comment: '文件ID,引用files表'
+  })
+  fileId!: number;
+
+  @CreateDateColumn({
+    name: 'upload_time',
+    type: 'datetime',
+    nullable: false,
+    default: () => 'CURRENT_TIMESTAMP',
+    comment: '上传时间'
+  })
+  uploadTime!: Date;
+
+  @Column({
+    name: 'can_download',
+    type: 'tinyint',
+    nullable: false,
+    default: 1,
+    comment: '是否可下载:1-是,0-否'
+  })
+  canDownload!: number;
+
+  // 关系定义 - 残疾人
+  @ManyToOne(() => DisabledPerson, person => person.photos, { onDelete: 'CASCADE' })
+  @JoinColumn({ name: 'person_id' })
+  person!: DisabledPerson;
+
+  // 关系定义 - 文件
+  @ManyToOne(() => File, { onDelete: 'CASCADE' })
+  @JoinColumn({ name: 'file_id' })
+  file!: File;
+}

+ 59 - 0
allin-packages/disability-module/src/entities/disabled-remark.entity.ts

@@ -0,0 +1,59 @@
+import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, JoinColumn, CreateDateColumn } from 'typeorm';
+import { DisabledPerson } from './disabled-person.entity';
+
+@Entity('disabled_remark')
+export class DisabledRemark {
+  @PrimaryGeneratedColumn({
+    name: 'remark_id',
+    type: 'int',
+    comment: '备注ID'
+  })
+  id!: number;
+
+  @Column({
+    name: 'person_id',
+    type: 'int',
+    nullable: false,
+    comment: '残疾人ID'
+  })
+  personId!: number;
+
+  @Column({
+    name: 'remark_content',
+    type: 'text',
+    nullable: false,
+    comment: '备注内容'
+  })
+  remarkContent!: string;
+
+  @Column({
+    name: 'is_special_needs',
+    type: 'tinyint',
+    nullable: false,
+    default: 0,
+    comment: '是否特殊需求:1-是,0-否'
+  })
+  isSpecialNeeds!: number;
+
+  @CreateDateColumn({
+    name: 'remark_time',
+    type: 'datetime',
+    nullable: false,
+    default: () => 'CURRENT_TIMESTAMP',
+    comment: '备注时间'
+  })
+  remarkTime!: Date;
+
+  @Column({
+    name: 'operator_id',
+    type: 'int',
+    nullable: false,
+    comment: '操作人ID'
+  })
+  operatorId!: number;
+
+  // 关系定义
+  @ManyToOne(() => DisabledPerson, person => person.remarks, { onDelete: 'CASCADE' })
+  @JoinColumn({ name: 'person_id' })
+  person!: DisabledPerson;
+}

+ 84 - 0
allin-packages/disability-module/src/entities/disabled-visit.entity.ts

@@ -0,0 +1,84 @@
+import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, JoinColumn, CreateDateColumn } from 'typeorm';
+import { DisabledPerson } from './disabled-person.entity';
+
+@Entity('disabled_visit')
+export class DisabledVisit {
+  @PrimaryGeneratedColumn({
+    name: 'visit_id',
+    type: 'int',
+    comment: '回访ID'
+  })
+  id!: number;
+
+  @Column({
+    name: 'person_id',
+    type: 'int',
+    nullable: false,
+    comment: '残疾人ID'
+  })
+  personId!: number;
+
+  @Column({
+    name: 'visit_date',
+    type: 'date',
+    nullable: false,
+    comment: '回访日期'
+  })
+  visitDate!: Date;
+
+  @Column({
+    name: 'visit_type',
+    type: 'varchar',
+    length: 50,
+    nullable: false,
+    comment: '回访类型'
+  })
+  visitType!: string;
+
+  @Column({
+    name: 'visit_content',
+    type: 'text',
+    nullable: false,
+    comment: '回访内容'
+  })
+  visitContent!: string;
+
+  @Column({
+    name: 'visit_result',
+    type: 'varchar',
+    length: 50,
+    nullable: true,
+    comment: '回访结果'
+  })
+  visitResult?: string;
+
+  @Column({
+    name: 'next_visit_date',
+    type: 'date',
+    nullable: true,
+    comment: '下次回访日期'
+  })
+  nextVisitDate?: Date;
+
+  @Column({
+    name: 'visitor_id',
+    type: 'int',
+    nullable: false,
+    comment: '回访人ID'
+  })
+  visitorId!: number;
+
+  @CreateDateColumn({
+    name: 'visit_time',
+    type: 'datetime',
+    nullable: false,
+    default: () => 'CURRENT_TIMESTAMP',
+    comment: '记录时间'
+  })
+  visitTime!: Date;
+
+  // 关系定义
+  @ManyToOne(() => DisabledPerson, person => person.visits, { onDelete: 'CASCADE' })
+  @JoinColumn({ name: 'person_id' })
+  person!: DisabledPerson;
+}

+ 5 - 0
allin-packages/disability-module/src/entities/index.ts

@@ -0,0 +1,5 @@
+export { DisabledPerson } from './disabled-person.entity';
+export { DisabledBankCard } from './disabled-bank-card.entity';
+export { DisabledPhoto } from './disabled-photo.entity';
+export { DisabledRemark } from './disabled-remark.entity';
+export { DisabledVisit } from './disabled-visit.entity';

+ 11 - 0
allin-packages/disability-module/src/index.ts

@@ -0,0 +1,11 @@
+// 导出实体
+export { DisabledPerson, DisabledBankCard, DisabledPhoto, DisabledRemark, DisabledVisit } from './entities';
+
+// 导出服务
+export { DisabledPersonService, AggregatedService } from './services';
+
+// 导出路由
+export { disabledPersonRoutes } from './routes';
+
+// 导出Schema
+export * from './schemas';

+ 239 - 0
allin-packages/disability-module/src/routes/aggregated.routes.ts

@@ -0,0 +1,239 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from '@hono/zod-openapi';
+import { AppDataSource, ErrorSchema, parseWithAwait } from '@d8d/shared-utils';
+import { authMiddleware } from '@d8d/auth-module';
+import { AuthContext } from '@d8d/shared-types';
+import { AggregatedService } from '../services/aggregated.service';
+import {
+  CreateAggregatedDisabledPersonSchema,
+  AggregatedDisabledPersonSchema
+} from '../schemas/disabled-person.schema';
+
+// 创建聚合残疾人信息路由(包含所有关联数据)
+const createAggregatedDisabledPersonRoute = createRoute({
+  method: 'post',
+  path: '/createAggregatedDisabledPerson',
+  middleware: [authMiddleware],
+  request: {
+    body: {
+      content: {
+        'application/json': { schema: CreateAggregatedDisabledPersonSchema }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '聚合残疾人信息创建成功',
+      content: {
+        'application/json': { schema: AggregatedDisabledPersonSchema }
+      }
+    },
+    400: {
+      description: '参数错误或身份证号已存在',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    401: {
+      description: '认证失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '创建聚合残疾人信息失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+// 获取聚合残疾人信息路由(包含所有关联数据)
+const getAggregatedDisabledPersonRoute = createRoute({
+  method: 'get',
+  path: '/getAggregatedDisabledPerson/{id}',
+  middleware: [authMiddleware],
+  request: {
+    params: z.object({
+      id: z.coerce.number().int().positive().openapi({
+        param: { name: 'id', in: 'path' },
+        example: 1,
+        description: '残疾人ID'
+      })
+    })
+  },
+  responses: {
+    200: {
+      description: '获取聚合残疾人信息成功',
+      content: {
+        'application/json': { schema: AggregatedDisabledPersonSchema.nullable() }
+      }
+    },
+    401: {
+      description: '认证失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '获取聚合残疾人信息失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+// 更新聚合残疾人信息路由(包含所有关联数据)
+const updateAggregatedDisabledPersonRoute = createRoute({
+  method: 'put',
+  path: '/updateAggregatedDisabledPerson/{id}',
+  middleware: [authMiddleware],
+  request: {
+    params: z.object({
+      id: z.coerce.number().int().positive().openapi({
+        param: { name: 'id', in: 'path' },
+        example: 1,
+        description: '残疾人ID'
+      })
+    }),
+    body: {
+      content: {
+        'application/json': { schema: CreateAggregatedDisabledPersonSchema }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '聚合残疾人信息更新成功',
+      content: {
+        'application/json': { schema: AggregatedDisabledPersonSchema }
+      }
+    },
+    400: {
+      description: '参数错误或身份证号已存在',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    401: {
+      description: '认证失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    404: {
+      description: '残疾人不存在',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '更新聚合残疾人信息失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+const app = new OpenAPIHono<AuthContext>()
+  // 创建聚合残疾人信息
+  .openapi(createAggregatedDisabledPersonRoute, async (c) => {
+    try {
+      const data = c.req.valid('json');
+      const aggregatedService = new AggregatedService(AppDataSource);
+
+      const result = await aggregatedService.createDisabledPersonWithAllData(data);
+
+      return c.json(await parseWithAwait(AggregatedDisabledPersonSchema, result), 200);
+    } catch (error) {
+      if (error instanceof z.ZodError) {
+        return c.json({
+          code: 400,
+          message: '参数错误',
+          errors: error.issues
+        }, 400);
+      }
+
+      // 处理身份证号重复错误
+      if (error instanceof Error && error.message.includes('身份证号已存在')) {
+        return c.json({
+          code: 400,
+          message: '身份证号已存在'
+        }, 400);
+      }
+
+      // 处理文件验证错误
+      if (error instanceof Error && error.message.includes('文件验证失败')) {
+        return c.json({
+          code: 400,
+          message: error.message
+        }, 400);
+      }
+
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '创建聚合残疾人信息失败'
+      }, 500);
+    }
+  })
+  // 获取聚合残疾人信息
+  .openapi(getAggregatedDisabledPersonRoute, async (c) => {
+    try {
+      const { id } = c.req.valid('param');
+      const aggregatedService = new AggregatedService(AppDataSource);
+
+      const result = await aggregatedService.findDisabledPersonWithAllData(id);
+
+      if (!result) {
+        return c.json(null, 200);
+      }
+
+      const validatedResult = await parseWithAwait(AggregatedDisabledPersonSchema, result);
+      return c.json(validatedResult, 200);
+    } catch (error) {
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '获取聚合残疾人信息失败'
+      }, 500);
+    }
+  })
+  // 更新聚合残疾人信息
+  .openapi(updateAggregatedDisabledPersonRoute, async (c) => {
+    try {
+      const { id } = c.req.valid('param');
+      const data = c.req.valid('json');
+      const aggregatedService = new AggregatedService(AppDataSource);
+
+      const result = await aggregatedService.updateDisabledPersonWithAllData(id, data);
+
+      if (!result) {
+        return c.json({ code: 404, message: '残疾人不存在' }, 404);
+      }
+
+      return c.json(await parseWithAwait(AggregatedDisabledPersonSchema, result), 200);
+    } catch (error) {
+      if (error instanceof z.ZodError) {
+        return c.json({
+          code: 400,
+          message: '参数错误',
+          errors: error.issues
+        }, 400);
+      }
+
+      // 处理身份证号重复错误
+      if (error instanceof Error && error.message.includes('身份证号已存在')) {
+        return c.json({
+          code: 400,
+          message: '身份证号已存在'
+        }, 400);
+      }
+
+      // 处理文件验证错误
+      if (error instanceof Error && error.message.includes('文件验证失败')) {
+        return c.json({
+          code: 400,
+          message: error.message
+        }, 400);
+      }
+
+      // 处理残疾人不存在错误
+      if (error instanceof Error && error.message.includes('残疾人不存在')) {
+        return c.json({
+          code: 404,
+          message: '残疾人不存在'
+        }, 404);
+      }
+
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '更新聚合残疾人信息失败'
+      }, 500);
+    }
+  });
+
+export default app;

+ 20 - 0
allin-packages/disability-module/src/routes/disabled-person-crud.routes.ts

@@ -0,0 +1,20 @@
+import { createCrudRoutes } from '@d8d/shared-crud';
+import { authMiddleware } from '@d8d/auth-module';
+import { DisabledPerson } from '../entities/disabled-person.entity';
+import { DisabledPersonSchema, CreateDisabledPersonSchema, UpdateDisabledPersonSchema } from '../schemas/disabled-person.schema';
+
+export const disabledPersonCrudRoutes = createCrudRoutes({
+  entity: DisabledPerson,
+  createSchema: CreateDisabledPersonSchema,
+  updateSchema: UpdateDisabledPersonSchema,
+  getSchema: DisabledPersonSchema,
+  listSchema: DisabledPersonSchema,
+  searchFields: ['name', 'idCard', 'address'],
+  relations: ['bankCards', 'photos', 'remarks', 'visits'],
+  middleware: [authMiddleware],
+  readOnly: true, // 设置为只读,因为创建、更新、删除操作通过自定义路由处理
+  userTracking: {
+    createdByField: 'createdBy',
+    updatedByField: 'updatedBy'
+  }
+});

+ 520 - 0
allin-packages/disability-module/src/routes/disabled-person-custom.routes.ts

@@ -0,0 +1,520 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from '@hono/zod-openapi';
+import { AppDataSource, ErrorSchema, parseWithAwait } from '@d8d/shared-utils';
+import { authMiddleware } from '@d8d/auth-module';
+import { AuthContext } from '@d8d/shared-types';
+import { DisabledPersonService } from '../services/disabled-person.service';
+import {
+  DisabledPersonSchema,
+  CreateDisabledPersonSchema,
+  UpdateDisabledPersonSchema,
+  DeleteDisabledPersonSchema,
+  PaginationQuerySchema,
+  SearchDisabledPersonQuerySchema
+} from '../schemas/disabled-person.schema';
+
+// 创建残疾人路由 - 返回残疾人信息
+const createDisabledPersonRoute = createRoute({
+  method: 'post',
+  path: '/createDisabledPerson',
+  middleware: [authMiddleware],
+  request: {
+    body: {
+      content: {
+        'application/json': { schema: CreateDisabledPersonSchema }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '残疾人创建成功',
+      content: {
+        'application/json': { schema: DisabledPersonSchema }
+      }
+    },
+    400: {
+      description: '参数错误或身份证号已存在',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    401: {
+      description: '认证失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '创建残疾人失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+// 删除残疾人路由 - 返回布尔值
+const deleteDisabledPersonRoute = createRoute({
+  method: 'post',
+  path: '/deleteDisabledPerson',
+  middleware: [authMiddleware],
+  request: {
+    body: {
+      content: {
+        'application/json': { schema: DeleteDisabledPersonSchema }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '残疾人删除成功',
+      content: {
+        'application/json': {
+          schema: z.object({
+            success: z.boolean().openapi({ description: '是否成功' })
+          })
+        }
+      }
+    },
+    400: {
+      description: '参数错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    401: {
+      description: '认证失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '删除残疾人失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+// 更新残疾人路由 - 返回残疾人信息
+const updateDisabledPersonRoute = createRoute({
+  method: 'post',
+  path: '/updateDisabledPerson',
+  middleware: [authMiddleware],
+  request: {
+    body: {
+      content: {
+        'application/json': { schema: UpdateDisabledPersonSchema }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '残疾人更新成功',
+      content: {
+        'application/json': { schema: DisabledPersonSchema }
+      }
+    },
+    400: {
+      description: '参数错误或身份证号已存在',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    401: {
+      description: '认证失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    404: {
+      description: '残疾人不存在',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '更新残疾人失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+// 获取所有残疾人路由 - 分页查询
+const getAllDisabledPersonsRoute = createRoute({
+  method: 'get',
+  path: '/getAllDisabledPersons',
+  middleware: [authMiddleware],
+  request: {
+    query: PaginationQuerySchema
+  },
+  responses: {
+    200: {
+      description: '获取残疾人列表成功',
+      content: {
+        'application/json': {
+          schema: z.object({
+            data: z.array(DisabledPersonSchema).openapi({ description: '残疾人列表' }),
+            total: z.number().int().openapi({ description: '总记录数' })
+          })
+        }
+      }
+    },
+    401: {
+      description: '认证失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '获取残疾人列表失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+// 搜索残疾人路由 - 按姓名、身份证号模糊搜索
+const searchDisabledPersonsRoute = createRoute({
+  method: 'get',
+  path: '/searchDisabledPersons',
+  middleware: [authMiddleware],
+  request: {
+    query: SearchDisabledPersonQuerySchema
+  },
+  responses: {
+    200: {
+      description: '搜索残疾人成功',
+      content: {
+        'application/json': {
+          schema: z.object({
+            data: z.array(DisabledPersonSchema).openapi({ description: '残疾人列表' }),
+            total: z.number().int().openapi({ description: '总记录数' })
+          })
+        }
+      }
+    },
+    400: {
+      description: '搜索关键词不能为空',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    401: {
+      description: '认证失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '搜索残疾人失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+// 获取单个残疾人路由
+const getDisabledPersonRoute = createRoute({
+  method: 'get',
+  path: '/getDisabledPerson/{id}',
+  middleware: [authMiddleware],
+  request: {
+    params: z.object({
+      id: z.coerce.number().int().positive().openapi({
+        param: { name: 'id', in: 'path' },
+        example: 1,
+        description: '残疾人ID'
+      })
+    })
+  },
+  responses: {
+    200: {
+      description: '获取残疾人详情成功',
+      content: {
+        'application/json': { schema: DisabledPersonSchema.nullable() }
+      }
+    },
+    401: {
+      description: '认证失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '获取残疾人详情失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+// 根据身份证号查询残疾人路由
+const findByIdCardRoute = createRoute({
+  method: 'get',
+  path: '/findByIdCard/{idCard}',
+  middleware: [authMiddleware],
+  request: {
+    params: z.object({
+      idCard: z.string().openapi({
+        param: { name: 'idCard', in: 'path' },
+        example: '110101199001011234',
+        description: '身份证号码'
+      })
+    })
+  },
+  responses: {
+    200: {
+      description: '根据身份证号查询成功',
+      content: {
+        'application/json': { schema: DisabledPersonSchema.nullable() }
+      }
+    },
+    400: {
+      description: '身份证号格式错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    401: {
+      description: '认证失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '查询失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+// 批量创建残疾人路由
+const batchCreateDisabledPersonsRoute = createRoute({
+  method: 'post',
+  path: '/batchCreateDisabledPersons',
+  middleware: [authMiddleware],
+  request: {
+    body: {
+      content: {
+        'application/json': {
+          schema: z.object({
+            persons: z.array(CreateDisabledPersonSchema).openapi({
+              description: '残疾人信息列表',
+              example: [
+                {
+                  name: '张三',
+                  idCard: '110101199001011234',
+                  gender: 1,
+                  birthDate: '1990-01-01',
+                  disabilityType: '视力残疾',
+                  disabilityLevel: '一级',
+                  address: '北京市东城区'
+                }
+              ]
+            })
+          })
+        }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '批量创建成功',
+      content: {
+        'application/json': {
+          schema: z.object({
+            success: z.boolean().openapi({ description: '是否成功' }),
+            createdCount: z.number().int().openapi({ description: '成功创建数量' }),
+            failedItems: z.array(z.object({
+              index: z.number().int().openapi({ description: '失败项索引' }),
+              error: z.string().openapi({ description: '错误信息' })
+            })).openapi({ description: '失败项列表' })
+          })
+        }
+      }
+    },
+    400: {
+      description: '参数错误或身份证号重复',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    401: {
+      description: '认证失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '批量创建失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+const app = new OpenAPIHono<AuthContext>()
+  // 创建残疾人
+  .openapi(createDisabledPersonRoute, async (c) => {
+    try {
+      const data = c.req.valid('json');
+      const disabledPersonService = new DisabledPersonService(AppDataSource);
+
+      const result = await disabledPersonService.create(data);
+
+      return c.json(await parseWithAwait(DisabledPersonSchema, result), 200);
+    } catch (error) {
+      if (error instanceof z.ZodError) {
+        return c.json({
+          code: 400,
+          message: '参数错误',
+          errors: error.issues
+        }, 400);
+      }
+
+      // 处理身份证号重复错误
+      if (error instanceof Error && error.message.includes('身份证号已存在')) {
+        return c.json({
+          code: 400,
+          message: '身份证号已存在'
+        }, 400);
+      }
+
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '创建残疾人失败'
+      }, 500);
+    }
+  })
+  // 删除残疾人
+  .openapi(deleteDisabledPersonRoute, async (c) => {
+    try {
+      const { id } = c.req.valid('json');
+      const disabledPersonService = new DisabledPersonService(AppDataSource);
+
+      const success = await disabledPersonService.delete(id);
+
+      return c.json({ success }, 200);
+    } catch (error) {
+      if (error instanceof z.ZodError) {
+        return c.json({
+          code: 400,
+          message: '参数错误',
+          errors: error.issues
+        }, 400);
+      }
+
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '删除残疾人失败'
+      }, 500);
+    }
+  })
+  // 更新残疾人
+  .openapi(updateDisabledPersonRoute, async (c) => {
+    try {
+      const data = c.req.valid('json');
+      const disabledPersonService = new DisabledPersonService(AppDataSource);
+
+      const result = await disabledPersonService.update(data.id, data);
+
+      if (!result) {
+        return c.json({ code: 404, message: '残疾人不存在' }, 404);
+      }
+
+      return c.json(await parseWithAwait(DisabledPersonSchema, result), 200);
+    } catch (error) {
+      if (error instanceof z.ZodError) {
+        return c.json({
+          code: 400,
+          message: '参数错误',
+          errors: error.issues
+        }, 400);
+      }
+
+      // 处理身份证号重复错误
+      if (error instanceof Error && error.message.includes('身份证号已存在')) {
+        return c.json({
+          code: 400,
+          message: '身份证号已存在'
+        }, 400);
+      }
+
+      // 处理残疾人不存在错误
+      if (error instanceof Error && error.message.includes('残疾人不存在')) {
+        return c.json({
+          code: 404,
+          message: '残疾人不存在'
+        }, 404);
+      }
+
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '更新残疾人失败'
+      }, 500);
+    }
+  })
+  // 获取所有残疾人
+  .openapi(getAllDisabledPersonsRoute, async (c) => {
+    try {
+      const { skip, take } = c.req.valid('query');
+      const disabledPersonService = new DisabledPersonService(AppDataSource);
+
+      const result = await disabledPersonService.findAll(skip, take);
+
+      return c.json(result, 200);
+    } catch (error) {
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '获取残疾人列表失败'
+      }, 500);
+    }
+  })
+  // 搜索残疾人
+  .openapi(searchDisabledPersonsRoute, async (c) => {
+    try {
+      const { keyword, skip, take } = c.req.valid('query');
+      const disabledPersonService = new DisabledPersonService(AppDataSource);
+
+      const result = await disabledPersonService.searchByNameOrIdCard(keyword, skip, take);
+
+      return c.json(result, 200);
+    } catch (error) {
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '搜索残疾人失败'
+      }, 500);
+    }
+  })
+  // 获取单个残疾人
+  .openapi(getDisabledPersonRoute, async (c) => {
+    try {
+      const { id } = c.req.valid('param');
+      const disabledPersonService = new DisabledPersonService(AppDataSource);
+
+      const result = await disabledPersonService.findOne(id);
+
+      if (!result) {
+        return c.json(null, 200);
+      }
+
+      const validatedResult = await parseWithAwait(DisabledPersonSchema, result);
+      return c.json(validatedResult, 200);
+    } catch (error) {
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '获取残疾人详情失败'
+      }, 500);
+    }
+  })
+  // 根据身份证号查询
+  .openapi(findByIdCardRoute, async (c) => {
+    try {
+      const { idCard } = c.req.valid('param');
+      const disabledPersonService = new DisabledPersonService(AppDataSource);
+
+      const result = await disabledPersonService.findByIdCard(idCard);
+
+      if (!result) {
+        return c.json(null, 200);
+      }
+
+      const validatedResult = await parseWithAwait(DisabledPersonSchema, result);
+      return c.json(validatedResult, 200);
+    } catch (error) {
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '根据身份证号查询失败'
+      }, 500);
+    }
+  })
+  // 批量创建残疾人
+  .openapi(batchCreateDisabledPersonsRoute, async (c) => {
+    try {
+      const { persons } = c.req.valid('json');
+      const disabledPersonService = new DisabledPersonService(AppDataSource);
+
+      const result = await disabledPersonService.batchCreate(persons);
+
+      return c.json(result, 200);
+    } catch (error) {
+      if (error instanceof z.ZodError) {
+        return c.json({
+          code: 400,
+          message: '参数错误',
+          errors: error.issues
+        }, 400);
+      }
+
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '批量创建残疾人失败'
+      }, 500);
+    }
+  });
+
+export default app;

+ 14 - 0
allin-packages/disability-module/src/routes/disabled-person.routes.ts

@@ -0,0 +1,14 @@
+import { OpenAPIHono } from '@hono/zod-openapi';
+import { AuthContext } from '@d8d/shared-types';
+import disabledPersonCustomRoutes from './disabled-person-custom.routes';
+import { disabledPersonCrudRoutes } from './disabled-person-crud.routes';
+import aggregatedRoutes from './aggregated.routes';
+
+// 创建路由实例 - 聚合自定义路由、CRUD路由和聚合路由
+const disabledPersonRoutes = new OpenAPIHono<AuthContext>()
+  .route('/', disabledPersonCustomRoutes)
+  .route('/', disabledPersonCrudRoutes)
+  .route('/', aggregatedRoutes);
+
+export { disabledPersonRoutes };
+export default disabledPersonRoutes;

+ 4 - 0
allin-packages/disability-module/src/routes/index.ts

@@ -0,0 +1,4 @@
+export { default as disabledPersonCustomRoutes } from './disabled-person-custom.routes';
+export { disabledPersonCrudRoutes } from './disabled-person-crud.routes';
+export { default as aggregatedRoutes } from './aggregated.routes';
+export { disabledPersonRoutes, default as default } from './disabled-person.routes';

+ 525 - 0
allin-packages/disability-module/src/schemas/disabled-person.schema.ts

@@ -0,0 +1,525 @@
+import { z } from '@hono/zod-openapi';
+
+// 基础字段定义
+const BaseDisabledPersonSchema = z.object({
+  name: z.string().min(1).max(50).openapi({
+    description: '姓名',
+    example: '张三'
+  }),
+  gender: z.string().length(1).openapi({
+    description: '性别:男/女',
+    example: '男'
+  }),
+  idCard: z.string().min(1).max(20).openapi({
+    description: '身份证号',
+    example: '110101199001011234'
+  }),
+  disabilityId: z.string().min(1).max(50).openapi({
+    description: '残疾证号',
+    example: 'CJZ20240001'
+  }),
+  disabilityType: z.string().min(1).max(50).openapi({
+    description: '残疾类型',
+    example: '视力残疾'
+  }),
+  disabilityLevel: z.string().min(1).max(20).openapi({
+    description: '残疾等级',
+    example: '一级'
+  }),
+  idAddress: z.string().min(1).max(200).openapi({
+    description: '身份证地址',
+    example: '北京市东城区'
+  }),
+  phone: z.string().min(1).max(20).openapi({
+    description: '联系方式',
+    example: '13800138000'
+  }),
+  province: z.string().min(1).max(50).openapi({
+    description: '省级',
+    example: '北京市'
+  }),
+  city: z.string().min(1).max(50).openapi({
+    description: '市级',
+    example: '北京市'
+  })
+});
+
+// 残疾人实体Schema
+export const DisabledPersonSchema = BaseDisabledPersonSchema.extend({
+  id: z.number().int().positive().openapi({
+    description: '残疾人ID',
+    example: 1
+  }),
+  idValidDate: z.coerce.date().nullable().optional().openapi({
+    description: '身份证有效期',
+    example: '2030-12-31T00:00:00Z'
+  }),
+  disabilityValidDate: z.coerce.date().nullable().optional().openapi({
+    description: '残疾证有效期',
+    example: '2030-12-31T00:00:00Z'
+  }),
+  canDirectContact: z.number().int().min(0).max(1).default(0).openapi({
+    description: '是否可直接联系:1-是,0-否',
+    example: 1
+  }),
+  isMarried: z.number().int().min(0).max(1).default(0).openapi({
+    description: '是否已婚:1-是,0-否',
+    example: 1
+  }),
+  nation: z.string().max(20).nullable().optional().openapi({
+    description: '民族',
+    example: '汉族'
+  }),
+  birthDate: z.coerce.date().nullable().optional().openapi({
+    description: '出生日期',
+    example: '1990-01-01T00:00:00Z'
+  }),
+  age: z.number().int().min(0).max(150).nullable().optional().openapi({
+    description: '年龄',
+    example: 34
+  }),
+  education: z.string().max(50).nullable().optional().openapi({
+    description: '学历',
+    example: '本科'
+  }),
+  graduateSchool: z.string().max(100).nullable().optional().openapi({
+    description: '毕业院校',
+    example: '北京大学'
+  }),
+  major: z.string().max(100).nullable().optional().openapi({
+    description: '专业',
+    example: '计算机科学'
+  }),
+  district: z.string().max(50).nullable().optional().openapi({
+    description: '区县级',
+    example: '东城区'
+  }),
+  detailedAddress: z.string().max(200).nullable().optional().openapi({
+    description: '详细地址',
+    example: '东城区某街道某号'
+  }),
+  postalCode: z.string().max(20).nullable().optional().openapi({
+    description: '邮政编码',
+    example: '100010'
+  }),
+  isInBlackList: z.number().int().min(0).max(1).default(0).openapi({
+    description: '是否在黑名单中:1-是,0-否',
+    example: 0
+  }),
+  jobStatus: z.number().int().min(0).max(1).default(0).openapi({
+    description: '在职状态:0-未在职,1-已在职',
+    example: 1
+  }),
+  createTime: z.coerce.date().openapi({
+    description: '创建时间',
+    example: '2024-01-01T00:00:00Z'
+  }),
+  updateTime: z.coerce.date().openapi({
+    description: '更新时间',
+    example: '2024-01-01T00:00:00Z'
+  })
+});
+
+// 创建残疾人DTO
+export const CreateDisabledPersonSchema = BaseDisabledPersonSchema.extend({
+  idValidDate: z.coerce.date().optional().openapi({
+    description: '身份证有效期',
+    example: '2030-12-31T00:00:00Z'
+  }),
+  disabilityValidDate: z.coerce.date().optional().openapi({
+    description: '残疾证有效期',
+    example: '2030-12-31T00:00:00Z'
+  }),
+  canDirectContact: z.number().int().min(0).max(1).default(0).optional().openapi({
+    description: '是否可直接联系:1-是,0-否',
+    example: 1
+  }),
+  isMarried: z.number().int().min(0).max(1).default(0).optional().openapi({
+    description: '是否已婚:1-是,0-否',
+    example: 1
+  }),
+  nation: z.string().max(20).optional().openapi({
+    description: '民族',
+    example: '汉族'
+  }),
+  birthDate: z.coerce.date().optional().openapi({
+    description: '出生日期',
+    example: '1990-01-01T00:00:00Z'
+  }),
+  age: z.number().int().min(0).max(150).optional().openapi({
+    description: '年龄',
+    example: 34
+  }),
+  education: z.string().max(50).optional().openapi({
+    description: '学历',
+    example: '本科'
+  }),
+  graduateSchool: z.string().max(100).optional().openapi({
+    description: '毕业院校',
+    example: '北京大学'
+  }),
+  major: z.string().max(100).optional().openapi({
+    description: '专业',
+    example: '计算机科学'
+  }),
+  district: z.string().max(50).optional().openapi({
+    description: '区县级',
+    example: '东城区'
+  }),
+  detailedAddress: z.string().max(200).optional().openapi({
+    description: '详细地址',
+    example: '东城区某街道某号'
+  }),
+  postalCode: z.string().max(20).optional().openapi({
+    description: '邮政编码',
+    example: '100010'
+  }),
+  isInBlackList: z.number().int().min(0).max(1).default(0).optional().openapi({
+    description: '是否在黑名单中:1-是,0-否',
+    example: 0
+  }),
+  jobStatus: z.number().int().min(0).max(1).default(0).optional().openapi({
+    description: '在职状态:0-未在职,1-已在职',
+    example: 1
+  })
+});
+
+// 更新残疾人DTO
+export const UpdateDisabledPersonSchema = z.object({
+  id: z.number().int().positive().openapi({
+    description: '残疾人ID',
+    example: 1
+  }),
+  name: z.string().min(1).max(50).optional().openapi({
+    description: '姓名',
+    example: '张三'
+  }),
+  gender: z.string().length(1).optional().openapi({
+    description: '性别:男/女',
+    example: '男'
+  }),
+  idCard: z.string().min(1).max(20).optional().openapi({
+    description: '身份证号',
+    example: '110101199001011234'
+  }),
+  disabilityId: z.string().min(1).max(50).optional().openapi({
+    description: '残疾证号',
+    example: 'CJZ20240001'
+  }),
+  disabilityType: z.string().min(1).max(50).optional().openapi({
+    description: '残疾类型',
+    example: '视力残疾'
+  }),
+  disabilityLevel: z.string().min(1).max(20).optional().openapi({
+    description: '残疾等级',
+    example: '一级'
+  }),
+  idAddress: z.string().min(1).max(200).optional().openapi({
+    description: '身份证地址',
+    example: '北京市东城区'
+  }),
+  idValidDate: z.coerce.date().optional().openapi({
+    description: '身份证有效期',
+    example: '2030-12-31T00:00:00Z'
+  }),
+  disabilityValidDate: z.coerce.date().optional().openapi({
+    description: '残疾证有效期',
+    example: '2030-12-31T00:00:00Z'
+  }),
+  phone: z.string().min(1).max(20).optional().openapi({
+    description: '联系方式',
+    example: '13800138000'
+  }),
+  canDirectContact: z.number().int().min(0).max(1).optional().openapi({
+    description: '是否可直接联系:1-是,0-否',
+    example: 1
+  }),
+  isMarried: z.number().int().min(0).max(1).optional().openapi({
+    description: '是否已婚:1-是,0-否',
+    example: 1
+  }),
+  nation: z.string().max(20).optional().openapi({
+    description: '民族',
+    example: '汉族'
+  }),
+  birthDate: z.coerce.date().optional().openapi({
+    description: '出生日期',
+    example: '1990-01-01T00:00:00Z'
+  }),
+  age: z.number().int().min(0).max(150).optional().openapi({
+    description: '年龄',
+    example: 34
+  }),
+  education: z.string().max(50).optional().openapi({
+    description: '学历',
+    example: '本科'
+  }),
+  graduateSchool: z.string().max(100).optional().openapi({
+    description: '毕业院校',
+    example: '北京大学'
+  }),
+  major: z.string().max(100).optional().openapi({
+    description: '专业',
+    example: '计算机科学'
+  }),
+  province: z.string().min(1).max(50).optional().openapi({
+    description: '省级',
+    example: '北京市'
+  }),
+  city: z.string().min(1).max(50).optional().openapi({
+    description: '市级',
+    example: '北京市'
+  }),
+  district: z.string().max(50).optional().openapi({
+    description: '区县级',
+    example: '东城区'
+  }),
+  detailedAddress: z.string().max(200).optional().openapi({
+    description: '详细地址',
+    example: '东城区某街道某号'
+  }),
+  postalCode: z.string().max(20).optional().openapi({
+    description: '邮政编码',
+    example: '100010'
+  }),
+  isInBlackList: z.number().int().min(0).max(1).optional().openapi({
+    description: '是否在黑名单中:1-是,0-否',
+    example: 0
+  }),
+  jobStatus: z.number().int().min(0).max(1).optional().openapi({
+    description: '在职状态:0-未在职,1-已在职',
+    example: 1
+  })
+});
+
+// 删除残疾人DTO
+export const DeleteDisabledPersonSchema = z.object({
+  id: z.number().int().positive().openapi({
+    description: '残疾人ID',
+    example: 1
+  })
+});
+
+// 分页查询参数Schema
+export const PaginationQuerySchema = z.object({
+  skip: z.coerce.number().int().min(0).default(0).optional().openapi({
+    description: '跳过记录数',
+    example: 0
+  }),
+  take: z.coerce.number().int().min(1).max(100).default(10).optional().openapi({
+    description: '获取记录数',
+    example: 10
+  })
+});
+
+// 搜索残疾人参数Schema
+export const SearchDisabledPersonQuerySchema = PaginationQuerySchema.extend({
+  keyword: z.string().min(1).openapi({
+    description: '搜索关键词(姓名或身份证号)',
+    example: '张三'
+  })
+});
+
+// 银行卡实体Schema
+export const DisabledBankCardSchema = z.object({
+  id: z.number().int().positive().openapi({
+    description: '银行卡ID',
+    example: 1
+  }),
+  personId: z.number().int().positive().openapi({
+    description: '残疾人ID',
+    example: 1
+  }),
+  subBankName: z.string().max(100).openapi({
+    description: '发卡支行',
+    example: '中国工商银行北京分行'
+  }),
+  bankName: z.string().max(50).openapi({
+    description: '银行名称',
+    example: '中国工商银行'
+  }),
+  cardNumber: z.string().max(50).openapi({
+    description: '卡号',
+    example: '6222021234567890123'
+  }),
+  cardholderName: z.string().max(50).openapi({
+    description: '持卡人姓名',
+    example: '张三'
+  }),
+  fileId: z.number().int().positive().openapi({
+    description: '银行卡照片文件ID',
+    example: 1
+  }),
+  isDefault: z.number().int().min(0).max(1).default(0).openapi({
+    description: '是否默认:1-是,0-否',
+    example: 1
+  }),
+  createTime: z.coerce.date().openapi({
+    description: '创建时间',
+    example: '2024-01-01T00:00:00Z'
+  }),
+  updateTime: z.coerce.date().openapi({
+    description: '更新时间',
+    example: '2024-01-01T00:00:00Z'
+  })
+});
+
+// 照片实体Schema
+export const DisabledPhotoSchema = z.object({
+  id: z.number().int().positive().openapi({
+    description: '照片ID',
+    example: 1
+  }),
+  personId: z.number().int().positive().openapi({
+    description: '残疾人ID',
+    example: 1
+  }),
+  photoType: z.string().max(50).openapi({
+    description: '照片类型',
+    example: '身份证照片'
+  }),
+  fileId: z.number().int().positive().openapi({
+    description: '照片文件ID',
+    example: 1
+  }),
+  canDownload: z.number().int().min(0).max(1).default(0).openapi({
+    description: '是否可下载:1-是,0-否',
+    example: 1
+  }),
+  createTime: z.coerce.date().openapi({
+    description: '创建时间',
+    example: '2024-01-01T00:00:00Z'
+  }),
+  updateTime: z.coerce.date().openapi({
+    description: '更新时间',
+    example: '2024-01-01T00:00:00Z'
+  })
+});
+
+// 备注实体Schema
+export const DisabledRemarkSchema = z.object({
+  id: z.number().int().positive().openapi({
+    description: '备注ID',
+    example: 1
+  }),
+  personId: z.number().int().positive().openapi({
+    description: '残疾人ID',
+    example: 1
+  }),
+  remarkContent: z.string().openapi({
+    description: '备注内容',
+    example: '该残疾人有特殊需求'
+  }),
+  isSpecialNeeds: z.number().int().min(0).max(1).default(0).openapi({
+    description: '是否特殊需求:1-是,0-否',
+    example: 1
+  }),
+  operatorId: z.number().int().positive().openapi({
+    description: '操作人ID',
+    example: 1
+  }),
+  createTime: z.coerce.date().openapi({
+    description: '创建时间',
+    example: '2024-01-01T00:00:00Z'
+  }),
+  updateTime: z.coerce.date().openapi({
+    description: '更新时间',
+    example: '2024-01-01T00:00:00Z'
+  })
+});
+
+// 回访实体Schema
+export const DisabledVisitSchema = z.object({
+  id: z.number().int().positive().openapi({
+    description: '回访ID',
+    example: 1
+  }),
+  personId: z.number().int().positive().openapi({
+    description: '残疾人ID',
+    example: 1
+  }),
+  visitDate: z.coerce.date().openapi({
+    description: '回访日期',
+    example: '2024-01-01T00:00:00Z'
+  }),
+  visitType: z.string().max(50).openapi({
+    description: '回访类型',
+    example: '电话回访'
+  }),
+  visitContent: z.string().openapi({
+    description: '回访内容',
+    example: '了解残疾人近期情况'
+  }),
+  visitResult: z.string().max(50).nullable().optional().openapi({
+    description: '回访结果',
+    example: '良好'
+  }),
+  nextVisitDate: z.coerce.date().nullable().optional().openapi({
+    description: '下次回访日期',
+    example: '2024-02-01T00:00:00Z'
+  }),
+  visitorId: z.number().int().positive().openapi({
+    description: '回访人ID',
+    example: 1
+  }),
+  createTime: z.coerce.date().openapi({
+    description: '创建时间',
+    example: '2024-01-01T00:00:00Z'
+  }),
+  updateTime: z.coerce.date().openapi({
+    description: '更新时间',
+    example: '2024-01-01T00:00:00Z'
+  })
+});
+
+// 创建聚合残疾人信息Schema
+export const CreateAggregatedDisabledPersonSchema = z.object({
+  personInfo: CreateDisabledPersonSchema.openapi({
+    description: '残疾人基本信息'
+  }),
+  bankCards: z.array(DisabledBankCardSchema.omit({ id: true, personId: true, createTime: true, updateTime: true })).optional().openapi({
+    description: '银行卡信息列表'
+  }),
+  photos: z.array(DisabledPhotoSchema.omit({ id: true, personId: true, createTime: true, updateTime: true })).optional().openapi({
+    description: '照片信息列表'
+  }),
+  remarks: z.array(DisabledRemarkSchema.omit({ id: true, personId: true, createTime: true, updateTime: true })).optional().openapi({
+    description: '备注信息列表'
+  }),
+  visits: z.array(DisabledVisitSchema.omit({ id: true, personId: true, createTime: true, updateTime: true })).optional().openapi({
+    description: '回访记录列表'
+  })
+});
+
+// 聚合残疾人信息Schema
+export const AggregatedDisabledPersonSchema = z.object({
+  personInfo: DisabledPersonSchema.openapi({
+    description: '残疾人基本信息'
+  }),
+  bankCards: z.array(DisabledBankCardSchema).openapi({
+    description: '银行卡信息列表'
+  }),
+  photos: z.array(DisabledPhotoSchema).openapi({
+    description: '照片信息列表'
+  }),
+  remarks: z.array(DisabledRemarkSchema).openapi({
+    description: '备注信息列表'
+  }),
+  visits: z.array(DisabledVisitSchema).openapi({
+    description: '回访记录列表'
+  })
+});
+
+// 类型定义
+export type DisabledPerson = z.infer<typeof DisabledPersonSchema>;
+export type CreateDisabledPersonDto = z.infer<typeof CreateDisabledPersonSchema>;
+export type UpdateDisabledPersonDto = z.infer<typeof UpdateDisabledPersonSchema>;
+export type DeleteDisabledPersonDto = z.infer<typeof DeleteDisabledPersonSchema>;
+export type PaginationQuery = z.infer<typeof PaginationQuerySchema>;
+export type SearchDisabledPersonQuery = z.infer<typeof SearchDisabledPersonQuerySchema>;
+export type DisabledBankCard = z.infer<typeof DisabledBankCardSchema>;
+export type DisabledPhoto = z.infer<typeof DisabledPhotoSchema>;
+export type DisabledRemark = z.infer<typeof DisabledRemarkSchema>;
+export type DisabledVisit = z.infer<typeof DisabledVisitSchema>;
+export type CreateAggregatedDisabledPersonDto = z.infer<typeof CreateAggregatedDisabledPersonSchema>;
+export type AggregatedDisabledPerson = z.infer<typeof AggregatedDisabledPersonSchema>;

+ 1 - 0
allin-packages/disability-module/src/schemas/index.ts

@@ -0,0 +1 @@
+export * from './disabled-person.schema';

+ 326 - 0
allin-packages/disability-module/src/services/aggregated.service.ts

@@ -0,0 +1,326 @@
+import { DataSource, Repository } from 'typeorm';
+import { DisabledPerson } from '../entities/disabled-person.entity';
+import { DisabledBankCard } from '../entities/disabled-bank-card.entity';
+import { DisabledPhoto } from '../entities/disabled-photo.entity';
+import { DisabledRemark } from '../entities/disabled-remark.entity';
+import { DisabledVisit } from '../entities/disabled-visit.entity';
+import { File } from '@d8d/file-module';
+import { DisabledPersonService } from './disabled-person.service';
+
+export class AggregatedService {
+  private readonly personRepository: Repository<DisabledPerson>;
+  private readonly bankCardRepository: Repository<DisabledBankCard>;
+  private readonly photoRepository: Repository<DisabledPhoto>;
+  private readonly remarkRepository: Repository<DisabledRemark>;
+  private readonly visitRepository: Repository<DisabledVisit>;
+  private readonly fileRepository: Repository<File>;
+  private readonly disabledPersonService: DisabledPersonService;
+
+  constructor(dataSource: DataSource) {
+    this.personRepository = dataSource.getRepository(DisabledPerson);
+    this.bankCardRepository = dataSource.getRepository(DisabledBankCard);
+    this.photoRepository = dataSource.getRepository(DisabledPhoto);
+    this.remarkRepository = dataSource.getRepository(DisabledRemark);
+    this.visitRepository = dataSource.getRepository(DisabledVisit);
+    this.fileRepository = dataSource.getRepository(File);
+    this.disabledPersonService = new DisabledPersonService(dataSource);
+  }
+
+  /**
+   * 聚合创建残疾人所有信息
+   */
+  async createDisabledPersonWithAllData(data: {
+    personInfo: Partial<DisabledPerson>;
+    bankCards?: Partial<DisabledBankCard>[];
+    photos?: Partial<DisabledPhoto>[];
+    remarks?: Partial<DisabledRemark>[];
+    visits?: Partial<DisabledVisit>[];
+  }): Promise<{
+    personInfo: DisabledPerson;
+    bankCards: DisabledBankCard[];
+    photos: DisabledPhoto[];
+    remarks: DisabledRemark[];
+    visits: DisabledVisit[];
+    message: string;
+    success: boolean;
+  }> {
+    const { personInfo, bankCards = [], photos = [], remarks = [], visits = [] } = data;
+
+    // 检查身份证号是否已存在
+    if (personInfo.idCard) {
+      const existingPerson = await this.personRepository.findOne({
+        where: { idCard: personInfo.idCard }
+      });
+      if (existingPerson) {
+        throw new Error('该身份证号已存在');
+      }
+    }
+
+    // 检查残疾证号是否已存在
+    if (personInfo.disabilityId) {
+      const existingPerson = await this.personRepository.findOne({
+        where: { disabilityId: personInfo.disabilityId }
+      });
+      if (existingPerson) {
+        throw new Error('该残疾证号已存在');
+      }
+    }
+
+    // 验证照片中的文件ID
+    for (const photo of photos) {
+      if (photo.fileId) {
+        const fileExists = await this.fileRepository.findOne({ where: { id: photo.fileId } });
+        if (!fileExists) {
+          throw new Error(`文件ID ${photo.fileId} 不存在`);
+        }
+      } else {
+        throw new Error('照片必须包含有效的文件ID');
+      }
+    }
+
+    // 创建残疾人基本信息
+    const person = this.personRepository.create(personInfo);
+    const savedPerson = await this.personRepository.save(person);
+
+    // 准备关联数据
+    const personId = savedPerson.id;
+
+    // 处理银行卡信息,确保只有一个默认银行卡
+    const processedBankCards = [...bankCards];
+    const defaultCardsCount = processedBankCards.filter(card => card.isDefault === 1).length;
+    if (defaultCardsCount > 1) {
+      // 只保留第一个默认银行卡
+      let defaultSet = false;
+      processedBankCards.forEach(card => {
+        if (card.isDefault === 1 && !defaultSet) {
+          defaultSet = true;
+        } else if (card.isDefault === 1) {
+          card.isDefault = 0;
+        }
+      });
+    }
+
+    // 创建银行卡信息
+    let savedBankCards: DisabledBankCard[] = [];
+    if (processedBankCards.length > 0) {
+      const cardsToSave = processedBankCards.map(card =>
+        this.bankCardRepository.create({ ...card, personId })
+      );
+      savedBankCards = await this.bankCardRepository.save(cardsToSave);
+    }
+
+    // 创建照片信息
+    let savedPhotos: DisabledPhoto[] = [];
+    if (photos.length > 0) {
+      const photosToSave = photos.map(photo =>
+        this.photoRepository.create({ ...photo, personId, uploadTime: new Date() })
+      );
+      savedPhotos = await this.photoRepository.save(photosToSave);
+    }
+
+    // 创建备注信息
+    let savedRemarks: DisabledRemark[] = [];
+    if (remarks.length > 0) {
+      const remarksToSave = remarks.map(remark =>
+        this.remarkRepository.create({ ...remark, personId, remarkTime: new Date() })
+      );
+      savedRemarks = await this.remarkRepository.save(remarksToSave);
+    }
+
+    // 创建回访记录
+    let savedVisits: DisabledVisit[] = [];
+    if (visits.length > 0) {
+      const visitsToSave = visits.map(visit =>
+        this.visitRepository.create({ ...visit, personId })
+      );
+      savedVisits = await this.visitRepository.save(visitsToSave);
+    }
+
+    // 为照片获取文件URL
+    for (const photo of savedPhotos) {
+      if (photo.fileId) {
+        try {
+          const file = await this.fileRepository.findOne({ where: { id: photo.fileId } });
+          if (file) {
+            const url = await file.fullUrl;
+            (photo as any).fileUrl = url;
+          }
+        } catch (error) {
+          console.error('获取文件URL失败:', error);
+          (photo as any).fileUrl = null;
+        }
+      }
+    }
+
+    // 返回创建的所有信息
+    return {
+      personInfo: savedPerson,
+      bankCards: savedBankCards,
+      photos: savedPhotos,
+      remarks: savedRemarks,
+      visits: savedVisits,
+      message: '残疾人所有信息创建成功',
+      success: true
+    };
+  }
+
+  /**
+   * 聚合查询残疾人所有信息
+   */
+  async findDisabledPersonWithAllData(personId: number): Promise<{
+    personInfo: DisabledPerson;
+    bankCards: DisabledBankCard[];
+    photos: DisabledPhoto[];
+    remarks: DisabledRemark[];
+    visits: DisabledVisit[];
+    success: boolean;
+  }> {
+    // 查询残疾人基本信息(包含关联数据)
+    const person = await this.disabledPersonService.findOne(personId);
+    if (!person) {
+      throw new Error('未找到该残疾人信息');
+    }
+
+    // 为照片获取文件URL
+    if (person.photos) {
+      for (const photo of person.photos) {
+        if (photo.file) {
+          try {
+            const url = await photo.file.fullUrl;
+            (photo as any).fileUrl = url;
+          } catch (error) {
+            console.error('获取文件URL失败:', error);
+            (photo as any).fileUrl = null;
+          }
+        }
+      }
+    }
+
+    return {
+      personInfo: person,
+      bankCards: person.bankCards || [],
+      photos: person.photos || [],
+      remarks: person.remarks || [],
+      visits: person.visits || [],
+      success: true
+    };
+  }
+
+  /**
+   * 更新残疾人所有信息
+   */
+  async updateDisabledPersonWithAllData(personId: number, data: {
+    personInfo?: Partial<DisabledPerson>;
+    bankCards?: Partial<DisabledBankCard>[];
+    photos?: Partial<DisabledPhoto>[];
+    remarks?: Partial<DisabledRemark>[];
+    visits?: Partial<DisabledVisit>[];
+  }): Promise<{
+    personInfo: DisabledPerson;
+    bankCards: DisabledBankCard[];
+    photos: DisabledPhoto[];
+    remarks: DisabledRemark[];
+    visits: DisabledVisit[];
+    message: string;
+    success: boolean;
+  } | null> {
+    const { personInfo = {}, bankCards = [], photos = [], remarks = [], visits = [] } = data;
+
+    // 检查残疾人是否存在
+    const existingPerson = await this.personRepository.findOne({
+      where: { id: personId },
+      relations: ['bankCards', 'photos', 'remarks', 'visits']
+    });
+
+    if (!existingPerson) {
+      return null;
+    }
+
+    // 更新残疾人基本信息
+    if (Object.keys(personInfo).length > 0) {
+      // 如果更新了身份证号,检查唯一性
+      if (personInfo.idCard && personInfo.idCard !== existingPerson.idCard) {
+        const existingWithIdCard = await this.personRepository.findOne({
+          where: { idCard: personInfo.idCard }
+        });
+        if (existingWithIdCard) {
+          throw new Error('身份证号已存在');
+        }
+      }
+
+      Object.assign(existingPerson, personInfo);
+      await this.personRepository.save(existingPerson);
+    }
+
+    // 更新银行卡信息(先删除旧的,再创建新的)
+    if (bankCards.length > 0) {
+      await this.bankCardRepository.delete({ personId });
+      const cardsToSave = bankCards.map(card =>
+        this.bankCardRepository.create({ ...card, personId })
+      );
+      await this.bankCardRepository.save(cardsToSave);
+    }
+
+    // 更新照片信息(先删除旧的,再创建新的)
+    if (photos.length > 0) {
+      await this.photoRepository.delete({ personId });
+      const photosToSave = photos.map(photo =>
+        this.photoRepository.create({ ...photo, personId, uploadTime: new Date() })
+      );
+      await this.photoRepository.save(photosToSave);
+    }
+
+    // 更新备注信息(先删除旧的,再创建新的)
+    if (remarks.length > 0) {
+      await this.remarkRepository.delete({ personId });
+      const remarksToSave = remarks.map(remark =>
+        this.remarkRepository.create({ ...remark, personId, remarkTime: new Date() })
+      );
+      await this.remarkRepository.save(remarksToSave);
+    }
+
+    // 更新回访记录(先删除旧的,再创建新的)
+    if (visits.length > 0) {
+      await this.visitRepository.delete({ personId });
+      const visitsToSave = visits.map(visit =>
+        this.visitRepository.create({ ...visit, personId })
+      );
+      await this.visitRepository.save(visitsToSave);
+    }
+
+    // 获取更新后的完整数据
+    const updatedPerson = await this.personRepository.findOne({
+      where: { id: personId },
+      relations: ['bankCards', 'photos', 'remarks', 'visits']
+    });
+
+    if (!updatedPerson) {
+      return null;
+    }
+
+    // 为照片获取文件URL
+    for (const photo of updatedPerson.photos || []) {
+      if (photo.fileId) {
+        try {
+          const file = await this.fileRepository.findOne({ where: { id: photo.fileId } });
+          if (file) {
+            const url = await file.fullUrl;
+            (photo as any).fileUrl = url;
+          }
+        } catch (error) {
+          console.error('获取文件URL失败:', error);
+        }
+      }
+    }
+
+    return {
+      personInfo: updatedPerson,
+      bankCards: updatedPerson.bankCards || [],
+      photos: updatedPerson.photos || [],
+      remarks: updatedPerson.remarks || [],
+      visits: updatedPerson.visits || [],
+      message: '残疾人信息更新成功',
+      success: true
+    };
+  }
+}

+ 322 - 0
allin-packages/disability-module/src/services/disabled-person.service.ts

@@ -0,0 +1,322 @@
+import { GenericCrudService } from '@d8d/shared-crud';
+import { DataSource, Repository, Like, Not, In } from 'typeorm';
+import { DisabledPerson } from '../entities/disabled-person.entity';
+import { DisabledBankCard } from '../entities/disabled-bank-card.entity';
+import { DisabledPhoto } from '../entities/disabled-photo.entity';
+import { DisabledRemark } from '../entities/disabled-remark.entity';
+import { DisabledVisit } from '../entities/disabled-visit.entity';
+import { File } from '@d8d/file-module/entities';
+import { FileService } from '@d8d/file-module/services';
+
+export class DisabledPersonService extends GenericCrudService<DisabledPerson> {
+  private readonly bankCardRepository: Repository<DisabledBankCard>;
+  private readonly photoRepository: Repository<DisabledPhoto>;
+  private readonly remarkRepository: Repository<DisabledRemark>;
+  private readonly visitRepository: Repository<DisabledVisit>;
+  private readonly fileRepository: Repository<File>;
+  private fileService: FileService;
+
+  constructor(dataSource: DataSource) {
+    super(dataSource, DisabledPerson);
+    this.bankCardRepository = dataSource.getRepository(DisabledBankCard);
+    this.photoRepository = dataSource.getRepository(DisabledPhoto);
+    this.remarkRepository = dataSource.getRepository(DisabledRemark);
+    this.visitRepository = dataSource.getRepository(DisabledVisit);
+    this.fileRepository = dataSource.getRepository(File);
+    this.fileService = new FileService(dataSource);
+  }
+
+  /**
+   * 创建残疾人 - 覆盖父类方法,添加身份证号唯一性检查
+   */
+  async create(data: Partial<DisabledPerson>, userId?: string | number): Promise<DisabledPerson> {
+    // 检查身份证号是否已存在
+    if (data.idCard) {
+      const existingPerson = await this.repository.findOne({
+        where: { idCard: data.idCard }
+      });
+      if (existingPerson) {
+        throw new Error('身份证号已存在');
+      }
+    }
+
+    // 检查残疾证号是否已存在
+    if (data.disabilityId) {
+      const existingPerson = await this.repository.findOne({
+        where: { disabilityId: data.disabilityId }
+      });
+      if (existingPerson) {
+        throw new Error('残疾证号已存在');
+      }
+    }
+
+    return super.create(data, userId);
+  }
+
+  /**
+   * 更新残疾人 - 覆盖父类方法,添加身份证号唯一性检查
+   */
+  async update(id: number, data: Partial<DisabledPerson>, userId?: string | number): Promise<DisabledPerson | null> {
+    // 检查残疾人是否存在
+    const person = await this.repository.findOne({ where: { id } });
+    if (!person) {
+      throw new Error('残疾人不存在');
+    }
+
+    // 检查身份证号是否与其他残疾人重复
+    if (data.idCard && data.idCard !== person.idCard) {
+      const existingPerson = await this.repository.findOne({
+        where: { idCard: data.idCard, id: Not(id) }
+      });
+      if (existingPerson) {
+        throw new Error('身份证号已存在');
+      }
+    }
+
+    // 检查残疾证号是否与其他残疾人重复
+    if (data.disabilityId && data.disabilityId !== person.disabilityId) {
+      const existingPerson = await this.repository.findOne({
+        where: { disabilityId: data.disabilityId, id: Not(id) }
+      });
+      if (existingPerson) {
+        throw new Error('残疾证号已存在');
+      }
+    }
+
+    return super.update(id, data, userId);
+  }
+
+  /**
+   * 获取单个残疾人(包含关联数据)
+   */
+  async findOne(id: number): Promise<DisabledPerson | null> {
+    const person = await this.repository.findOne({
+      where: { id },
+      relations: ['bankCards', 'photos', 'photos.file', 'remarks', 'visits']
+    });
+
+    if (person && person.photos) {
+      // 为每个照片获取文件URL
+      for (const photo of person.photos) {
+        if (photo.file) {
+          try {
+            const url = await photo.file.fullUrl;
+            (photo as any).fileUrl = url;
+          } catch (error) {
+            console.error('获取文件URL失败:', error);
+            (photo as any).fileUrl = null;
+          }
+        }
+      }
+    }
+
+    return person;
+  }
+
+  /**
+   * 获取所有残疾人(分页+条件查询) - 返回源服务的格式
+   */
+  async findAll(query: {
+    name?: string;
+    idCard?: string;
+    disabilityType?: string;
+    disabilityLevel?: string;
+    page?: number;
+    limit?: number;
+  }): Promise<{ data: DisabledPerson[], total: number }> {
+    const {
+      name,
+      idCard,
+      disabilityType,
+      disabilityLevel,
+      page = 1,
+      limit = 10
+    } = query;
+
+    const queryBuilder = this.repository.createQueryBuilder('person');
+
+    if (name) {
+      queryBuilder.andWhere('person.name LIKE :name', { name: `%${name}%` });
+    }
+    if (idCard) {
+      queryBuilder.andWhere('person.idCard = :idCard', { idCard });
+    }
+    if (disabilityType) {
+      queryBuilder.andWhere('person.disabilityType = :disabilityType', { disabilityType });
+    }
+    if (disabilityLevel) {
+      queryBuilder.andWhere('person.disabilityLevel = :disabilityLevel', { disabilityLevel });
+    }
+
+    const [data, total] = await queryBuilder
+      .skip((page - 1) * limit)
+      .take(limit)
+      .orderBy('person.createTime', 'DESC')
+      .getManyAndCount();
+
+    // 加载关联数据
+    if (data.length > 0) {
+      const personIds = data.map(p => p.id);
+
+      const [bankCards, photos, remarks, visits] = await Promise.all([
+        this.bankCardRepository.find({ where: { personId: In(personIds) } }),
+        this.photoRepository.find({
+          where: { personId: In(personIds) },
+          relations: ['file']
+        }),
+        this.remarkRepository.find({ where: { personId: In(personIds) } }),
+        this.visitRepository.find({ where: { personId: In(personIds) } })
+      ]);
+
+      // 为每个照片获取文件URL
+      for (const photo of photos) {
+        if (photo.file) {
+          try {
+            const url = await photo.file.fullUrl;
+            (photo as any).fileUrl = url;
+          } catch (error) {
+            console.error('获取文件URL失败:', error);
+            (photo as any).fileUrl = null;
+          }
+        }
+      }
+
+      // 将关联数据分组到对应的残疾人
+      const bankCardsMap = new Map<number, DisabledBankCard[]>();
+      const photosMap = new Map<number, DisabledPhoto[]>();
+      const remarksMap = new Map<number, DisabledRemark[]>();
+      const visitsMap = new Map<number, DisabledVisit[]>();
+
+      for (const card of bankCards) {
+        const cards = bankCardsMap.get(card.personId) || [];
+        cards.push(card);
+        bankCardsMap.set(card.personId, cards);
+      }
+
+      for (const photo of photos) {
+        const photos = photosMap.get(photo.personId) || [];
+        photos.push(photo);
+        photosMap.set(photo.personId, photos);
+      }
+
+      for (const remark of remarks) {
+        const remarks = remarksMap.get(remark.personId) || [];
+        remarks.push(remark);
+        remarksMap.set(remark.personId, remarks);
+      }
+
+      for (const visit of visits) {
+        const visits = visitsMap.get(visit.personId) || [];
+        visits.push(visit);
+        visitsMap.set(visit.personId, visits);
+      }
+
+      // 将关联数据附加到每个残疾人
+      for (const person of data) {
+        person.bankCards = bankCardsMap.get(person.id) || [];
+        person.photos = photosMap.get(person.id) || [];
+        person.remarks = remarksMap.get(person.id) || [];
+        person.visits = visitsMap.get(person.id) || [];
+      }
+    }
+
+    return { data, total };
+  }
+
+  /**
+   * 根据身份证号查询残疾人
+   */
+  async findByIdCard(idCard: string): Promise<DisabledPerson | null> {
+    const person = await this.repository.findOne({
+      where: { idCard },
+      relations: ['bankCards', 'photos', 'photos.file', 'remarks', 'visits']
+    });
+
+    if (person && person.photos) {
+      // 为每个照片获取文件URL
+      for (const photo of person.photos) {
+        if (photo.file) {
+          try {
+            const url = await photo.file.fullUrl;
+            (photo as any).fileUrl = url;
+          } catch (error) {
+            console.error('获取文件URL失败:', error);
+            (photo as any).fileUrl = null;
+          }
+        }
+      }
+    }
+
+    return person;
+  }
+
+  /**
+   * 验证文件ID是否存在
+   */
+  async validateFileId(fileId: number): Promise<boolean> {
+    try {
+      const file = await this.fileRepository.findOne({ where: { id: fileId } });
+      return !!file;
+    } catch (error) {
+      console.error('验证文件ID失败:', error);
+      return false;
+    }
+  }
+
+  /**
+   * 创建照片记录(包含文件验证)
+   */
+  async createPhoto(photoData: Partial<DisabledPhoto>): Promise<DisabledPhoto> {
+    // 验证文件ID是否存在
+    if (photoData.fileId) {
+      const fileExists = await this.validateFileId(photoData.fileId);
+      if (!fileExists) {
+        throw new Error('文件不存在');
+      }
+    } else {
+      throw new Error('文件ID不能为空');
+    }
+
+    // 验证残疾人是否存在
+    if (photoData.personId) {
+      const personExists = await this.repository.findOne({ where: { id: photoData.personId } });
+      if (!personExists) {
+        throw new Error('残疾人不存在');
+      }
+    } else {
+      throw new Error('残疾人ID不能为空');
+    }
+
+    const photo = this.photoRepository.create(photoData);
+    return await this.photoRepository.save(photo);
+  }
+
+  /**
+   * 批量创建照片记录
+   */
+  async createPhotosBatch(photosData: Partial<DisabledPhoto>[]): Promise<DisabledPhoto[]> {
+    // 验证所有文件ID和残疾人ID
+    for (const photoData of photosData) {
+      if (photoData.fileId) {
+        const fileExists = await this.validateFileId(photoData.fileId);
+        if (!fileExists) {
+          throw new Error(`文件ID ${photoData.fileId} 不存在`);
+        }
+      } else {
+        throw new Error('文件ID不能为空');
+      }
+
+      if (photoData.personId) {
+        const personExists = await this.repository.findOne({ where: { id: photoData.personId } });
+        if (!personExists) {
+          throw new Error(`残疾人ID ${photoData.personId} 不存在`);
+        }
+      } else {
+        throw new Error('残疾人ID不能为空');
+      }
+    }
+
+    const photos = this.photoRepository.create(photosData);
+    return await this.photoRepository.save(photos);
+  }
+}

+ 2 - 0
allin-packages/disability-module/src/services/index.ts

@@ -0,0 +1,2 @@
+export { DisabledPersonService } from './disabled-person.service';
+export { AggregatedService } from './aggregated.service';

+ 847 - 0
allin-packages/disability-module/tests/integration/disability.integration.test.ts

@@ -0,0 +1,847 @@
+import { describe, it, expect, beforeEach } from 'vitest';
+import { testClient } from 'hono/testing';
+import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util';
+import { JWTUtil } from '@d8d/shared-utils';
+import { UserEntity, Role } from '@d8d/user-module';
+import { FileEntity } from '@d8d/file-module';
+import disabledPersonRoutes from '../../src/routes/disabled-person.routes';
+import { DisabledPerson } from '../../src/entities/disabled-person.entity';
+import { DisabledBankCard } from '../../src/entities/disabled-bank-card.entity';
+import { DisabledPhoto } from '../../src/entities/disabled-photo.entity';
+import { DisabledRemark } from '../../src/entities/disabled-remark.entity';
+import { DisabledVisit } from '../../src/entities/disabled-visit.entity';
+
+// 设置集成测试钩子 - 包含所有相关实体
+setupIntegrationDatabaseHooksWithEntities([
+  UserEntity,
+  Role,
+  FileEntity,
+  DisabledPerson,
+  DisabledBankCard,
+  DisabledPhoto,
+  DisabledRemark,
+  DisabledVisit
+])
+
+describe('残疾人管理API集成测试', () => {
+  let client: ReturnType<typeof testClient<typeof disabledPersonRoutes>>;
+  let testToken: string;
+  let testUser: UserEntity;
+  let testFile: FileEntity;
+
+  beforeEach(async () => {
+    // 创建测试客户端
+    client = testClient(disabledPersonRoutes);
+
+    // 获取数据源
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+
+    // 创建测试用户
+    const userRepository = dataSource.getRepository(UserEntity);
+    testUser = userRepository.create({
+      username: `test_user_${Date.now()}`,
+      password: 'test_password',
+      nickname: '测试用户',
+      registrationSource: 'web'
+    });
+    await userRepository.save(testUser);
+
+    // 创建测试文件(用于照片集成测试)
+    const fileRepository = dataSource.getRepository(FileEntity);
+    testFile = fileRepository.create({
+      filename: 'test_photo.jpg',
+      originalFilename: 'test_photo.jpg',
+      mimeType: 'image/jpeg',
+      size: 1024,
+      bucket: 'd8dai',
+      key: `test/${Date.now()}/photo.jpg`,
+      uploaderId: testUser.id
+    });
+    await fileRepository.save(testFile);
+
+    // 生成测试用户的token
+    testToken = JWTUtil.generateToken({
+      id: testUser.id,
+      username: testUser.username,
+      roles: [{name:'user'}]
+    });
+  });
+
+  describe('POST /createDisabledPerson', () => {
+    it('应该成功创建残疾人基本信息', async () => {
+      const createData = {
+        name: '张三',
+        gender: '男',
+        idCard: '110101199001011234',
+        disabilityId: 'D123456789',
+        disabilityType: '肢体残疾',
+        disabilityLevel: '一级',
+        idAddress: '北京市东城区',
+        phone: '13800138000',
+        province: '北京市',
+        city: '北京市'
+      };
+
+      const response = await client.createDisabledPerson.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      console.debug('创建残疾人响应状态:', response.status);
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.name).toBe(createData.name);
+        expect(data.idCard).toBe(createData.idCard);
+        expect(data.disabilityId).toBe(createData.disabilityId);
+      }
+    });
+
+    it('应该验证身份证号唯一性', async () => {
+      // 先创建一个残疾人
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const disabledPersonRepository = dataSource.getRepository(DisabledPerson);
+      const existingPerson = disabledPersonRepository.create({
+        name: '李四',
+        gender: '女',
+        idCard: '110101199001011235',
+        disabilityId: 'D123456788',
+        disabilityType: '视力残疾',
+        disabilityLevel: '二级',
+        idAddress: '北京市西城区',
+        phone: '13900139000',
+        province: '北京市',
+        city: '北京市'
+      });
+      await disabledPersonRepository.save(existingPerson);
+
+      // 尝试创建相同身份证号的残疾人
+      const createData = {
+        name: '王五',
+        gender: '男',
+        idCard: '110101199001011235', // 重复的身份证号
+        disabilityId: 'D123456777',
+        disabilityType: '听力残疾',
+        disabilityLevel: '三级',
+        idAddress: '北京市朝阳区',
+        phone: '13700137000',
+        province: '北京市',
+        city: '北京市'
+      };
+
+      const response = await client.createDisabledPerson.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(400);
+    });
+
+    it('应该验证必填字段', async () => {
+      const createData = {
+        name: '', // 空字符串,应该验证失败
+        gender: '男',
+        idCard: '110101199001011236',
+        disabilityId: 'D123456776',
+        disabilityType: '肢体残疾',
+        disabilityLevel: '一级',
+        idAddress: '北京市海淀区',
+        phone: '13600136000',
+        province: '北京市',
+        city: '北京市'
+      };
+
+      const response = await client.createDisabledPerson.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(400);
+    });
+  });
+
+  describe('POST /deleteDisabledPerson', () => {
+    it('应该成功删除残疾人', async () => {
+      // 先创建一个残疾人
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const disabledPersonRepository = dataSource.getRepository(DisabledPerson);
+      const person = disabledPersonRepository.create({
+        name: '测试删除人员',
+        gender: '男',
+        idCard: '110101199001011237',
+        disabilityId: 'D123456775',
+        disabilityType: '肢体残疾',
+        disabilityLevel: '一级',
+        idAddress: '北京市石景山区',
+        phone: '13500135000',
+        province: '北京市',
+        city: '北京市'
+      });
+      await disabledPersonRepository.save(person);
+
+      const deleteData = {
+        id: person.id
+      };
+
+      const response = await client.deleteDisabledPerson.$post({
+        json: deleteData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.success).toBe(true);
+
+        // 验证残疾人已被删除
+        const deletedPerson = await disabledPersonRepository.findOne({ where: { id: person.id } });
+        expect(deletedPerson).toBeNull();
+      }
+    });
+
+    it('应该处理不存在的残疾人ID', async () => {
+      const deleteData = {
+        id: 99999 // 不存在的ID
+      };
+
+      const response = await client.deleteDisabledPerson.$post({
+        json: deleteData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(404);
+    });
+  });
+
+  describe('POST /updateDisabledPerson', () => {
+    it('应该成功更新残疾人信息', async () => {
+      // 先创建一个残疾人
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const disabledPersonRepository = dataSource.getRepository(DisabledPerson);
+      const person = disabledPersonRepository.create({
+        name: '原始姓名',
+        gender: '男',
+        idCard: '110101199001011238',
+        disabilityId: 'D123456774',
+        disabilityType: '肢体残疾',
+        disabilityLevel: '一级',
+        idAddress: '北京市通州区',
+        phone: '13400134000',
+        province: '北京市',
+        city: '北京市'
+      });
+      await disabledPersonRepository.save(person);
+
+      const updateData = {
+        id: person.id,
+        name: '更新后的姓名',
+        gender: '女',
+        phone: '13300133000'
+      };
+
+      const response = await client.updateDisabledPerson.$post({
+        json: updateData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.name).toBe(updateData.name);
+        expect(data.gender).toBe(updateData.gender);
+        expect(data.phone).toBe(updateData.phone);
+      }
+    });
+
+    it('应该验证身份证号唯一性(更新时)', async () => {
+      // 创建两个残疾人
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const disabledPersonRepository = dataSource.getRepository(DisabledPerson);
+
+      const person1 = disabledPersonRepository.create({
+        name: '人员A',
+        gender: '男',
+        idCard: '110101199001011239',
+        disabilityId: 'D123456773',
+        disabilityType: '肢体残疾',
+        disabilityLevel: '一级',
+        idAddress: '北京市顺义区',
+        phone: '13200132000',
+        province: '北京市',
+        city: '北京市'
+      });
+      await disabledPersonRepository.save(person1);
+
+      const person2 = disabledPersonRepository.create({
+        name: '人员B',
+        gender: '女',
+        idCard: '110101199001011240',
+        disabilityId: 'D123456772',
+        disabilityType: '视力残疾',
+        disabilityLevel: '二级',
+        idAddress: '北京市大兴区',
+        phone: '13100131000',
+        province: '北京市',
+        city: '北京市'
+      });
+      await disabledPersonRepository.save(person2);
+
+      // 尝试将人员2的身份证号改为人员1的身份证号
+      const updateData = {
+        id: person2.id,
+        idCard: '110101199001011239' // 重复的身份证号
+      };
+
+      const response = await client.updateDisabledPerson.$post({
+        json: updateData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(400);
+    });
+
+    it('应该处理不存在的残疾人', async () => {
+      const updateData = {
+        id: 99999, // 不存在的ID
+        name: '新姓名'
+      };
+
+      const response = await client.updateDisabledPerson.$post({
+        json: updateData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(404);
+    });
+  });
+
+  describe('GET /getAllDisabledPersons', () => {
+    it('应该成功获取残疾人列表(分页)', async () => {
+      // 创建一些测试数据
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const disabledPersonRepository = dataSource.getRepository(DisabledPerson);
+
+      for (let i = 1; i <= 5; i++) {
+        const person = disabledPersonRepository.create({
+          name: `残疾人${i}`,
+          gender: i % 2 === 0 ? '女' : '男',
+          idCard: `1101011990010112${40 + i}`,
+          disabilityId: `D1234567${70 + i}`,
+          disabilityType: '肢体残疾',
+          disabilityLevel: '一级',
+          idAddress: `北京市测试区${i}`,
+          phone: `138001380${i}`,
+          province: '北京市',
+          city: '北京市'
+        });
+        await disabledPersonRepository.save(person);
+      }
+
+      const response = await client.getAllDisabledPersons.$get({
+        query: {
+          skip: 0,
+          take: 10
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.data).toHaveLength(5);
+        expect(data.total).toBe(5);
+        expect(data.data[0].name).toBe('残疾人5'); // 按ID降序排列
+      }
+    });
+
+    it('应该处理分页参数', async () => {
+      // 创建更多测试数据
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const disabledPersonRepository = dataSource.getRepository(DisabledPerson);
+
+      for (let i = 1; i <= 15; i++) {
+        const person = disabledPersonRepository.create({
+          name: `分页人员${i}`,
+          gender: i % 2 === 0 ? '女' : '男',
+          idCard: `1101011990010113${i}`,
+          disabilityId: `D1234568${i}`,
+          disabilityType: '肢体残疾',
+          disabilityLevel: '一级',
+          idAddress: `北京市分页区${i}`,
+          phone: `138001381${i}`,
+          province: '北京市',
+          city: '北京市'
+        });
+        await disabledPersonRepository.save(person);
+      }
+
+      const response = await client.getAllDisabledPersons.$get({
+        query: {
+          skip: 5,
+          take: 5
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.data).toHaveLength(5);
+        expect(data.total).toBe(15);
+      }
+    });
+  });
+
+  describe('GET /searchDisabledPersons', () => {
+    it('应该成功按姓名搜索残疾人', async () => {
+      // 创建测试数据
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const disabledPersonRepository = dataSource.getRepository(DisabledPerson);
+
+      const person1 = disabledPersonRepository.create({
+        name: '张三',
+        gender: '男',
+        idCard: '110101199001011241',
+        disabilityId: 'D123456771',
+        disabilityType: '肢体残疾',
+        disabilityLevel: '一级',
+        idAddress: '北京市昌平区',
+        phone: '13000130001',
+        province: '北京市',
+        city: '北京市'
+      });
+      await disabledPersonRepository.save(person1);
+
+      const person2 = disabledPersonRepository.create({
+        name: '李四',
+        gender: '女',
+        idCard: '110101199001011242',
+        disabilityId: 'D123456770',
+        disabilityType: '视力残疾',
+        disabilityLevel: '二级',
+        idAddress: '北京市平谷区',
+        phone: '13000130002',
+        province: '北京市',
+        city: '北京市'
+      });
+      await disabledPersonRepository.save(person2);
+
+      const response = await client.searchDisabledPersons.$get({
+        query: {
+          name: '张三',
+          skip: 0,
+          take: 10
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.data).toHaveLength(1);
+        expect(data.data[0].name).toBe('张三');
+      }
+    });
+
+    it('应该验证搜索关键词不能为空', async () => {
+      const response = await client.searchDisabledPersons.$get({
+        query: {
+          name: '', // 空关键词
+          skip: 0,
+          take: 10
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(400);
+    });
+  });
+
+  describe('GET /getDisabledPerson/{id}', () => {
+    it('应该成功获取单个残疾人详情', async () => {
+      // 先创建一个残疾人
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const disabledPersonRepository = dataSource.getRepository(DisabledPerson);
+      const person = disabledPersonRepository.create({
+        name: '测试人员详情',
+        gender: '男',
+        idCard: '110101199001011243',
+        disabilityId: 'D123456769',
+        disabilityType: '肢体残疾',
+        disabilityLevel: '一级',
+        idAddress: '北京市怀柔区',
+        phone: '13000130003',
+        province: '北京市',
+        city: '北京市',
+        canDirectContact: 1,
+        isMarried: 1,
+        education: '本科'
+      });
+      await disabledPersonRepository.save(person);
+
+      const response = await client.getDisabledPerson[':id'].$get({
+        param: {
+          id: person.id
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data?.name).toBe('测试人员详情');
+        expect(data?.education).toBe('本科');
+        expect(data?.canDirectContact).toBe(1);
+      }
+    });
+
+    it('应该处理不存在的残疾人ID', async () => {
+      const response = await client.getDisabledPerson[':id'].$get({
+        param: {
+          id: 99999 // 不存在的ID
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(200); // 返回200,但数据为null
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data).toBeNull();
+      }
+    });
+  });
+
+  describe('GET /getDisabledPersonByIdCard', () => {
+    it('应该成功根据身份证号查询残疾人', async () => {
+      // 先创建一个残疾人
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const disabledPersonRepository = dataSource.getRepository(DisabledPerson);
+      const person = disabledPersonRepository.create({
+        name: '身份证查询测试',
+        gender: '女',
+        idCard: '110101199001011244',
+        disabilityId: 'D123456768',
+        disabilityType: '听力残疾',
+        disabilityLevel: '三级',
+        idAddress: '北京市密云区',
+        phone: '13000130004',
+        province: '北京市',
+        city: '北京市'
+      });
+      await disabledPersonRepository.save(person);
+
+      const response = await client.getDisabledPersonByIdCard.$get({
+        query: {
+          idCard: '110101199001011244'
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data?.name).toBe('身份证查询测试');
+        expect(data?.idCard).toBe('110101199001011244');
+      }
+    });
+
+    it('应该处理不存在的身份证号', async () => {
+      const response = await client.getDisabledPersonByIdCard.$get({
+        query: {
+          idCard: '999999999999999999' // 不存在的身份证号
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(200); // 返回200,但数据为null
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data).toBeNull();
+      }
+    });
+  });
+
+  describe('POST /createAggregatedDisabledPerson', () => {
+    it('应该成功创建聚合残疾人信息(包含所有关联数据)', async () => {
+      const createData = {
+        disabledPerson: {
+          name: '聚合创建测试',
+          gender: '男',
+          idCard: '110101199001011245',
+          disabilityId: 'D123456767',
+          disabilityType: '肢体残疾',
+          disabilityLevel: '一级',
+          idAddress: '北京市延庆区',
+          phone: '13000130005',
+          province: '北京市',
+          city: '北京市'
+        },
+        bankCards: [
+          {
+            bankName: '中国工商银行',
+            cardNumber: '6222021234567890123',
+            cardholderName: '聚合创建测试'
+          }
+        ],
+        photos: [
+          {
+            photoType: '身份证照片',
+            fileId: testFile.id // 使用测试文件ID
+          }
+        ],
+        remarks: [
+          {
+            remarkType: '家庭情况',
+            remarkContent: '家庭经济困难,需要帮助'
+          }
+        ],
+        visits: [
+          {
+            visitDate: '2025-12-02T10:00:00Z',
+            visitContent: '初次回访,了解基本情况',
+            visitor: '工作人员A'
+          }
+        ]
+      };
+
+      const response = await client.createAggregatedDisabledPerson.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.disabledPerson.name).toBe('聚合创建测试');
+        expect(data.bankCards).toHaveLength(1);
+        expect(data.photos).toHaveLength(1);
+        expect(data.remarks).toHaveLength(1);
+        expect(data.visits).toHaveLength(1);
+
+        // 验证文件集成
+        expect(data.photos[0].fileId).toBe(testFile.id);
+      }
+    });
+
+    it('应该验证文件ID的有效性', async () => {
+      const createData = {
+        disabledPerson: {
+          name: '文件验证测试',
+          gender: '女',
+          idCard: '110101199001011246',
+          disabilityId: 'D123456766',
+          disabilityType: '视力残疾',
+          disabilityLevel: '二级',
+          idAddress: '北京市房山区',
+          phone: '13000130006',
+          province: '北京市',
+          city: '北京市'
+        },
+        photos: [
+          {
+            photoType: '身份证照片',
+            fileId: 99999 // 不存在的文件ID
+          }
+        ]
+      };
+
+      const response = await client.createAggregatedDisabledPerson.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(400); // 应该返回400,因为文件ID无效
+    });
+  });
+
+  describe('GET /getAggregatedDisabledPerson/{personId}', () => {
+    it('应该成功获取聚合残疾人信息', async () => {
+      // 先创建一个完整的残疾人数据(包含所有关联数据)
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+
+      // 创建残疾人
+      const disabledPersonRepository = dataSource.getRepository(DisabledPerson);
+      const person = disabledPersonRepository.create({
+        name: '聚合查询测试',
+        gender: '男',
+        idCard: '110101199001011247',
+        disabilityId: 'D123456765',
+        disabilityType: '肢体残疾',
+        disabilityLevel: '一级',
+        idAddress: '北京市门头沟区',
+        phone: '13000130007',
+        province: '北京市',
+        city: '北京市'
+      });
+      await disabledPersonRepository.save(person);
+
+      // 创建银行卡
+      const bankCardRepository = dataSource.getRepository(DisabledBankCard);
+      const bankCard = bankCardRepository.create({
+        personId: person.id,
+        bankName: '中国建设银行',
+        cardNumber: '6227001234567890123',
+        cardholderName: '聚合查询测试'
+      });
+      await bankCardRepository.save(bankCard);
+
+      // 创建照片(使用测试文件)
+      const photoRepository = dataSource.getRepository(DisabledPhoto);
+      const photo = photoRepository.create({
+        personId: person.id,
+        photoType: '身份证照片',
+        fileId: testFile.id
+      });
+      await photoRepository.save(photo);
+
+      // 创建备注
+      const remarkRepository = dataSource.getRepository(DisabledRemark);
+      const remark = remarkRepository.create({
+        personId: person.id,
+        remarkType: '工作情况',
+        remarkContent: '目前无工作,需要就业帮助'
+      });
+      await remarkRepository.save(remark);
+
+      // 创建回访记录
+      const visitRepository = dataSource.getRepository(DisabledVisit);
+      const visit = visitRepository.create({
+        personId: person.id,
+        visitDate: new Date('2025-12-01T14:30:00Z'),
+        visitContent: '了解就业需求',
+        visitor: '工作人员B'
+      });
+      await visitRepository.save(visit);
+
+      const response = await client.getAggregatedDisabledPerson[':personId'].$get({
+        param: {
+          personId: person.id
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.disabledPerson.name).toBe('聚合查询测试');
+        expect(data.bankCards).toHaveLength(1);
+        expect(data.photos).toHaveLength(1);
+        expect(data.remarks).toHaveLength(1);
+        expect(data.visits).toHaveLength(1);
+
+        // 验证文件数据完整性
+        expect(data.photos[0].fileId).toBe(testFile.id);
+      }
+    });
+
+    it('应该处理不存在的残疾人ID', async () => {
+      const response = await client.getAggregatedDisabledPerson[':personId'].$get({
+        param: {
+          personId: 99999 // 不存在的ID
+        }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(404);
+    });
+  });
+
+  describe('认证测试', () => {
+    it('应该验证所有端点需要认证', async () => {
+      // 测试没有token的情况
+      const response = await client.createDisabledPerson.$post({
+        json: {
+          name: '测试人员'
+        }
+      });
+
+      expect(response.status).toBe(401);
+    });
+
+    it('应该验证无效token', async () => {
+      const response = await client.createDisabledPerson.$post({
+        json: {
+          name: '测试人员'
+        }
+      }, {
+        headers: {
+          'Authorization': 'Bearer invalid_token'
+        }
+      });
+
+      expect(response.status).toBe(401);
+    });
+  });
+});

+ 16 - 0
allin-packages/disability-module/tsconfig.json

@@ -0,0 +1,16 @@
+{
+  "extends": "../../tsconfig.json",
+  "compilerOptions": {
+    "composite": true,
+    "rootDir": ".",
+    "outDir": "dist"
+  },
+  "include": [
+    "src/**/*",
+    "tests/**/*"
+  ],
+  "exclude": [
+    "node_modules",
+    "dist"
+  ]
+}

+ 21 - 0
allin-packages/disability-module/vitest.config.ts

@@ -0,0 +1,21 @@
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+  test: {
+    globals: true,
+    environment: 'node',
+    include: ['tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+    coverage: {
+      provider: 'v8',
+      reporter: ['text', 'json', 'html'],
+      exclude: [
+        'tests/**',
+        '**/*.d.ts',
+        '**/*.config.*',
+        '**/dist/**'
+      ]
+    },
+    // 关闭并行测试以避免数据库连接冲突
+    fileParallelism: false
+  }
+});

+ 81 - 58
docs/stories/007.004.transplant-disability-management-module.story.md

@@ -1,7 +1,7 @@
 # Story 007.004: 移植残疾人管理模块(disability_person → @d8d/allin-disability-module)
 
 ## Status
-Draft
+Ready for Development
 
 ## Story
 **As a** 开发者,
@@ -21,50 +21,50 @@ Draft
 10. ✅ 整体验证:与其他模块的集成测试
 
 ## Tasks / Subtasks
-- [ ] 创建`allin-packages/disability-module`目录结构 (AC: 1)
-  - [ ] 创建`allin-packages/disability-module/`目录
-  - [ ] 创建`package.json`文件,配置包名`@d8d/allin-disability-module`和workspace依赖
+- [x] 创建`allin-packages/disability-module`目录结构 (AC: 1)
+  - [x] 创建`allin-packages/disability-module/`目录
+  - [x] 创建`package.json`文件,配置包名`@d8d/allin-disability-module`和workspace依赖
     - **参考文件**: `allin-packages/platform-module/package.json`
     - **修改点**: 包名改为`@d8d/allin-disability-module`,添加对`@d8d/file-module`的依赖
     - **关键依赖**: `@d8d/file-module`, `@d8d/core-module`, `@d8d/shared-crud`, `@d8d/shared-utils`
     - **注意吸取经验**: 根据故事007.006的经验,需要在`pnpm-workspace.yaml`中添加`allin-packages/*`配置
-  - [ ] 创建`tsconfig.json`文件,配置TypeScript编译选项
+  - [x] 创建`tsconfig.json`文件,配置TypeScript编译选项
     - **参考文件**: `allin-packages/platform-module/tsconfig.json`
-  - [ ] 创建`vitest.config.ts`文件,配置测试环境
+  - [x] 创建`vitest.config.ts`文件,配置测试环境
     - **参考文件**: `allin-packages/platform-module/vitest.config.ts`
-  - [ ] 创建`src/`目录结构:`entities/`, `services/`, `routes/`, `schemas/`, `types/`
+  - [x] 创建`src/`目录结构:`entities/`, `services/`, `routes/`, `schemas/`, `types/`
     - **参考结构**: `allin-packages/platform-module/src/`目录结构
-- [ ] 完成实体转换:`DisabledPerson`、`DisabledBankCard`、`DisabledPhoto`、`DisabledRemark`、`DisabledVisit`实体转换 (AC: 2)
-  - [ ] 分析源实体`allin_system-master/server/src/disability_person/disabled_person.entity.ts`
+- [x] 完成实体转换:`DisabledPerson`、`DisabledBankCard`、`DisabledPhoto`、`DisabledRemark`、`DisabledVisit`实体转换 (AC: 2)
+  - [x] 分析源实体`allin_system-master/server/src/disability_person/disabled_person.entity.ts`
     - **源文件**: `allin_system-master/server/src/disability_person/disabled_person.entity.ts`
     - **关键字段**: `person_id`, `name`, `gender`, `id_card`, `disability_id`, `disability_type`, `disability_level`, `id_address`, `phone`, `province`, `city`, `district`等
     - **注意吸取经验**: 根据故事007.006的经验,主键属性名应直接定义为`id`(而不是`personId`)以遵循GenericCrudService约定
     - **迁移文件路径**: `allin-packages/disability-module/src/entities/disabled-person.entity.ts`
-  - [ ] 创建转换后的实体文件`src/entities/disabled-person.entity.ts`
+  - [x] 创建转换后的实体文件`src/entities/disabled-person.entity.ts`
     - **参考文件**: `allin-packages/platform-module/src/entities/platform.entity.ts`
     - **转换要点**: 下划线命名 → 驼峰命名,添加详细TypeORM配置
     - **将下划线字段名转换为驼峰命名**: `person_id` → `id`, `id_card` → `idCard`, `disability_id` → `disabilityId`等
     - **添加详细的TypeORM装饰器配置**: `@Column({ name: 'person_id', type: 'int', primary: true, generated: true })`
     - **保持数据库表名不变**: `@Entity('disabled_person')`
     - **区域包集成**: 考虑添加`provinceId`, `cityId`, `districtId`字段引用`AreaEntity`(可选)
-  - [ ] 分析源实体`allin_system-master/server/src/disability_person/disabled_bank_card.entity.ts`
+  - [x] 分析源实体`allin_system-master/server/src/disability_person/disabled_bank_card.entity.ts`
     - **源文件**: `allin_system-master/server/src/disability_person/disabled_bank_card.entity.ts`
     - **迁移文件路径**: `allin-packages/disability-module/src/entities/disabled-bank-card.entity.ts`
-  - [ ] 分析源实体`allin_system-master/server/src/disability_person/disabled_photo.entity.ts`
+  - [x] 分析源实体`allin_system-master/server/src/disability_person/disabled_photo.entity.ts`
     - **源文件**: `allin_system-master/server/src/disability_person/disabled_photo.entity.ts`
     - **关键字段**: `photo_id`, `person_id`, `photo_type`, `photo_url`, `upload_time`
     - **迁移文件路径**: `allin-packages/disability-module/src/entities/disabled-photo.entity.ts`
-  - [ ] 分析源实体`allin_system-master/server/src/disability_person/disabled_remark.entity.ts`
+  - [x] 分析源实体`allin_system-master/server/src/disability_person/disabled_remark.entity.ts`
     - **源文件**: `allin_system-master/server/src/disability_person/disabled_remark.entity.ts`
     - **迁移文件路径**: `allin-packages/disability-module/src/entities/disabled-remark.entity.ts`
-  - [ ] 分析源实体`allin_system-master/server/src/disability_person/disabled_visit.entity.ts`
+  - [x] 分析源实体`allin_system-master/server/src/disability_person/disabled_visit.entity.ts`
     - **源文件**: `allin_system-master/server/src/disability_person/disabled_visit.entity.ts`
     - **迁移文件路径**: `allin-packages/disability-module/src/entities/disabled-visit.entity.ts`
-- [ ] **文件模块集成**:使用`@d8d/file-module`包管理照片文件,修改`DisabledPhoto`实体添加`fileId`字段 (AC: 3)
-  - [ ] 分析`@d8d/file-module`包结构和API
+- [x] **文件模块集成**:使用`@d8d/file-module`包管理照片文件,修改`DisabledPhoto`实体添加`fileId`字段 (AC: 3)
+  - [x] 分析`@d8d/file-module`包结构和API
     - **参考文件**: `packages/file-module/src/entities/file.entity.ts`
     - **关键方法**: 文件上传、下载、删除、获取URL
-  - [ ] 修改`DisabledPhoto`实体,添加`fileId`字段引用`File`实体
+  - [x] 修改`DisabledPhoto`实体,添加`fileId`字段引用`File`实体
     - **修改方案**: 移除`photo_url`字符串字段,添加`fileId`外键字段
     - **外键关系**: `@ManyToOne(() => FileEntity)`,`@JoinColumn({ name: 'file_id' })`
     - **数据迁移**: 需要制定现有URL数据迁移到files表的方案
@@ -73,38 +73,38 @@ Draft
     - **错误处理**: 文件不存在时返回相应错误信息
   - [ ] 在API响应中返回文件完整信息
     - **响应格式**: 包含文件ID和文件URL的完整信息
-- [ ] 完成服务层转换:残疾人业务逻辑,包含聚合服务 (AC: 4)
-  - [ ] 分析源服务`allin_system-master/server/src/disability_person/disabled_person.service.ts`
+- [x] 完成服务层转换:残疾人业务逻辑,包含聚合服务 (AC: 4)
+  - [x] 分析源服务`allin_system-master/server/src/disability_person/disabled_person.service.ts`
     - **源文件**: `allin_system-master/server/src/disability_person/disabled_person.service.ts`
     - **关键方法**: `create`, `findOne`, `findAll`, `findByIdCard`, `update`, `delete`
     - **业务逻辑**: 身份证号唯一性检查,关联查询其他模块实体
-  - [ ] 创建转换后的服务文件`src/services/disabled-person.service.ts`
+  - [x] 创建转换后的服务文件`src/services/disabled-person.service.ts`
     - **参考文件**: `allin-packages/platform-module/src/services/platform.service.ts`
     - **架构**: 继承`GenericCrudService<DisabledPerson>`
     - **迁移文件路径**: `allin-packages/disability-module/src/services/disabled-person.service.ts`
-  - [ ] 分析源聚合服务`allin_system-master/server/src/disability_person/aggregated.service.ts`
+  - [x] 分析源聚合服务`allin_system-master/server/src/disability_person/aggregated.service.ts`
     - **源文件**: `allin_system-master/server/src/disability_person/aggregated.service.ts`
     - **关键方法**: `createDisabledPersonWithAllData`, `findDisabledPersonWithAllData`
     - **聚合逻辑**: 一次性创建/查询残疾人所有相关信息
-  - [ ] 创建聚合服务文件`src/services/aggregated.service.ts`
+  - [x] 创建聚合服务文件`src/services/aggregated.service.ts`
     - **迁移文件路径**: `allin-packages/disability-module/src/services/aggregated.service.ts`
-  - [ ] 继承`GenericCrudService<DisabledPerson>`,配置搜索字段
+  - [x] 继承`GenericCrudService<DisabledPerson>`,配置搜索字段
     - **参考**: `packages/shared-crud/src/services/generic-crud.service.ts`
     - **搜索字段**: 姓名、身份证号、残疾证号、残疾类型、残疾等级等
-  - [ ] 覆盖`create`方法:添加身份证号唯一性检查
+  - [x] 覆盖`create`方法:添加身份证号唯一性检查
     - **源逻辑**: `disabled_person.controller.ts:18-22` - 检查身份证号是否已存在
     - **新逻辑**: 在服务层检查`idCard`的唯一性
-  - [ ] 覆盖`findAll`方法:需要返回`{ data: DisabledPerson[], total: number }`格式
+  - [x] 覆盖`findAll`方法:需要返回`{ data: DisabledPerson[], total: number }`格式
     - **源逻辑**: `disabled_person.service.ts:44-...` - 支持多条件查询和分页
     - **新逻辑**: 支持按姓名、身份证号、残疾类型等条件查询
-  - [ ] 自定义`findByIdCard`方法:按身份证号查询
+  - [x] 自定义`findByIdCard`方法:按身份证号查询
     - **源逻辑**: `disabled_person.service.ts:...` - 根据身份证号查询
     - **新逻辑**: 根据`idCard`查询,支持身份证号参数
-  - [ ] 集成文件服务验证逻辑
+  - [x] 集成文件服务验证逻辑
     - **文件验证**: 使用`FileService`验证文件ID的有效性
     - **错误处理**: 文件不存在时抛出相应异常
-- [ ] 完成路由层转换:Hono路由实现,包含聚合路由 (AC: 5)
-  - [ ] 分析源控制器`allin_system-master/server/src/disability_person/disabled_person.controller.ts`
+- [x] 完成路由层转换:Hono路由实现,包含聚合路由 (AC: 5)
+  - [x] 分析源控制器`allin_system-master/server/src/disability_person/disabled_person.controller.ts`
     - **源文件**: `allin_system-master/server/src/disability_person/disabled_person.controller.ts`
     - **API端点**:
       - `POST /disability-persons` - 创建残疾人基本信息
@@ -113,61 +113,84 @@ Draft
       - `PUT /disability-persons/:id` - 更新残疾人信息
       - `DELETE /disability-persons/:id` - 删除残疾人信息
     - **认证**: 所有端点需要JWT认证 (`@UseGuards(JwtAuthGuard)`)
-  - [ ] 分析源聚合控制器`allin_system-master/server/src/disability_person/aggregated.controller.ts`
+  - [x] 分析源聚合控制器`allin_system-master/server/src/disability_person/aggregated.controller.ts`
     - **源文件**: `allin_system-master/server/src/disability_person/aggregated.controller.ts`
     - **API端点**:
       - `POST /disability-persons/aggregated/create` - 聚合创建残疾人所有信息
       - `GET /disability-persons/aggregated/:personId` - 聚合查询残疾人所有信息
-  - [ ] 创建自定义路由文件`src/routes/disabled-person-custom.routes.ts`
+  - [x] 创建自定义路由文件`src/routes/disabled-person-custom.routes.ts`
     - **参考文件**: `allin-packages/platform-module/src/routes/platform-custom.routes.ts`
     - **迁移文件路径**: `allin-packages/disability-module/src/routes/disabled-person-custom.routes.ts`
-  - [ ] 创建聚合路由文件`src/routes/aggregated.routes.ts`
+    - **实现功能**: 创建残疾人、删除残疾人、更新残疾人、获取所有残疾人、搜索残疾人、获取单个残疾人、根据身份证号查询、批量创建残疾人
+  - [x] 创建聚合路由文件`src/routes/aggregated.routes.ts`
     - **迁移文件路径**: `allin-packages/disability-module/src/routes/aggregated.routes.ts`
-  - [ ] 创建CRUD路由文件`src/routes/disabled-person-crud.routes.ts`
+    - **实现功能**: 创建聚合残疾人信息、获取聚合残疾人信息、更新聚合残疾人信息
+  - [x] 创建CRUD路由文件`src/routes/disabled-person-crud.routes.ts`
     - **参考文件**: `allin-packages/platform-module/src/routes/platform-crud.routes.ts`
     - **架构**: 使用`createCrudRoutes`生成标准CRUD路由
-  - [ ] 创建主路由文件`src/routes/disabled-person.routes.ts`
+    - **配置**: 设置readOnly: true,关联关系包含所有5个关联实体
+  - [x] 创建主路由文件`src/routes/disabled-person.routes.ts`
     - **参考文件**: `allin-packages/platform-module/src/routes/platform.routes.ts`
     - **功能**: 聚合自定义路由、CRUD路由和聚合路由,导出路由实例
-- [ ] 完成验证系统转换:Zod Schema定义 (AC: 6)
-  - [ ] 分析源DTO`allin_system-master/server/src/disability_person/disabled_person.dto.ts`
+  - [x] 创建路由导出文件`src/routes/index.ts`
+    - **功能**: 导出所有路由模块
+- [x] 完成验证系统转换:Zod Schema定义 (AC: 6)
+  - [x] 分析源DTO`allin_system-master/server/src/disability_person/disabled_person.dto.ts`
     - **源文件**: `allin_system-master/server/src/disability_person/disabled_person.dto.ts`
     - **DTO类型**: `CreateDisabledPersonDto`, `UpdateDisabledPersonDto`, `QueryDisabledPersonDto`
-  - [ ] 分析源聚合DTO`allin_system-master/server/src/disability_person/aggregated.dto.ts`
+  - [x] 分析源聚合DTO`allin_system-master/server/src/disability_person/aggregated.dto.ts`
     - **源文件**: `allin_system-master/server/src/disability_person/aggregated.dto.ts`
     - **DTO类型**: `CreateDisabledPersonWithAllDataDto`
-  - [ ] 创建转换后的Schema文件`src/schemas/disabled-person.schema.ts`
+  - [x] 分析源关联实体DTO:`disabled_bank_card.dto.ts`, `disabled_photo.dto.ts`, `disabled_remark.dto.ts`, `disabled_visit.dto.ts`
+  - [x] 创建转换后的Schema文件`src/schemas/disabled-person.schema.ts`
     - **参考文件**: `allin-packages/platform-module/src/schemas/platform.schema.ts`
     - **迁移文件路径**: `allin-packages/disability-module/src/schemas/disabled-person.schema.ts`
-  - [ ] 创建聚合Schema文件`src/schemas/aggregated.schema.ts`
-    - **迁移文件路径**: `allin-packages/disability-module/src/schemas/aggregated.schema.ts`
-  - [ ] 使用`z.object()`定义`CreateDisabledPersonSchema`, `UpdateDisabledPersonSchema`, `QueryDisabledPersonSchema`, `CreateDisabledPersonWithAllDataSchema`
-  - [ ] 添加详细的验证规则:身份证号格式、手机号格式、日期格式、必填字段、可选字段
-- [ ] 配置package.json:依赖管理,包含对`@d8d/file-module`的依赖 (AC: 7)
-  - [ ] 配置`package.json`中的`name`字段为`@d8d/allin-disability-module`
+    - **包含Schema**:
+      - `DisabledPersonSchema`: 残疾人实体Schema
+      - `CreateDisabledPersonSchema`: 创建残疾人DTO
+      - `UpdateDisabledPersonSchema`: 更新残疾人DTO
+      - `DeleteDisabledPersonSchema`: 删除残疾人DTO
+      - `PaginationQuerySchema`: 分页查询参数
+      - `SearchDisabledPersonQuerySchema`: 搜索残疾人参数
+      - `DisabledBankCardSchema`: 银行卡实体Schema
+      - `DisabledPhotoSchema`: 照片实体Schema(已集成文件模块,使用fileId字段)
+      - `DisabledRemarkSchema`: 备注实体Schema
+      - `DisabledVisitSchema`: 回访实体Schema
+      - `CreateAggregatedDisabledPersonSchema`: 创建聚合残疾人信息Schema
+      - `AggregatedDisabledPersonSchema`: 聚合残疾人信息Schema
+  - [x] 创建schemas导出文件`src/schemas/index.ts`
+    - **功能**: 导出所有Schema定义
+  - [x] 添加详细的验证规则:身份证号格式、手机号格式、日期格式、必填字段、可选字段
+    - **身份证号**: 长度1-20字符
+    - **手机号**: 长度1-20字符
+    - **日期**: 使用datetime格式验证
+    - **必填字段**: 姓名、性别、身份证号、残疾证号、残疾类型、残疾等级、身份证地址、联系方式、省级、市级
+    - **可选字段**: 身份证有效期、残疾证有效期、是否可直接联系、是否已婚、民族、出生日期、年龄、学历、毕业院校、专业、区县级、详细地址、邮政编码、是否在黑名单中、在职状态
+- [x] 配置package.json:依赖管理,包含对`@d8d/file-module`的依赖 (AC: 7)
+  - [x] 配置`package.json`中的`name`字段为`@d8d/allin-disability-module`
     - **参考文件**: `allin-packages/platform-module/package.json`
-  - [ ] 设置`type: "module"`和主入口`src/index.ts`
-  - [ ] 添加workspace依赖:`@d8d/core-module`, `@d8d/shared-crud`, `@d8d/shared-utils`, `@d8d/file-module`
-  - [ ] 添加外部依赖:`@hono/zod-openapi`, `typeorm`, `zod`
-  - [ ] 配置导出路径:`services`, `schemas`, `routes`, `entities`
-- [ ] 编写API集成测试:验证残疾人管理功能,包含文件集成测试 (AC: 8)
-  - [ ] 创建测试文件`tests/integration/disability.integration.test.ts`
+  - [x] 设置`type: "module"`和主入口`src/index.ts`
+  - [x] 添加workspace依赖:`@d8d/shared-crud`, `@d8d/shared-utils`, `@d8d/file-module`, `@d8d/auth-module`, `@d8d/user-module`
+  - [x] 添加外部依赖:`@hono/zod-openapi`, `typeorm`, `zod`
+  - [x] 配置导出路径:`services`, `schemas`, `routes`, `entities`
+- [x] 编写API集成测试:验证残疾人管理功能,包含文件集成测试 (AC: 8)
+  - [x] 创建测试文件`tests/integration/disability.integration.test.ts`
     - **参考文件**: `allin-packages/platform-module/tests/integration/platform.integration.test.ts`
     - **迁移文件路径**: `allin-packages/disability-module/tests/integration/disability.integration.test.ts`
-  - [ ] 参考`platform-module`的集成测试模式
+  - [x] 参考`platform-module`的集成测试模式
     - **测试模式**: 使用`testClient`, `setupIntegrationDatabaseHooksWithEntities`
-  - [ ] 使用`testClient`创建测试客户端
-  - [ ] 使用`setupIntegrationDatabaseHooksWithEntities`设置测试数据库
+  - [x] 使用`testClient`创建测试客户端
+  - [x] 使用`setupIntegrationDatabaseHooksWithEntities`设置测试数据库
     - **工具**: `@d8d/shared-test-util`中的测试基础设施
     - **实体**: 包含`DisabledPerson`, `DisabledBankCard`, `DisabledPhoto`, `DisabledRemark`, `DisabledVisit`, `FileEntity`
-  - [ ] 编写测试用例覆盖所有端点:创建、查询、更新、删除、聚合创建、聚合查询
-  - [ ] **文件集成测试重点**:
+  - [x] 编写测试用例覆盖所有端点:创建、查询、更新、删除、聚合创建、聚合查询
+  - [x] **文件集成测试重点**:
     - 验证文件ID的有效性检查
     - 测试文件不存在时的错误处理
     - 验证文件数据在响应中的完整性
-  - [ ] 添加认证测试、数据验证测试、错误处理测试
-  - [ ] 包含边界条件和异常场景测试
-  - [ ] 特别测试身份证号唯一性检查和聚合服务功能
+  - [x] 添加认证测试、数据验证测试、错误处理测试
+  - [x] 包含边界条件和异常场景测试
+  - [x] 特别测试身份证号唯一性检查和聚合服务功能
 - [ ] 通过类型检查和基本测试验证 (AC: 9)
   - [ ] 运行`pnpm typecheck`确保无类型错误
   - [ ] 运行`pnpm test`确保所有测试通过

+ 186 - 0
pnpm-lock.yaml

@@ -67,6 +67,134 @@ importers:
         specifier: ^3.2.4
         version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.1)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.64.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)
 
+  allin-packages/company-module:
+    dependencies:
+      '@d8d/allin-platform-module':
+        specifier: workspace:*
+        version: link:../platform-module
+      '@d8d/auth-module':
+        specifier: workspace:*
+        version: link:../../packages/auth-module
+      '@d8d/file-module':
+        specifier: workspace:*
+        version: link:../../packages/file-module
+      '@d8d/shared-crud':
+        specifier: workspace:*
+        version: link:../../packages/shared-crud
+      '@d8d/shared-types':
+        specifier: workspace:*
+        version: link:../../packages/shared-types
+      '@d8d/shared-utils':
+        specifier: workspace:*
+        version: link:../../packages/shared-utils
+      '@d8d/user-module':
+        specifier: workspace:*
+        version: link:../../packages/user-module
+      '@hono/zod-openapi':
+        specifier: ^1.0.2
+        version: 1.0.2(hono@4.8.5)(zod@4.1.12)
+      hono:
+        specifier: ^4.8.5
+        version: 4.8.5
+      typeorm:
+        specifier: ^0.3.20
+        version: 0.3.27(ioredis@5.8.2)(pg@8.16.3)(redis@4.7.1)(reflect-metadata@0.2.2)
+      zod:
+        specifier: ^4.1.12
+        version: 4.1.12
+    devDependencies:
+      '@d8d/shared-test-util':
+        specifier: workspace:*
+        version: link:../../packages/shared-test-util
+      '@types/node':
+        specifier: ^22.10.2
+        version: 22.19.1
+      '@typescript-eslint/eslint-plugin':
+        specifier: ^8.18.1
+        version: 8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.8.3)
+      '@typescript-eslint/parser':
+        specifier: ^8.18.1
+        version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.8.3)
+      eslint:
+        specifier: ^9.17.0
+        version: 9.38.0(jiti@2.6.1)
+      typescript:
+        specifier: ^5.8.3
+        version: 5.8.3
+      vitest:
+        specifier: ^3.2.4
+        version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.1)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.64.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)
+
+  allin-packages/disability-module:
+    dependencies:
+      '@d8d/auth-module':
+        specifier: workspace:*
+        version: link:../../packages/auth-module
+      '@d8d/file-module':
+        specifier: workspace:*
+        version: link:../../packages/file-module
+      '@d8d/shared-crud':
+        specifier: workspace:*
+        version: link:../../packages/shared-crud
+      '@d8d/shared-types':
+        specifier: workspace:*
+        version: link:../../packages/shared-types
+      '@d8d/shared-utils':
+        specifier: workspace:*
+        version: link:../../packages/shared-utils
+      '@d8d/user-module':
+        specifier: workspace:*
+        version: link:../../packages/user-module
+      '@hono/zod-openapi':
+        specifier: ^1.0.2
+        version: 1.0.2(hono@4.8.5)(zod@4.1.12)
+      hono:
+        specifier: ^4.8.5
+        version: 4.8.5
+      typeorm:
+        specifier: ^0.3.20
+        version: 0.3.27(ioredis@5.8.2)(pg@8.16.3)(redis@4.7.1)(reflect-metadata@0.2.2)
+      zod:
+        specifier: ^4.1.12
+        version: 4.1.12
+    devDependencies:
+      '@d8d/shared-test-util':
+        specifier: workspace:*
+        version: link:../../packages/shared-test-util
+      '@types/node':
+        specifier: ^22.10.2
+        version: 22.19.1
+      '@typescript-eslint/eslint-plugin':
+        specifier: ^8.18.1
+        version: 8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.8.3)
+      '@typescript-eslint/parser':
+        specifier: ^8.18.1
+        version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.8.3)
+      eslint:
+        specifier: ^9.17.0
+        version: 9.38.0(jiti@2.6.1)
+      typescript:
+        specifier: ^5.8.3
+        version: 5.8.3
+      vitest:
+        specifier: ^3.2.4
+        version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.1)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.64.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)
+
+  allin-packages/enums:
+    devDependencies:
+      '@types/node':
+        specifier: ^22.10.2
+        version: 22.19.1
+      '@vitest/coverage-v8':
+        specifier: ^3.2.4
+        version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.1)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.64.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))
+      typescript:
+        specifier: ^5.8.3
+        version: 5.8.3
+      vitest:
+        specifier: ^3.2.4
+        version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.1)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.64.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)
+
   allin-packages/platform-module:
     dependencies:
       '@d8d/auth-module':
@@ -122,6 +250,64 @@ importers:
         specifier: ^3.2.4
         version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.1)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.64.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)
 
+  allin-packages/salary-module:
+    dependencies:
+      '@d8d/auth-module':
+        specifier: workspace:*
+        version: link:../../packages/auth-module
+      '@d8d/file-module':
+        specifier: workspace:*
+        version: link:../../packages/file-module
+      '@d8d/geo-areas':
+        specifier: workspace:*
+        version: link:../../packages/geo-areas
+      '@d8d/shared-crud':
+        specifier: workspace:*
+        version: link:../../packages/shared-crud
+      '@d8d/shared-types':
+        specifier: workspace:*
+        version: link:../../packages/shared-types
+      '@d8d/shared-utils':
+        specifier: workspace:*
+        version: link:../../packages/shared-utils
+      '@d8d/user-module':
+        specifier: workspace:*
+        version: link:../../packages/user-module
+      '@hono/zod-openapi':
+        specifier: ^1.0.2
+        version: 1.0.2(hono@4.8.5)(zod@4.1.12)
+      hono:
+        specifier: ^4.8.5
+        version: 4.8.5
+      typeorm:
+        specifier: ^0.3.20
+        version: 0.3.27(ioredis@5.8.2)(pg@8.16.3)(redis@4.7.1)(reflect-metadata@0.2.2)
+      zod:
+        specifier: ^4.1.12
+        version: 4.1.12
+    devDependencies:
+      '@d8d/shared-test-util':
+        specifier: workspace:*
+        version: link:../../packages/shared-test-util
+      '@types/node':
+        specifier: ^22.10.2
+        version: 22.19.1
+      '@typescript-eslint/eslint-plugin':
+        specifier: ^8.18.1
+        version: 8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.8.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.8.3)
+      '@typescript-eslint/parser':
+        specifier: ^8.18.1
+        version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.8.3)
+      eslint:
+        specifier: ^9.17.0
+        version: 9.38.0(jiti@2.6.1)
+      typescript:
+        specifier: ^5.8.3
+        version: 5.8.3
+      vitest:
+        specifier: ^3.2.4
+        version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.1)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.64.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)
+
   mini:
     dependencies:
       '@babel/runtime':