Sfoglia il codice sorgente

feat: 创建 Story 15.9 详细文档

- 创建 15-9-id-valid-date-long-term-support.md
- 包含完整的验收标准(AC1-AC7)
- 包含详细的技术实现要点
- 包含数据结构设计、UI 设计参考
- 包含任务分解(6个任务,21个子任务)
- 更新 sprint-status.yaml 状态为 ready-for-dev

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 1 giorno fa
parent
commit
d447c551bd

+ 302 - 0
_bmad-output/implementation-artifacts/15-9-id-valid-date-long-term-support.md

@@ -0,0 +1,302 @@
+# Story 15.9: 身份证有效期支持长期选项
+
+Status: ready-for-dev
+
+<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
+
+## Story
+
+作为 用户,
+我想要 在残疾人管理表单中选择身份证有效期为"长期有效",
+以便 准确记录长期有效的身份证信息,而不需要被迫填写一个虚假的截止日期。
+
+## 背景
+
+中国居民身份证存在"长期有效"的情况(通常为高龄老人或特殊人群)。当前系统只支持选择具体日期,无法表达"长期有效"状态。用户被迫填写一个虚假日期(如 2099-12-31),导致数据不准确。残疾证有效期也存在同样的"长期有效"需求。
+
+## Acceptance Criteria
+
+### AC1: 表单支持选择"长期有效"
+1. **Given** 用户在残疾人管理创建/编辑表单页面
+2. **When** 用户查看身份证有效期字段
+3. **Then** 显示两个选项:"指定日期"和"长期有效"
+4. **And** 使用单选按钮(RadioGroup)让用户选择
+5. **And** 默认选中"指定日期"选项
+6. **And** "指定日期"选项下显示日期选择器
+7. **And** "长期有效"选项显示辅助说明"身份证无固定期限时选择"
+
+### AC2: 选项互斥逻辑
+1. **Given** 用户在残疾人管理表单页面
+2. **When** 用户选择"长期有效"选项
+3. **Then** 日期选择器自动禁用或隐藏
+4. **And** 已选择的日期值被清空
+5. **When** 用户选择"指定日期"选项
+6. **Then** "长期有效"选项自动取消选中
+7. **And** 日期选择器恢复可用状态
+
+### AC3: 数据存储
+1. **Given** 用户选择了"长期有效"选项
+2. **When** 用户提交表单
+3. **Then** 数据库中 `id_valid_date` 字段存储为 `NULL`
+4. **Given** 用户选择了"指定日期"选项
+5. **When** 用户提交表单
+6. **Then** 数据库中 `id_valid_date` 字段存储为选定的日期
+
+### AC4: 编辑时数据回显
+1. **Given** 数据库中某残疾人的 `id_valid_date` 为 `NULL`(长期有效)
+2. **When** 用户打开编辑表单
+3. **Then** "长期有效"选项自动选中
+4. **And** 日期选择器显示为空或禁用
+5. **Given** 数据库中某残疾人的 `id_valid_date` 为具体日期
+6. **When** 用户打开编辑表单
+7. **Then** "指定日期"选项自动选中
+8. **And** 日期选择器显示存储的日期值
+
+### AC5: 查看和列表页面显示
+1. **Given** 残疾人的身份证有效期为长期(`id_valid_date` 为 `NULL`)
+2. **When** 用户在列表或查看页面查看信息
+3. **Then** 身份证有效期字段显示"长期"或"长期有效"
+4. **And** 不显示具体日期
+5. **Given** 残疾人的身份证有效期为具体日期
+6. **When** 用户在列表或查看页面查看信息
+7. **Then** 身份证有效期字段显示格式化的日期(如"2026-12-31")
+
+### AC6: 残疾证有效期同步支持(可选)
+1. **Given** 用户在残疾人管理表单页面
+2. **When** 用户查看残疾证有效期字段
+3. **Then** 残疾证有效期也支持"长期有效"选项
+4. **And** 实现逻辑与身份证有效期一致
+5. **And** 两个字段可以独立选择(一个长期,一个具体日期)
+
+### AC7: 表单验证
+1. **Given** 用户在残疾人管理表单页面
+2. **When** 用户未选择任何选项(既未选择日期,也未选择长期)
+3. **Then** 字段可以为空(因为是可选字段)
+4. **When** 用户选择"指定日期"但未选择日期
+5. **Then** 显示验证错误"请选择身份证有效期"
+6. **When** 用户选择"长期有效"
+7. **Then** 不需要额外的验证
+
+## Tasks / Subtasks
+
+- [ ] Task 1: 分析现有代码结构和表单实现 (AC: All)
+  - [ ] Subtask 1.1: 阅读 DisabilityPersonManagement.tsx 了解当前表单结构
+  - [ ] Subtask 1.2: 分析身份证有效期字段当前实现方式
+  - [ ] Subtask 1.3: 查看残疾证有效期字段的实现
+
+- [ ] Task 2: 设计数据结构和状态管理 (AC: AC1, AC2, AC3)
+  - [ ] Subtask 2.1: 设计表单状态结构(idValidDateType + idValidDate)
+  - [ ] Subtask 2.2: 确认 Schema 无需修改(已支持 nullable)
+  - [ ] Subtask 2.3: 确认数据库无需修改
+
+- [ ] Task 3: 实现前端表单组件 (AC: AC1, AC2)
+  - [ ] Subtask 3.1: 替换身份证有效期字段为 RadioGroup 组件
+  - [ ] Subtask 3.2: 实现选项互斥逻辑
+  - [ ] Subtask 3.3: 添加表单验证规则
+  - [ ] Subtask 3.4: 实现残疾证有效期同步支持(可选)
+
+- [ ] Task 4: 实现数据回显逻辑 (AC: AC4)
+  - [ ] Subtask 4.1: 编辑表单加载时根据 NULL 值判断选中"长期有效"
+  - [ ] Subtask 4.2: 编辑表单加载时根据具体日期选中"指定日期"
+
+- [ ] Task 5: 实现查看和列表页面显示 (AC: AC5)
+  - [ ] Subtask 5.1: 修改查看页面显示逻辑(NULL 显示"长期")
+  - [ ] Subtask 5.2: 修改列表页面显示逻辑(NULL 显示"长期")
+
+- [ ] Task 6: 编写 E2E 测试 (AC: All)
+  - [ ] Subtask 6.1: 创建残疾人-选择"长期有效"测试
+  - [ ] Subtask 6.2: 创建残疾人-选择"指定日期"测试
+  - [ ] Subtask 6.3: 编辑残疾人-从长期改为具体日期测试
+  - [ ] Subtask 6.4: 编辑残疾人-从具体日期改为长期测试
+  - [ ] Subtask 6.5: 编辑残疾人-长期选项正确回显测试
+  - [ ] Subtask 6.6: 编辑残疾人-具体日期正确回显测试
+  - [ ] Subtask 6.7: 查看页面-长期显示为"长期"测试
+  - [ ] Subtask 6.8: 列表页面-长期显示为"长期"测试
+  - [ ] Subtask 6.9: 表单验证-未选择日期时的错误提示测试
+
+## Dev Notes
+
+### Epic Context
+
+Epic 15 的目标是修复残疾人管理系统生产环境问题。本 Story 是用户体验改进的一部分,与已完成的 Story 15.1(残疾证号自动填充)和 Story 15.8(残疾人企业查询页面增强)性质相似,都是表单优化和用户体验提升。
+
+### 当前实现状态分析
+
+根据代码探索结果,当前身份证有效期字段实现如下:
+
+**前端组件位置**:
+- `allin-packages/disability-person-management-ui/src/components/DisabilityPersonManagement.tsx:1144-1162`
+
+**当前实现**:
+```tsx
+<FormField
+  control={createForm.control}
+  name="idValidDate"
+  render={({ field }) => (
+    <FormItem>
+      <FormLabel>身份证有效期</FormLabel>
+      <FormControl>
+        <Input
+          type="date"
+          placeholder="选择身份证有效期"
+          {...field}
+          value={field.value ? new Date(field.value).toISOString().split('T')[0] : ''}
+          onChange={(e) => field.onChange(e.target.value ? new Date(e.target.value) : undefined)}
+        />
+      </FormControl>
+      <FormMessage />
+    </FormItem>
+  )}
+/>
+```
+
+**Schema 定义**:
+- `allin-packages/disability-module/src/schemas/disabled-person.schema.ts`
+- `idValidDate: z.coerce.date<Date>().nullable()` - 已支持 nullable,无需修改
+
+**数据库字段**:
+- `allin-packages/disability-module/src/entities/disabled-person.entity.ts:87-93`
+- `idValidDate!: Date | null` - 已支持 NULL,无需修改
+
+### 技术实现要点
+
+**1. 数据结构设计**
+
+表单状态需要扩展为:
+```typescript
+{
+  idValidDateType: 'date' | 'long_term',  // 新增字段
+  idValidDate: Date | null                 // 保持现有字段
+}
+```
+
+API 提交时:
+```typescript
+{
+  idValidDate: Date | null  // null 表示长期有效,Date 表示具体日期
+}
+```
+
+**2. 前端组件修改**
+
+需要将现有的 `<Input type="date" />` 替换为 RadioGroup 组件。推荐使用 shadcn/ui 的 RadioGroup 组件,位于 `@d8d/shared-ui-components` 中。
+
+参考组件路径:
+- RadioGroup: `@d8d/shared-ui/components/ui/radio-group`
+- FormLabel, FormItem, FormControl: `@d8d/shared-ui/components/ui/form`
+
+**3. UI 设计参考**
+
+```
+┌─────────────────────────────────────────┐
+│ 身份证有效期 *                          │
+│ ○ 指定日期                              │
+│   ┌─────────────────┐                  │
+│   │ 2026-02-09     │ (日期选择器)     │
+│   └─────────────────┘                  │
+│ ○ 长期有效                              │
+│   <身份证无固定期限时选择>              │
+└─────────────────────────────────────────┘
+```
+
+**4. 互斥逻辑实现**
+
+```typescript
+// 选择"长期有效"时
+const handleLongTermChange = (checked: boolean) => {
+  if (checked) {
+    form.setValue('idValidDate', undefined);      // 清空日期
+    form.setValue('idValidDateType', 'long_term'); // 设置类型
+  }
+};
+
+// 选择"指定日期"时
+const handleDateChange = (date: Date) => {
+  if (date) {
+    form.setValue('idValidDateType', 'date'); // 设置类型
+  }
+};
+```
+
+**5. 数据回显逻辑**
+
+编辑表单加载时:
+```typescript
+useEffect(() => {
+  if (personInfo) {
+    if (personInfo.idValidDate === null) {
+      // 长期有效
+      form.setValue('idValidDateType', 'long_term');
+      form.setValue('idValidDate', undefined);
+    } else {
+      // 具体日期
+      form.setValue('idValidDateType', 'date');
+      form.setValue('idValidDate', personInfo.idValidDate);
+    }
+  }
+}, [personInfo]);
+```
+
+**6. 查看页面显示**
+
+```typescript
+// 查看页面 (DisabilityPersonManagement.tsx:1786-1790)
+{viewData.personInfo.idValidDate ? (
+  format(new Date(viewData.personInfo.idValidDate), 'yyyy-MM-dd')
+) : (
+  '长期'
+)}
+```
+
+### Project Structure Notes
+
+项目使用 Monorepo 结构,相关包位于:
+- `allin-packages/disability-person-management-ui/` - 管理后台残疾人管理 UI
+- `allin-packages/disability-module/` - 后端残疾人模块
+
+遵循项目统一的项目结构规范:
+- 使用 shadcn/ui 组件库
+- 使用 react-hook-form + zod 进行表单管理
+- 使用 Hono RPC 进行前后端通信
+
+### References
+
+- Epic 15 完整定义: `_bmad-output/planning-artifacts/epics.md#Epic-15`
+- Story 15.9 定义: `_bmad-output/planning-artifacts/epics.md#Story-15.9`
+- Sprint 状态: `_bmad-output/implementation-artifacts/sprint-status.yaml`
+- 表单组件: `allin-packages/disability-person-management-ui/src/components/DisabilityPersonManagement.tsx:1144-1162`
+- Schema 定义: `allin-packages/disability-module/src/schemas/disabled-person.schema.ts:59-62`
+- Entity 定义: `allin-packages/disability-module/src/entities/disabled-person.entity.ts:87-93`
+
+### 测试运行命令
+
+**E2E 测试运行**:
+```bash
+# 在 web 目录下运行
+cd web
+pnpm test:e2e:chromium disability-person-id-valid-date-longterm
+```
+
+**快速失败模式** (推荐调试时使用):
+```bash
+timeout 60 pnpm test:e2e:chromium disability-person-id-valid-date-longterm
+```
+
+## Dev Agent Record
+
+### Agent Model Used
+
+Claude Opus 4 (d8d-model) via Happy
+
+### Debug Log References
+
+### Completion Notes List
+
+### File List
+
+---
+
+**Generated:** 2026-02-09
+**Epic:** 15 - 残疾人管理系统生产环境问题修复
+**Story:** 15.9 - 身份证有效期支持长期选项
+**Status:** ready-for-dev

