470 lines
17 KiB
Markdown
470 lines
17 KiB
Markdown
# 报销系统(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`)。
|