# Story 009.004: 银行卡管理优化

## Status
Draft

## Story
**As a** 残疾人信息管理员
**I want** 优化银行卡管理功能
**so that** 更便捷地管理残疾人的银行卡信息

## Acceptance Criteria
1. 银行名称改为选项式，支持用户自行添加（参考省份选择逻辑）
2. 修复银行卡照片无法上传的问题
3. 增加银行卡类型选择项（一类卡/二类卡）

## Tasks / Subtasks
- [ ] 创建银行名称模块（基于广告类型模块） (AC: 1)
  - [ ] 复制 `packages/advertisements-module` → `packages/bank-names-module`
  - [ ] 修改实体：`AdvertisementType` → `BankName`，表名：`ad_type` → `bank_name`
  - [ ] 适配字段：name→银行名称，code→银行代码，remark→备注，status→状态
  - [ ] 更新路由、服务、schema等所有相关文件
- [ ] 创建银行名称管理UI（基于广告类型管理UI） (AC: 1)
  - [ ] 复制 `packages/advertisement-type-management-ui` → `packages/bank-name-management-ui`
  - [ ] 重命名组件：`AdvertisementTypeManagement` → `BankNameManagement`
  - [ ] 重命名组件：`AdvertisementTypeSelector` → `BankNameSelector`
  - [ ] 更新API客户端和类型定义
- [ ] 更新残疾人模块建立银行名称关联 (AC: 1)
  - [ ] 修改 `DisabledBankCard` 实体，将 `bankName` 字段改为外键关联
  - [ ] 更新相关schema和DTO，支持银行名称ID引用
  - [ ] 更新数据库迁移脚本
- [ ] 创建银行选择组件 (AC: 1)
  - [ ] 基于 `BankNameSelector` 组件创建 `BankSelect` 组件
  - [ ] 支持选项式选择（从银行名称模块获取数据）
  - [ ] 支持用户自定义输入（参考AreaSelect组件的省份选择逻辑）
  - [ ] 集成到 `BankCardManagement` 组件中
- [ ] 修复银行卡照片上传功能 (AC: 2)
  - [ ] 检查FileSelector组件的集成问题
  - [ ] 修复文件ID绑定逻辑：从FileSelector选择照片后，确保表单中的fileId字段正确更新
- [ ] 添加银行卡类型选择字段 (AC: 3)
  - [ ] 在BankCardManagement组件中添加银行卡类型字段
  - [ ] 创建银行卡类型枚举（一类卡/二类卡）
  - [ ] 更新相关schema和类型定义
- [ ] 更新测试用例 (AC: 1, 2, 3, 4)
  - [ ] 更新BankCardManagement组件的集成测试
  - [ ] 添加银行选择组件测试
  - [ ] 添加银行卡类型选择测试
  - [ ] 验证选择文件ID功能测试

## Dev Notes

### 技术栈信息 [Source: architecture/tech-stack.md]
- **前端框架**: React 19.1.0 + TypeScript
- **UI组件库**: shadcn/ui (基于Radix UI)
- **状态管理**: @tanstack/react-query (服务端状态) + Context (本地状态)
- **表单处理**: react-hook-form + zodResolver
- **HTTP客户端**: 基于Hono Client的RPC封装
- **样式**: Tailwind CSS 4.1.11
- **构建工具**: Vite 7.0.0

### 项目结构信息 [Source: architecture/source-tree.md]
- **UI包位置**: `allin-packages/disability-person-management-ui/`
- **组件文件**: `src/components/BankCardManagement.tsx`
- **API客户端**: `src/api/disabilityClient.ts`
- **测试文件**: `tests/integration/disability-person.integration.test.tsx`
- **后端模块**: `allin-packages/disability-module/`
- **Schema文件**: `allin-packages/disability-module/src/schemas/disabled-person.schema.ts`

### 新模块结构信息
- **银行名称模块**: `packages/bank-names-module/`
  - 实体文件: `src/entities/bank-name.entity.ts`
  - 路由文件: `src/routes/bank-names.ts`
  - Schema文件: `src/schemas/bank-name.schema.ts`
  - 服务文件: `src/services/bank-name.service.ts`
- **银行名称管理UI**: `packages/bank-name-management-ui/`
  - 管理组件: `src/components/BankNameManagement.tsx`
  - 选择器组件: `src/components/BankNameSelector.tsx`
  - API客户端: `src/api/bankNameClient.ts`
  - 类型定义: `src/types/bankName.ts`
- **银行选择组件**: `allin-packages/disability-person-management-ui/src/components/BankSelect.tsx`

