17 KiB
报销系统(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),实现:
- 业务等价:备用金、报销、工作流、支付/冲抵、视图中心等能力与 bx 原系统一致;
- 协议兼容:移植 API 保持 bx 原 REST 风格(GET/PUT/DELETE/POST)、入参位置、响应 envelope(
code:0、msg、data.rows),不统一为 CRM 的 POST Body 风格; - 体系融合:用户/角色/菜单/认证使用门户
sys_*,业务表使用bx_*+ 门户公共字段; - 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,例如:
# bx 原:prefix="/api/advances"
# 门户:prefix="/advances"
router = APIRouter(prefix="/advances", tags=["备用金管理"])
# 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:
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
@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 层追加:
query.filter(Model.tenant_no == tenant_no, Model.del_flag == 0)
创建记录时:
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(每个模块重复)
- 读源:
bx-backend/app/routers/{module}.py+services/{module}.py,中文简述业务逻辑 - Schema:复制到
schemas/bx/,id及外键改str,保留 camelCase alias - Service:复制到
services/bx/,追加tenant_no过滤、UUID 生成、用户字段映射 - Router:复制到
api/bx/,改 Depends / import / prefix(去/api) - 注册:在
api/bx/__init__.pyinclude_router - 验证:Swagger 或 Postman 对照 bx 原接口逐项测试
- 更新:module-status.md
9. 完整 API 路径索引(移植后保持不变)
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 | 本文:总实施方案 |
| module-status.md | 模块进度跟踪 |
| 01-api-inventory.md | 全量 API 路径与方法 |
| 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 并在 PR/提交说明中标注阶段编号(如 bx P1 expense-item)。