Bläddra i källkod

docs: add design for optional reimburse detail occur date

Capture the agreed plan to add nullable occur_date (YYYY-MM-DD) on
bx_reimburse_detail across backend, web-admin, and bx-mobile.

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 2 månader sedan
förälder
incheckning
071281150d

+ 142 - 0
docs/superpowers/specs/2026-08-17-reimburse-detail-occur-date-design.md

@@ -0,0 +1,142 @@
+# 报销明细「发生时间」字段 — 设计文档
+
+> **日期**: 2026-08-17  
+> **状态**: 已确认设计(待实现)  
+> **仓库**: shanxia2.0(`backend/` + `web-admin/` + `bx-mobile/`)  
+> **入口页**: `http://localhost:3006/#/bx/reimburse/apply`
+
+## 背景与问题
+
+报销申请「新增 / 编辑」弹窗的报销明细当前列为:序号、费用项目、金额、报销事由、操作。  
+客户希望在「报销事由」后增加可选字段「发生时间」,可填可不填;精度为年月日。
+
+## 已确认决策
+
+| # | 决策 | 说明 |
+|---|------|------|
+| D1 | **独立字段** | 表 `bx_reimburse_detail` 新增可空 `occur_date`(DATE),不用拼进 `remark` |
+| D2 | **仅年月日** | API / 前端统一 `YYYY-MM-DD`;不做时分秒 |
+| D3 | **可选** | 不校验必填;空串 / null → 库 `NULL` |
+| D4 | **只读空值** | 始终展示该列/字段,空显示为 `—` |
+| D5 | **三端一致** | backend + web-admin + bx-mobile 同步读写与展示 |
+| D6 | **不做** | 列表筛选、导出、未来日期限制、主表列、列表卡片摘要 |
+
+---
+
+## 一、目标与范围
+
+### 目标
+
+每条报销明细可记录可选的费用发生日期,并在申请录入与各只读明细场景一致展示。
+
+### 范围内
+
+- 库表 / Model / Schema / API 写入与回读
+- Web:申请弹窗录入 + 查看弹窗;审批页与 view(mine/todo/all)明细表
+- Mobile:申请页录入;详情页与审批页明细展示
+
+### 范围外
+
+- 列表筛选 / 导出 / 打印
+- `ApprovalDetailPage`(当前未展示报销明细分列,不改)
+- 历史数据回填(存量保持 `NULL`)
+
+---
+
+## 二、数据模型与 API
+
+### 2.1 表字段
+
+`bx_reimburse_detail` 新增:
+
+| 列名 | 类型 | 可空 | 位置 | 注释 |
+|------|------|------|------|------|
+| `occur_date` | `date` | YES | `amount` 之后、`tenant_no` 之前 | 费用发生日期 |
+
+存量行:`occur_date = NULL`。
+
+### 2.2 DDL 与快照
+
+- 迁移脚本:`backend/scripts/migrate_20260817_bx_reimburse_detail_occur_date.sql`
+- 同步更新:`docs/sql/shanxia2.0.sql` 中 `bx_reimburse_detail` 表定义
+
+### 2.3 Model / Schema
+
+- Model:`BxReimburseDetail.occur_date`(nullable Date)
+- `ReimburseDetailCreate.occur_date: Optional[str] = None`
+- `ReimburseDetailOut.occur_date: OptDateToStr = None`(序列化为 `YYYY-MM-DD` 或 null)
+- JSON 驼峰别名:`occurDate`
+
+### 2.4 写入链路
+
+1. `api/bx/reimburse.py` → `_build_details_data` 透传 `occur_date`
+2. `services/bx/reimburse.py` → `create_with_details` / `update_with_details` 写入明细
+3. 解析:空 → `NULL`;非空按与 `reimburse_date` 同类的日期解析(`YYYY-MM-DD`)
+4. 出参:`ReimburseDetailOut.model_validate` 自然带出(字段挂在 Model 上即可)
+
+---
+
+## 三、前端交互
+
+### 3.1 列 / 字段顺序
+
+费用项目 → 金额 → 报销事由 → **发生时间** →(操作列,仅编辑表)
+
+### 3.2 web-admin
+
+**录入** — `views/bx/reimburse/apply/index.vue`
+
+- 明细表新增「发生时间」列(无必填星号)
+- `el-date-picker`:`type="date"`,`value-format="YYYY-MM-DD"`,可清空
+- `DetailLine`、提交 payload、编辑回填增加 `occurDate`
+- 不做必填校验
+
+**只读**(始终显示列,空为 `—`)
+
+- 同页查看弹窗明细表
+- `views/bx/reimburse/approve/index.vue`
+- `views/bx/view/mine|todo|all/index.vue`
+
+### 3.3 bx-mobile
+
+**录入** — `ReimburseApplyPage.vue`:报销事由后增加发生时间日期选择(可清空);提交/回填带 `occurDate`。
+
+**只读**(始终显示,空为 `—`)
+
+- `ReimburseDetailPage.vue`
+- `ReimburseApprovePage.vue`
+
+---
+
+## 四、改动文件清单
+
+| 层 | 文件 |
+|----|------|
+| DB | `backend/scripts/migrate_20260817_bx_reimburse_detail_occur_date.sql` |
+| DB 快照 | `docs/sql/shanxia2.0.sql` |
+| Model | `backend/app/models/bx_models.py` |
+| Schema | `backend/app/schemas/bx/reimburse.py` |
+| API | `backend/app/api/bx/reimburse.py` |
+| Service | `backend/app/services/bx/reimburse.py` |
+| Web | `web-admin/src/views/bx/reimburse/apply/index.vue` |
+| Web | `web-admin/src/views/bx/reimburse/approve/index.vue` |
+| Web | `web-admin/src/views/bx/view/mine/index.vue` |
+| Web | `web-admin/src/views/bx/view/todo/index.vue` |
+| Web | `web-admin/src/views/bx/view/all/index.vue` |
+| Mobile | `bx-mobile/src/views/reimburse/ReimburseApplyPage.vue` |
+| Mobile | `bx-mobile/src/views/reimburse/ReimburseDetailPage.vue` |
+| Mobile | `bx-mobile/src/views/reimburse/ReimburseApprovePage.vue` |
+
+---
+
+## 五、验收要点
+
+1. 新增/编辑:可填、可清空发生时间;不填可保存与提交。
+2. 各只读明细场景:有值显示 `YYYY-MM-DD`,无值显示 `—`。
+3. Web 与 bx-mobile 行为一致。
+4. 旧单据明细为空时展示 `—`,编辑其他字段不受影响。
+
+## 六、错误处理
+
+- 非法日期格式:后端解析失败时返回 400(与现有报销日期解析策略一致)。
+- 前端 date-picker 正常情况下只产出合法 `YYYY-MM-DD` 或空。