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>
This commit is contained in:
zlwuandCursor committed 2026-08-17 15:45:27 +08:00
1 parent 8558d9439e
commit 071281150d
1 file changed
+142
@@ -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` 或空。