Files
shanxia2.0/docs/bx-migration/00-migration-plan.md
T
2026-06-07 00:40:01 +08:00

470 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 报销系统(bx)移植实施方案
> **编写日期**:2026-06-06
> **状态**:方案已定稿,待按阶段实施
> **源系统**:`shanxia/bx/bx-backend` + `shanxia/bx/bx-web`
> **目标系统**:`shanxia2.0/backend` + `shanxia2.0/web-admin`
> **DDL 基线**:`docs/sql/20260606_bx_tables.sql`(已在 shanxia2.0 库执行)
---
## 1. 迁移目标
将原报销系统业务功能迁入山峡一体化门户(shanxia2.0),实现:
1. **业务等价**:备用金、报销、工作流、支付/冲抵、视图中心等能力与 bx 原系统一致;
2. **协议兼容**:移植 API 保持 bx 原 **REST 风格**(GET/PUT/DELETE/POST)、**入参位置**、**响应 envelope**(`code:0`、`msg`、`data.rows`),**不**统一为 CRM 的 POST Body 风格;
3. **体系融合**:用户/角色/菜单/认证使用门户 `sys_*`,业务表使用 `bx_*` + 门户公共字段;
4. **UI 融合**:Web 端挂「报销管理」一级菜单,页面适配 Fresh Mint 规范(第二阶段)。
**不在本次范围**:`bx-mobile` 移动端、`bx_user/bx_role/bx_menu` 等独立权限表、历史业务数据导入(空库启动)。
---
## 2. 范围边界
### 2.1 数据库(10 张业务表,已完成 DDL)
| 表 | 用途 | DDL 状态 |
|----|------|---------|
| `bx_expense_item` | 费用项目(二级树) | ✅ |
| `bx_workflow_def` | 工作流定义 | ✅ |
| `bx_workflow_node` | 工作流节点 | ✅ |
| `bx_advance_apply` | 备用金申请 | ✅ |
| `bx_advance_approval` | 备用金审批记录 | ✅ |
| `bx_reimburse_apply` | 报销申请 | ✅ |
| `bx_reimburse_detail` | 报销明细 | ✅ |
| `bx_reimburse_approval` | 报销审批记录 | ✅ |
| `bx_reimburse_offset` | 冲抵关联 | ✅ |
| `bx_attachment` | 附件 | ✅ |
**不建表**:`bx_user`、`bx_role`、`bx_role_user`、`bx_menu`、`bx_menu_role`。
### 2.2 后端 API(约 60 个端点,待移植)
| 模块 | 源文件 | 端点数 | 优先级 |
|------|--------|--------|--------|
| 费用项目 | `routers/expense_item.py` | 5 | P1 |
| 工作流 | `routers/workflow.py` | 8 | P1 |
| 备用金 | `routers/advance.py` | 14 | P2 |
| 附件 | `routers/attachment.py` | 6 | P2 |
| 报销 | `routers/reimburse.py` | 15 | P3 |
| 支付/发放 | `routers/payment.py` | 12 | P4 |
**不移植**:`auth.py`、`user.py`、`role.py`、`menu.py`。
### 2.3 前端页面(13 页,第二阶段)
| 分组 | 页面 | 源文件 |
|------|------|--------|
| 基础配置 | 费用项目管理、工作流(备用金)、工作流(报销) | `ExpenseProjectPage`、`WfAdvancePage`、`WfReimbursePage` |
| 备用金 | 申请、审批、发放管理 | `AdvanceApplyPage`、`AdvanceApprovePage`、`AdvanceIssuePage` |
| 报销 | 申请、审批、支付管理 | `ReimburseApplyPage`、`ReimburseApprovePage`、`PaymentPage` |
| 视图中心 | 我的申请、待我审批、全部申请单 | `MinePage`、`TodoPage`、`AllPage` |
**不移植**:用户/角色/菜单管理、首页 Dashboard、Demo 页。
---
## 3. 核心原则(与 CRM 移植的差异)
| 原则 | 说明 |
|------|------|
| **API 协议保持 bx 原样** | 列表用 GET + Query;响应 `{ code: 0, msg, data }`;分页 `data.rows`;**禁止**改为门户 `code:200` / `message` / `data.list` |
| **Router 近原样复制** | 从 `bx-backend/app/routers/` 复制到 `backend/app/api/bx/`,仅改 Depends、import、前缀、租户注入 |
| **Service 选择性迁移** | `services/*.py` 业务算法可复用;必须加 `tenant_no`、UUID 主键、`sys_user` 用户 ID |
| **不走 CRM crud 分层** | bx 模块内部保持 `router → service → model`,不强制拆 `crud/bx/`(除非后续维护需要) |
| **最小改动** | 不重命名 URL、不改 HTTP 方法、不改前端 camelCase 字段名 |
| **门户能力注入** | 认证、租户、DB Session、对象存储用 shanxia2.0 现有能力 |
---
## 4. 源系统与目标系统对照
### 4.1 技术栈(同源,利于移植)
| 项 | bx-backend | shanxia2.0 backend |
|----|-----------|-------------------|
| 语言 | Python 3 | Python 3.9 |
| 框架 | FastAPI | FastAPI |
| ORM | SQLAlchemy | SQLAlchemy |
| 校验 | Pydantic v2 | Pydantic v2 |
### 4.2 API 协议对照
| 维度 | bx 原系统 | shanxia2.0 CRM(勿用于 bx) | bx 移植目标 |
|------|----------|---------------------------|------------|
| 成功码 | `0` | `200` | **保持 `0`** |
| 消息字段 | `msg` | `message` | **保持 `msg`** |
| 时间戳 | 无 | `timestamp` | **不加** |
| 分页字段 | `rows, total, page, pageSize` | `list, total, page, pageSize` | **保持 `rows`** |
| 列表请求 | GET + query | POST + body | **保持 GET** |
| 路径示例 | `/api/advances/` | `/api/web/crm/customer/list` | **保持 `/api/advances/`** |
### 4.3 数据模型对照
| 维度 | bx 原 sx.sql | shanxia2.0 已执行 DDL |
|------|-------------|----------------------|
| 主键 | `bigint AUTO_INCREMENT` | `varchar(64) UUID` |
| 外键 ID | `bigint` | `varchar(64)` |
| 租户 | 无 | `tenant_no DEFAULT 'shanxia2.0'` |
| 用户引用 | `bx_user.id` | `sys_user.id` |
---
## 5. 目标架构
### 5.1 后端目录结构
```
backend/app/
├── models/bx_models.py # 10 张表 ORM(对齐 20260606 DDL)
├── schemas/bx/
│ ├── common.py # ApiResponse / PagedData(从 bx 复制)
│ ├── expense_item.py
│ ├── workflow.py
│ ├── advance.py
│ ├── reimburse.py
│ ├── payment.py
│ └── attachment.py # id: int → str
├── services/bx/ # 从 bx-backend/services 迁移
│ ├── base.py # 可选:通用分页(改 UUID)
│ ├── expense_item.py
│ ├── workflow.py
│ ├── advance.py
│ ├── reimburse.py
│ ├── payment.py
│ └── attachment.py
├── core/
│ └── bx_auth.py # 门户 user → BxUserContext
└── api/bx/
├── __init__.py # 聚合 bx_router
├── expense_item.py # prefix="/expense-items"
├── workflow.py # prefix="/workflows"
├── advance.py # prefix="/advances"
├── reimburse.py # prefix="/reimburses"
├── payment.py # prefix="/payments"
└── attachment.py # prefix="/attachments"
```
### 5.2 路由挂载(避免 `/api/api/...`)
bx 原 router 前缀为 `/api/advances`;门户 `main.py` 已挂 `prefix="/api"`。
**规则**:bx router 的 `APIRouter(prefix=...)` **去掉 `/api`**,例如:
```python
# bx 原:prefix="/api/advances"
# 门户:prefix="/advances"
router = APIRouter(prefix="/advances", tags=["备用金管理"])
```
```python
# main.py
from app.api.bx import bx_router
app.include_router(bx_router, prefix=settings.API_V1_PREFIX) # → /api/advances/...
```
### 5.3 响应包装(独立体系)
bx 模块 **禁止** 使用 `app.core.response.success_response()`。
统一使用 `schemas/bx/common.py`:
```python
class ApiResponse(BaseModel, Generic[T]):
code: int = 0
msg: str = "success"
data: Optional[T] = None
class PagedData(BaseModel, Generic[T]):
rows: List[T]
total: int
page: int
page_size: int # JSON 序列化为 pageSize(alias_generator=to_camel)
```
---
## 6. 门户适配层
### 6.1 认证(`core/bx_auth.py`)
bx 原:`get_current_user` → ORM `User`,`user.id: int`
门户:`require_admin_or_super_admin` → `dict`,`user_id: str`
```python
@dataclass
class BxUserContext:
id: str # sys_user.id
name: str # nick_name 冗余写入 applicant_name 等
dept_id: str # create_dept_id
def get_bx_current_user(user: dict = Depends(require_admin_or_super_admin)) -> BxUserContext:
...
```
Router 中:`Depends(get_current_user)` → `Depends(get_bx_current_user)`。
### 6.2 租户隔离
所有业务查询/写入通过 `get_tenant_no(db, user_dict)` 获取 `tenant_no`,在 Service 层追加:
```python
query.filter(Model.tenant_no == tenant_no, Model.del_flag == 0)
```
创建记录时:
```python
obj_in["id"] = generate_uuid()
obj_in["tenant_no"] = tenant_no
obj_in["create_by"] = user.id
obj_in["create_dept_id"] = user.dept_id or ""
```
### 6.3 API 加密中间件
门户默认对 `/api/*` 启用 `ApiCryptoMiddleware`;bx 原系统无加密。
**开发阶段**:将 bx 业务路径加入 `API_ENCRYPT_EXCLUDE_PATHS`(`backend/app/core/config.py`):
```
/api/expense-items,
/api/workflows,
/api/advances,
/api/reimburses,
/api/payments,
/api/attachments
```
**前端接入 web-admin 后**:评估是否改为走门户加密(需 bx 页 axios 适配),或长期保持 exclude。
### 6.4 附件 / MinIO
不直接拷贝 `bx-backend/app/services/minio.py`。实现 `services/bx/storage.py` 适配器,内部调用门户已有 OSS/MinIO 上传能力,对外仍暴露:
- `POST /api/attachments/upload`(multipart)
- `GET /api/attachments/download/{id}`(StreamingResponse)
- `GET /api/attachments/preview/{id}`
---
## 7. 分阶段实施计划
### 总览
```
P0 基础设施 → P1 基础配置 → P2 备用金+附件 → P3 报销 → P4 支付 → P5 菜单 → P6 前端
│ │ │ │ │ │ │
ORM/Auth expense+wf advance reimburse payment sys_menu web-admin
```
### P0:基础设施(后端骨架)
| 步骤 | 任务 | 产出 | 完成标准 |
|------|------|------|---------|
| P0-1 | 创建 `models/bx_models.py` | 10 表 ORM | 字段顺序与 DDL 一致 |
| P0-2 | 创建 `schemas/bx/common.py` | ApiResponse / PagedData | 与 bx 响应一致 |
| P0-3 | 创建 `core/bx_auth.py` | BxUserContext + Depends | 能解析门户 JWT 用户 |
| P0-4 | 创建 `api/bx/__init__.py` | 空 bx_router | main.py 可注册 |
| P0-5 | 配置加密排除路径 | config 更新 | bx 路径不加密 |
| P0-6 | 迁移 `utils/codegen.py`、`date_range.py` | 单号生成等 | 单元可调用 |
**验收**:服务启动无报错;`GET /api/health` 正常;bx_router 已挂载。
---
### P1:基础配置(费用项目 + 工作流)
| 步骤 | 任务 | 源参考 |
|------|------|--------|
| P1-1 | `schemas/bx/expense_item.py` | `bx-backend/app/schemas/expense_item.py` |
| P1-2 | `services/bx/expense_item.py` | 加 tenant + UUID |
| P1-3 | `api/bx/expense_item.py` | 5 端点原样 |
| P1-4 | `schemas/bx/workflow.py` + service + router | 8 端点 |
**API 清单**:
| 方法 | 路径 |
|------|------|
| GET | `/api/expense-items/tree` |
| GET/POST/PUT/DELETE | `/api/expense-items/`、`/api/expense-items/{item_id}` |
| GET | `/api/workflows/`、`/api/workflows/biz/{biz_type}`、`/api/workflows/{workflow_id}` |
| POST/PUT/DELETE | `/api/workflows/`、`/api/workflows/{workflow_id}` |
| PUT | `/api/workflows/{workflow_id}/nodes` |
**验收**:Swagger 可调通 CRUD;费用树、工作流节点保存与原 bx 行为一致。
---
### P2:备用金 + 附件
| 步骤 | 任务 | 说明 |
|------|------|------|
| P2-1 | advance schemas / service / router | 含 submit/approve/reject 状态机 |
| P2-2 | attachment storage 适配 + router | upload/download/preview/bind-biz |
| P2-3 | 联调:申请 → 上传附件 → 提交审批 | 端到端 |
**API 清单(advance,14 端点)**:
| 方法 | 路径 |
|------|------|
| GET | `/api/advances/`、`/my`、`/my/stats`、`/stats/status-counts` |
| GET | `/api/advances/pending-approval`、`/pending-approval/stats`、`/approved-by-me` |
| GET | `/api/advances/{advance_id}`、`/{advance_id}/approvals` |
| POST | `/api/advances/`、`/{advance_id}/submit`、`/approve`、`/reject` |
| PUT/DELETE | `/api/advances/{advance_id}` |
**验收**:完整备用金审批链路;附件 upload/list/download 可用。
---
### P3:报销
| 步骤 | 任务 |
|------|------|
| P3-1 | reimburse schemas(含 detail) |
| P3-2 | reimburse service(明细、标题生成) |
| P3-3 | reimburse router(15 端点) |
**验收**:报销申请 CRUD + 明细 + 审批流与 bx 一致。
---
### P4:支付 / 发放 / 冲抵
| 步骤 | 任务 |
|------|------|
| P4-1 | payment service(最复杂:pay/offset/issue/batch-offset) |
| P4-2 | payment router(含 CSV export StreamingResponse) |
**验收**:报销支付、备用金发放、冲抵关联、offset-records 查询正确。
---
### P5:菜单与权限(门户 sys_menu)
在 `sys_menu` 新增一级 **「报销管理」**(`parent_id='2'`,Web 端根),子结构:
```
报销管理
├── 基础配置
│ ├── 费用项目管理
│ ├── 工作流配置(备用金)
│ └── 工作流配置(报销单)
├── 备用金
│ ├── 备用金申请
│ ├── 备用金审批
│ └── 备用金发放管理
├── 报销
│ ├── 报销申请
│ ├── 报销审批
│ └── 支付管理(发放/冲抵)
└── 视图中心
├── 我的申请单
├── 待我审批
└── 全部申请单(总经理)
```
权限标识(`perms`)建议前缀 `bx:`,与 CRM `crm:` 并列。角色-菜单通过 `sys_role_menu` 配置(可参考 bx 原 `bx-menu-role-init.sql` 角色语义:员工/财务/总经理)。
**验收**:admin 登录可见菜单;无权限用户 403。
---
### P6:前端移植(web-admin)
| 步骤 | 任务 | 参考 |
|------|------|------|
| P6-1 | 拷贝 `bx-web/src/views/pages/*.vue` → `web-admin/src/views/bx/` | 排除 User/Role/Menu/Home/Demo |
| P6-2 | 拷贝/适配 `bx-web/src/api/` | baseURL 指向门户 `/api`,协议已是 bx 风格 |
| P6-3 | Fresh Mint UI 适配 | 参考 `docs/crm-migration/step-03-frontend-adapt.md` |
| P6-4 | 认证改为门户登录态 | 不再调 `/api/auth/login` |
| P6-5 | 用户下拉改用 `sys_user` API | 替代 `userApi.dropdown` |
| P6-6 | 路由与 component 路径对齐 sys_menu | hash 路由 |
**UI 规范**:`docs/web-admin-ui-spec-fresh-mint.md`;列表参考 `/#/demo/order`,树形参考 `/#/demo/category`。
**验收**:各页面功能与 bx-web 对照通过;UI 符合 Fresh Mint。
---
## 8. 单模块后端实施 SOP(每个模块重复)
1. **读源**:`bx-backend/app/routers/{module}.py` + `services/{module}.py`,中文简述业务逻辑
2. **Schema**:复制到 `schemas/bx/`,`id` 及外键改 `str`,保留 camelCase alias
3. **Service**:复制到 `services/bx/`,追加 `tenant_no` 过滤、UUID 生成、用户字段映射
4. **Router**:复制到 `api/bx/`,改 Depends / import / prefix(去 `/api`)
5. **注册**:在 `api/bx/__init__.py` include_router
6. **验证**:Swagger 或 Postman 对照 bx 原接口逐项测试
7. **更新**:[module-status.md](./module-status.md)
---
## 9. 完整 API 路径索引(移植后保持不变)
详见 [01-api-inventory.md](./01-api-inventory.md)。
---
## 10. 验证清单
### 10.1 后端协议
- [ ] 成功响应 `code === 0`,字段名为 `msg`(非 `message`)
- [ ] 分页 `data.rows`(非 `data.list`)
- [ ] 列表接口为 GET + query 参数
- [ ] 路径与 bx-web `api/index.ts` 一致
- [ ] 错误时 HTTP 4xx/5xx + 合理 detail(与 bx 行为接近)
### 10.2 业务
- [ ] 费用项目二级树 CRUD
- [ ] 工作流定义 + 节点保存(advance / reimburse 各一条)
- [ ] 备用金:草稿 → 提交 → 多级审批 → 驳回/通过 → 发放
- [ ] 报销:申请 + 明细 + 审批 + 支付/冲抵
- [ ] 附件:upload → bind-biz → download/preview
- [ ] 视图:我的 / 待办 / 全部(总经理)
### 10.3 门户集成
- [ ] 租户隔离:不同 tenant 数据不可见
- [ ] 门户 JWT 可访问 bx 接口
- [ ] 菜单权限控制生效
---
## 11. 风险与对策
| 风险 | 对策 |
|------|------|
| ID 类型 int→str,前端 bx 页仍用 number | Schema 用 str;前端 P6 逐步改类型;JSON 字符串 ID 可兼容 |
| API 加密与 bx GET 冲突 | 开发期 exclude;联调后再定是否前端加密 |
| 门户与 bx 响应双标准并存 | bx 模块独立 common.py,禁止混用 success_response |
| payment 业务复杂 | 单独 P4,充分对照 bx service 单测/手测 |
| 审批人取自 workflow_node.approver_id | 配置工作流时选 sys_user,与原 bx 选 bx_user 操作一致 |
---
## 12. 文档索引
| 文档 | 用途 |
|------|------|
| [00-migration-plan.md](./00-migration-plan.md) | 本文:总实施方案 |
| [module-status.md](./module-status.md) | 模块进度跟踪 |
| [01-api-inventory.md](./01-api-inventory.md) | 全量 API 路径与方法 |
| [ref-backend-adapt.md](./ref-backend-adapt.md) | 后端适配细则(认证/租户/Service 改造清单) |
| `docs/sql/20260606_bx_tables.sql` | DDL 权威来源 |
| `docs/sql/sx.sql` | bx 原表业务字段对照 |
| `docs/crm-migration/ref-lessons-learned.md` | 前端/UI 踩坑(P6 参考) |
| `docs/web-admin-ui-spec-fresh-mint.md` | Web UI 规范(P6) |
---
## 13. 下一步行动
**当前建议从 P0 开始**:创建 `bx_models.py`、`schemas/bx/common.py`、`core/bx_auth.py` 并挂载空 `bx_router`。
每完成一个阶段,更新 [module-status.md](./module-status.md) 并在 PR/提交说明中标注阶段编号(如 `bx P1 expense-item`)。