|
|
@@ -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
|