### 数据模型信息 [Source: architecture/data-model-schema-changes.md]
**银行卡实体Schema** (来自disabled-person.schema.ts:268-319):
```typescript
export const DisabledBankCardSchema = z.object({
  id: z.number().int().positive(),
  personId: z.number().int().positive(),
  subBankName: z.string().max(100),      // 发卡支行
  bankName: z.string().max(50),          // 银行名称
  cardNumber: z.string().max(50),        // 卡号
  cardholderName: z.string().max(50),    // 持卡人姓名
  fileId: z.number().int().positive(),   // 银行卡照片文件ID
  file: FileSchema.nullable().optional(), // 银行卡照片文件实体
  isDefault: z.number().int().min(0).max(1).default(0) // 是否默认
});
```

**当前BankCardManagement组件字段**:
- `subBankName`: 发卡支行（当前为必填）
- `bankName`: 银行名称（当前为必填，需要改为选项式）
- `cardNumber`: 银行卡号（当前为必填）
- `cardholderName`: 持卡人姓名（当前为必填）
- `fileId`: 银行卡照片文件ID（当前可选，但上传有问题）
- `isDefault`: 是否默认银行卡（当前可选）

**需要新增字段**:
- `cardType`: 银行卡类型（一类卡/二类卡）
- `bankNameId`: 银行名称ID（外键关联，替换原来的bankName字符串字段）

**银行名称实体Schema** (基于广告类型实体适配):
```typescript
@Entity('bank_name')
export class BankName {
  @PrimaryGeneratedColumn({ unsigned: true })
  id!: number;

  @Column({
    name: 'name',
    type: 'varchar',
    length: 50,
    comment: '银行名称'
  })
  name!: string;

  @Column({
    name: 'code',
    type: 'varchar',
    length: 20,
    unique: true,
    comment: '银行代码'
  })
  code!: string;

  @Column({
    name: 'remark',
    type: 'varchar',
    length: 100,
    nullable: true,
    comment: '备注'
  })
  remark!: string | null;

  @CreateDateColumn({
    name: 'created_at',
    type: 'timestamp',
    comment: '创建时间'
  })
  createdAt!: Date;

  @UpdateDateColumn({
    name: 'updated_at',
    type: 'timestamp',
    comment: '更新时间'
  })
  updatedAt!: Date;

  @Column({
    name: 'created_by',
    type: 'int',
    unsigned: true,
    nullable: true,
    comment: '创建用户ID'
  })
  createdBy!: number | null;

  @Column({
    name: 'updated_by',
    type: 'int',
    unsigned: true,
    nullable: true,
    comment: '更新用户ID'
  })
  updatedBy!: number | null;

  @Column({
    name: 'status',
    type: 'int',
    default: 0,
    comment: '状态 0禁用 1启用'
  })
  status!: number;
}
```

### UI包开发规范 [Source: architecture/ui-package-standards.md]
**必须遵循的规范**:
1. **API路径映射验证**: 开发前必须验证故事中的API路径映射与实际后端路由定义的一致性
2. **类型推断最佳实践**: 必须使用RPC推断类型，而不是直接导入schema类型
3. **测试选择器优化**: 必须为关键交互元素添加`data-testid`属性
4. **表单组件模式**: 必须使用条件渲染两个独立的Form组件
5. **API调用一致性**: 必须根据实际路由名称修正API调用

**参考现有组件模式**:
- **AreaSelect组件**: 参考省份选择逻辑实现银行选择组件
- **PlatformManagement组件**: 参考表单处理模式，确保一致性
- **广告类型管理UI包**: `packages/advertisement-type-management-ui` 作为银行名称模块的参考实现
- **广告类型选择器**: `AdvertisementTypeSelector` 组件作为 `BankNameSelector` 的参考实现

**银行名称模块特定规范**:
1. **模块复制规范**: 必须完整复制广告类型模块结构，保持一致的目录组织和文件命名
2. **实体适配规范**: 修改实体时需同步更新表名、字段注释和业务含义
3. **API路径映射**: 银行名称模块的API路径需与广告类型模块保持一致模式
4. **类型安全**: 使用RPC推断类型，确保前端类型与后端schema同步
5. **测试覆盖**: 新模块需达到与广告类型模块相同的测试覆盖率

### 组件架构信息 [Source: architecture/component-architecture.md]
**前端组件架构**:
- 使用React 19.1.0 + TypeScript
- 路由: React Router v7
- 状态管理: @tanstack/react-query + Context
- UI组件库: shadcn/ui (基于Radix UI)

**文件管理服务**:
- 已实现完整功能，支持分段上传、预签名URL生成
- 核心功能: 单文件上传（≤5MB）、多部分分段上传（>5MB大文件）
- 预签名URL生成（支持下载和直接访问）

### 编码标准和测试策略 [Source: architecture/coding-standards.md]
**测试框架**: 使用Vitest + Testing Library + hono/testing + Playwright
**测试位置**: `tests/` 文件夹与源码并列
**覆盖率目标**: 核心业务逻辑 > 80%
**测试类型**: 单元测试、集成测试、E2E测试

