# 报销系统(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`)。