+ 1 - 1
_bmad-output/implementation-artifacts/sprint-status.yaml

@@ -329,7 +329,7 @@ development_status:
   15-5-employment-date-edit: ready-for-dev   # 订单人员入职/离职日期编辑功能(2026-01-20 新增)- 在订单详情对话框中使入职日期和离职日期可编辑,支持修正错误记录和设置离职日期
   15-6-guardian-phone-layout-optimization: ready-for-dev   # 监护人电话布局优化(2026-01-20 新增)- 将"残疾人本人电话"和"监护人电话"组织到相邻区域,本人电话支持多号码动态添加
   15-8-disability-person-company-query-union-table: review   # 残疾人企业查询页面并集查询与表格增强(2026-01-22 新增)- 新增姓名和身份证号筛选框,实现并集查询逻辑(姓名 OR 身份证号 OR 平台 OR 公司),表格新增4列(离职日期、在职状态、入职地点、籍贯)
-  15-9-id-valid-date-long-term-support: backlog   # 身份证有效期支持长期选项(2026-02-09 新增)- 身份证和残疾证有效期支持"长期有效"选项,单选组合控件(指定日期/长期有效),NULL 表示长期
+  15-9-id-valid-date-long-term-support: ready-for-dev   # 身份证有效期支持长期选项(2026-02-09 新增)- 身份证和残疾证有效期支持"长期有效"选项,单选组合控件(指定日期/长期有效),NULL 表示长期
   epic-15-retrospective: optional
 
 # 技术改进完成状态 (2026-01-10):