**关键集成规则**:
- 现有API兼容性: 确保测试不破坏现有API契约
- 数据库集成: 使用测试数据库，避免污染生产数据
- 错误处理: 测试各种错误场景和边界条件
- 日志一致性: 测试日志格式和错误信息

### 测试
**测试文件位置**: `allin-packages/disability-person-management-ui/tests/integration/disability-person.integration.test.tsx`

**测试标准**:
- 使用Vitest作为测试框架
- 使用Testing Library进行React组件测试
- 集成测试验证组件与API的交互
- 必须添加`data-testid`属性用于测试选择器
- 参考现有集成测试模式进行测试编写

**测试要求**:
1. 验证银行选择组件功能（选项选择、自定义输入）
2. 验证银行卡照片上传功能修复
3. 验证银行卡类型选择功能
4. 验证表单验证规则更新（仅必要字段必填）
5. 确保现有功能不受影响

**Radix UI组件测试环境修复** (基于故事008.007经验):
- 在测试环境中使用Radix UI组件时，必须添加必要的DOM API mock
- 特别是Select、DropdownMenu等组件需要`scrollIntoView` mock
- 在测试setup文件中添加: `Element.prototype.scrollIntoView = vi.fn();`

### 项目结构对齐检查
**当前结构**:
- `allin-packages/disability-person-management-ui/src/components/BankCardManagement.tsx`
- `allin-packages/disability-module/src/schemas/disabled-person.schema.ts`
- `allin-packages/disability-person-management-ui/tests/integration/disability-person.integration.test.tsx`

**需要创建/修改的文件**:
1. 银行选择组件（新组件）
2. BankCardManagement.tsx（修改现有组件）
3. 相关类型定义文件
4. 集成测试文件更新

**结构一致性**: 符合现有UI包结构规范，无需调整项目结构。

### 技术实现方案（基于广告类型模块）

**银行名称模块架构**:
基于现有广告类型模块(`packages/advertisements-module`)和广告类型管理UI(`packages/advertisement-type-management-ui`)进行复制和适配：

1. **后端模块复制与适配**:
   - 复制 `packages/advertisements-module` → `packages/bank-names-module`
   - 修改实体：`AdvertisementType` → `BankName`
   - 修改表名：`ad_type` → `bank_name`
   - 字段适配：
     - `name`: 银行名称（如：中国工商银行）
     - `code`: 银行代码（如：ICBC）
     - `remark`: 备注信息
     - `status`: 状态（0禁用/1启用）

2. **管理UI复制与适配**:
   - 复制 `packages/advertisement-type-management-ui` → `packages/bank-name-management-ui`
   - 组件重命名：`AdvertisementTypeManagement` → `BankNameManagement`
   - 组件重命名：`AdvertisementTypeSelector` → `BankNameSelector`
   - API客户端适配：`advertisementTypeClient` → `bankNameClient`
   - 类型定义适配：`AdvertisementType` → `BankName`

3. **与残疾人银行卡实体关联**:
   - 在 `allin-packages/disability-module` 中建立关联
   - 修改 `DisabledBankCard` 实体，将 `bankName` 字段改为外键关联
   - 更新相关schema和DTO，支持银行名称ID引用

4. **银行选择组件实现**:
   - 基于 `BankNameSelector` 组件创建 `BankSelect` 组件
   - 支持选项式选择（从银行名称模块获取数据）
   - 支持用户自定义输入（参考省份选择逻辑）
   - 集成到 `BankCardManagement` 组件中

**实施步骤**:
1. 创建银行名称模块（后端）
2. 创建银行名称管理UI（管理界面）
3. 更新残疾人模块，建立银行名称关联
4. 创建银行选择组件
5. 更新BankCardManagement组件集成银行选择
6. 修复照片上传功能
7. 添加银行卡类型字段
8. 优化表单验证规则
9. 更新测试用例

**参考实现**:
- 广告类型模块：`packages/advertisements-module/`
- 广告类型管理UI：`packages/advertisement-type-management-ui/`
- 省份选择组件：参考AreaSelect组件逻辑

## Change Log
| Date | Version | Description | Author |
|------|---------|-------------|--------|
| 2025-12-10 | 1.0 | 初始故事创建 | Bob (Scrum Master) |
| 2025-12-10 | 1.1 | 添加基于广告类型模块的技术实现方案<br>- 银行名称模块架构设计<br>- 银行名称管理UI复制方案<br>- 与残疾人银行卡实体关联方案<br>- 详细实施步骤和文件结构 | John (Product Manager) |

## Dev Agent Record

### Agent Model Used
*待开发代理填写*

### Debug Log References
*待开发代理填写*

### Completion Notes List
*待开发代理填写*

### File List
*待开发代理填写*

## QA Results
*待QA代理填写*