# TBOSS OA 模块 — 系统架构设计
> 版本:v1.0 | 日期:2026-06-03 | 基于 `tboss-oa-product-strategy.md`
---
## 1. 整体分层架构
```mermaid
graph TB
subgraph 客户端层["客户端层 — Flutter 3.38.10"]
direction LR
A1[Android 宿主
Java/Kotlin]
A2[iOS 宿主
OC / Xcode 14.2]
A3[Flutter OA Module
业务表单 / 公告 / 报表 / 权限管理]
A1 --- A3
A2 --- A3
end
subgraph 通道层["Platform Channel"]
B1[MethodChannel
相机 / 相册 / 通讯录
地图选点 / GPS 定位]
end
subgraph 服务端层[".NET Framework 4.8 服务端"]
direction TB
C1["OA API Controller
RESTful HTTP API"]
subgraph 业务服务层
C2["权限服务
OaPermission / ACL"]
C3["单据服务
报销 / 事前 / 加班 / 用车 / 外勤"]
C4["公告服务
发布 / 触达审计 / DING"]
C5["报表服务
5 大明细报表 / 导出"]
end
subgraph 集成适配层
C6["审批适配器
ErpAAdapter / ErpBAdapter"]
C7["消息适配器
App Push / 站内消息"]
C8["数据适配器
用户 / 部门 / 客户查询"]
end
C1 --> C2
C1 --> C3
C1 --> C4
C1 --> C5
C2 --> C8
C3 --> C6
C3 --> C8
C4 --> C7
end
subgraph 数据层["数据层"]
D1[("SQL Server 2019+
OA 业务库
单据 / 公告 / 权限 / 车辆")]
end
subgraph 外部系统["外部系统"]
direction LR
E1["ERP-A
审批引擎 + 主数据"]
E2["ERP-B
审批引擎 + 主数据"]
E3["ERP-C
审批引擎 + 主数据"]
end
A3 -->|"HTTP REST"| C1
A3 -.->|"MethodChannel"| B1
C3 --> D1
C4 --> D1
C2 --> D1
C6 -->|"审批 API
(签名不同)"| E1
C6 --> E2
C6 --> E3
C8 -->|"主数据查询"| E1
C8 --> E2
C8 --> E3
C7 -->|"App Push"| A3
```
### 分层职责
| 层 | 职责 | 不负责 |
|----|------|--------|
| **客户端层** | UI 渲染、表单校验、本地缓存、原生能力调用 | 审批逻辑、权限判定、数据聚合 |
| **通道层** | 桥接 Flutter 与原生 SDK(相机/GPS/通讯录/地图) | 业务逻辑 |
| **服务端层** | REST API、业务逻辑、多 ERP 适配、消息推送触发 | 最终审批执行(由 ERP 完成) |
| **数据层** | OA 业务数据持久化 | 用户/组织/客户/审批数据存储 |
| **外部系统** | 审批引擎、用户主数据、客户主数据 | OA 特有的业务表单 |
---
## 2. 技术选型
| 层 | 技术 | 版本 | 选型理由 |
|----|------|------|---------|
| 移动端框架 | **Flutter** | 3.38.10 | 跨平台代码复用;已有 Flutter Module 嵌入 Android/iOS 宿主 |
| Android 宿主 | Java/Kotlin | — | 现有 ERP App 技术栈,MethodChannel 互调已验证 |
| iOS 宿主 | Objective-C | Xcode 14.2 | 现有 ERP App 技术栈;Flutter 3.38.10 兼容已验证 |
| 后端框架 | **.NET Framework 4.8** | 4.8 | 现有服务端技术栈;团队熟悉;已有消息/审批代理模块可复用 |
| 数据访问 | **Dapper** | — | 轻量高效;复杂 SQL 手写场景优于 EF;与现有服务端一致 |
| 数据库 | **SQL Server 2019+** | 2019+ | 现有基础设施;与 ERP 同生态 |
| HTTP 客户端 | **Dio** | — | Flutter 端标准 HTTP 库;拦截器/超时/重试 |
| 路由 | **GoRouter** | — | 声明式路由;Deep Link 支持 |
| 状态管理 | Provider / Riverpod | — | 轻量;全局角色/权限状态注入 |
| UI 组件库 | **TDesign Flutter** | — | 企业级组件库;项目已统一采用 |
| 图表 | **fl_chart** | — | 报表趋势图/柱状图 |
| 原生互调 | **MethodChannel** | — | Flutter ↔ 原生双向通信 |
### 不选型的理由
| 未选 | 理由 |
|------|------|
| 独立用户体系 | ERP 已有用户/组织数据,避免双写和同步 |
| 自建审批引擎 | ERP 审批引擎成熟且多套在用,OA 只做消费端 |
| 自建消息推送 | .NET 服务端已有完整推送链路 |
| Flutter 独立 App | 必须是 App 内嵌模块,不能是独立应用 |
| Entity Framework | 团队偏好 Dapper;现有服务端已是 Dapper |
---
## 3. 模块划分
```
tboss_oa_module/lib/
├── main.dart # 入口
├── app.dart # MaterialApp + GoRouter + Provider 注入
│
├── core/
│ ├── router/app_router.dart # 路由表 + Deep Link 处理
│ ├── network/
│ │ ├── api_client.dart # Dio 实例(base URL / 拦截器 / 超时)
│ │ ├── api_response.dart # 统一响应模型
│ │ └── api_exception.dart # 业务异常
│ ├── auth/
│ │ └── auth_service.dart # Token 管理 + 401 处理
│ ├── theme/
│ │ ├── app_colors.dart # 色彩 Token
│ │ └── app_theme.dart # ThemeData
│ ├── utils/
│ │ ├── date_utils.dart
│ │ ├── responsive.dart # 横竖屏适配
│ │ └── validators.dart # 表单校验
│ └── i18n/
│ ├── app_localizations.dart
│ └── locale_provider.dart
│
├── shared/
│ ├── models/
│ │ ├── user_model.dart # 用户模型(从 ERP API 映射)
│ │ ├── approval_status.dart # 审批状态枚举
│ │ └── pagination_model.dart # 分页模型
│ └── widgets/
│ ├── app_card.dart
│ ├── status_tag.dart # 状态标签组件
│ ├── status_banner.dart # 详情页状态横幅
│ ├── list_card.dart # 列表卡片
│ ├── action_bar.dart # 底部操作栏
│ ├── filter_tabs.dart # Chip 筛选条
│ ├── form_section.dart # 表单分组
│ ├── form_field_row.dart # 表单行
│ ├── section_card.dart # 分区卡片
│ ├── empty_state.dart # 空状态
│ ├── loading_widget.dart # 骨架屏/菊花
│ ├── approval_actions.dart # 审批操作栏(经理版)
│ ├── approval_timeline.dart # 审批时间线
│ ├── report_filter_bar.dart # 报表筛选
│ ├── message_item.dart # 消息卡片
│ ├── profile_menu_item.dart # 个人中心菜单项
│ └── pencil_nav_bar.dart # 底部导航栏
│
├── features/
│ ├── shell/
│ │ ├── app_shell.dart # Appshell(底部 Tab)
│ │ └── nav_bar_config.dart
│ ├── home/
│ │ ├── home_page.dart # 工作台
│ │ └── home_controller.dart
│ ├── messages/
│ │ ├── message_list_page.dart
│ │ ├── message_controller.dart
│ │ └── message_model.dart
│ ├── profile/
│ │ └── profile_page.dart
│ ├── expense_application/
│ │ ├── expense_application_api.dart
│ │ ├── expense_application_model.dart
│ │ ├── expense_application_apply_page.dart
│ │ ├── expense_application_list_page.dart
│ │ ├── expense_application_list_controller.dart
│ │ └── expense_application_detail_page.dart
│ ├── expense/
│ │ ├── expense_api.dart
│ │ ├── expense_model.dart
│ │ ├── expense_apply_page.dart
│ │ ├── expense_list_page.dart
│ │ ├── expense_list_controller.dart
│ │ └── expense_detail_page.dart
│ ├── overtime/
│ │ ├── overtime_api.dart
│ │ ├── overtime_model.dart
│ │ ├── overtime_apply_page.dart
│ │ ├── overtime_list_page.dart
│ │ ├── overtime_list_controller.dart
│ │ └── overtime_detail_page.dart
│ ├── vehicle/
│ │ ├── vehicle_api.dart
│ │ ├── vehicle_model.dart
│ │ ├── vehicle_apply_page.dart
│ │ ├── vehicle_list_page.dart
│ │ ├── vehicle_list_controller.dart
│ │ └── vehicle_detail_page.dart
│ ├── outing_log/
│ │ ├── outing_log_api.dart
│ │ ├── outing_log_model.dart
│ │ ├── outing_log_create_page.dart
│ │ ├── outing_log_list_page.dart
│ │ ├── outing_log_list_controller.dart
│ │ └── outing_log_detail_page.dart
│ ├── announcement/
│ │ ├── announcement_api.dart
│ │ ├── announcement_model.dart
│ │ ├── announcement_list_page.dart
│ │ ├── announcement_list_controller.dart
│ │ ├── announcement_detail_page.dart
│ │ └── announcement_create_page.dart
│ ├── report/
│ │ ├── expense_apply_detail_report_page.dart
│ │ ├── expense_detail_report_page.dart
│ │ ├── overtime_detail_report_page.dart
│ │ ├── vehicle_detail_report_page.dart
│ │ └── outing_log_report_page.dart
│ └── admin/
│ └── admin_permissions_page.dart
│
└── .gitkeep 文件(各模块目录占位)
```
### 模块依赖关系
```mermaid
graph LR
subgraph features["features/"]
home["home"]
expense["expense"]
expense_apply["expense_application"]
overtime["overtime"]
vehicle["vehicle"]
outing_log["outing_log"]
announcement["announcement"]
report["report"]
admin["admin"]
messages["messages"]
profile["profile"]
shell["shell"]
end
subgraph shared["shared/"]
widgets["widgets"]
models["models"]
end
subgraph core["core/"]
router["router"]
network["network"]
auth["auth"]
theme["theme"]
utils["utils"]
i18n["i18n"]
end
home --> shared
expense --> shared
announcement --> shared
report --> shared
admin --> shared
shell --> router
shared --> core
```
所有 feature 模块依赖 `shared/` 和 `core/`。`shell` 依赖 `router` 管理 Tab 切换。模块之间不直接相互依赖,页面跳转通过 `GoRouter` 完成。
---
## 4. .NET 服务端模块划分
```
Tboss.OA.Server/
├── Controllers/
│ ├── OaPermissionController.cs # 权限管理 API
│ ├── OaExpenseApplyController.cs # 事前申请 API
│ ├── OaExpenseController.cs # 费用报销 API
│ ├── OaOvertimeController.cs # 加班申请 API
│ ├── OaVehicleController.cs # 用车申请 API
│ ├── OaOutingLogController.cs # 外勤日志 API
│ ├── OaAnnouncementController.cs # 公告管理 API
│ ├── OaReportController.cs # 报表 API
│ └── OaApprovalController.cs # 审批代理 API
│
├── Services/
│ ├── PermissionService.cs # 权限校验 + 套餐管理
│ ├── ExpenseApplyService.cs
│ ├── ExpenseService.cs
│ ├── OvertimeService.cs
│ ├── VehicleService.cs
│ ├── OutingLogService.cs
│ ├── AnnouncementService.cs
│ ├── ReportService.cs
│ └── ApplicationNoGenerator.cs # 单据编号原子生成
│
├── ErpIntegration/ # ERP 集成适配器(统一模式,共 9 个)
│ ├── IApprovalAdapter.cs # 审批:创建实例/查询状态/执行动作/撤回
│ ├── IBudgetAdapter.cs # 预算:余额查询/冻结/扣减/释放
│ ├── IProjectAdapter.cs # 项目:列表/级联查询
│ ├── ISubjectAdapter.cs # 预算科目:按项目查科目列表
│ ├── ICostCenterAdapter.cs # 成本中心:列表查询
│ ├── IStandardAdapter.cs # 费用标准:按类型/城市/级别查上限
│ ├── IExchangeRateAdapter.cs # 汇率:币种→汇率查询
│ ├── ICustomerAdapter.cs # 客户:搜索/创建
│ ├── ISupplierAdapter.cs # 供应商:搜索(对公付款选供应商)
│ ├── NullAdapters.cs # 空实现集合(ERP 无能力时返回空/不限)
│ └── ErpAdapterFactory.cs # 按配置选取实现
│
├── Data/
│ ├── Repositories/ # Dapper 数据访问
│ └── Sql/ # SQL 脚本 / 迁移
│
├── Models/
│ ├── Dto/ # 请求/响应 DTO
│ └── Entity/ # 数据库实体映射
│
└── Infrastructure/
├── ErpClient.cs # ERP HTTP 客户端封装
├── MessageClient.cs # 消息推送客户端
└── CacheManager.cs # 报表数据缓存
```
---
## 5. 核心接口清单
### 5.1 OA 权限
| 方法 | URL | 说明 |
|------|-----|------|
| GET | `/api/oa/permissions` | 获取权限点列表 |
| GET | `/api/oa/user-permissions?userId=` | 获取用户权限 |
| PUT | `/api/oa/user-permissions` | 保存用户权限 |
| GET | `/api/oa/permission-changelog?userId=&page=` | 权限变更审计日志 |
### 5.2 业务单据
| 方法 | URL | 说明 |
|------|-----|------|
| POST | `/api/oa/expense-apply/submit` | 提交事前申请 |
| POST | `/api/oa/expense/submit` | 提交费用报销 |
| POST | `/api/oa/overtime/submit` | 提交加班申请 |
| POST | `/api/oa/vehicle/submit` | 提交用车申请 |
| PUT | `/api/oa/{bizType}/draft` | 存草稿 |
| GET | `/api/oa/{bizType}/list?status=&page=` | 列表查询 |
| GET | `/api/oa/{bizType}/detail/:id` | 详情查询 |
| DELETE | `/api/oa/{bizType}/:id` | 软删除单据 |
### 5.3 外勤日志
| 方法 | URL | 说明 |
|------|-----|------|
| PUT | `/api/oa/outing-log/submit` | 提交外勤日志 |
| PUT | `/api/oa/outing-log/:id/view` | 标记已查看 |
| POST | `/api/oa/outing-log/:id/comment` | 主管点评 |
### 5.4 审批代理
| 方法 | URL | 说明 |
|------|-----|------|
| GET | `/api/oa/approval/pending?userId=&bizType=&page=` | 待审批列表 |
| GET | `/api/oa/approval/pending-count?userId=` | 待审批数量 |
| GET | `/api/oa/approval/timeline?bizType=&bizId=` | 审批时间线 |
| POST | `/api/oa/approval/action` | 审批动作(同意/拒绝/转交) |
| POST | `/api/oa/approval/withdraw` | 撤回申请 |
| GET | `/api/oa/approval/my-history?userId=&page=` | 我的审批历史 |
| GET | `/api/oa/approval/subordinates?approverId=&bizType=&page=` | 下属单据列表 |
### 5.5 公告
| 方法 | URL | 说明 |
|------|-----|------|
| POST | `/api/oa/announcement/publish` | 发布/更新公告 |
| POST | `/api/oa/announcement/:id/read` | 标记已读 |
| POST | `/api/oa/announcement/:id/ding` | DING 催办 |
| GET | `/api/oa/announcement/read-stats/:id` | 已读/未读统计 |
### 5.6 报表
| 方法 | URL | 说明 |
|------|-----|------|
| GET | `/api/oa/report/expense-apply?range=&userId=` | 事前申请报表 |
| GET | `/api/oa/report/expense?range=&userId=` | 费用报销报表 |
| GET | `/api/oa/report/overtime?range=&userId=` | 加班报表 |
| GET | `/api/oa/report/vehicle?range=&userId=` | 用车报表 |
| GET | `/api/oa/report/outing-log?range=&userId=` | 外勤日志报表 |
| POST | `/api/oa/report/expense/export` | 导出费用报销 Excel |
### 5.7 基础数据
| 方法 | URL | 说明 |
|------|-----|------|
| GET | `/api/oa/vehicles` | 车辆池列表 |
| POST | `/api/oa/vehicles` | 添加车辆 |
| PUT | `/api/oa/vehicles/:id` | 修改车辆 |
| GET | `/api/oa/banners` | 轮播图列表 |
### 5.8 复用已有 API
| 方法 | URL | 说明 |
|------|-----|------|
| GET | `/api/user/{id}` | 用户信息 |
| GET | `/api/user/search?q=&page=` | 用户搜索 |
| GET | `/api/dept/tree` | 部门树 |
| PUT | `/api/user/avatar` | 上传头像 |
| GET | `/api/customer/search?q=` | 客户联想搜索 |
| GET | `/api/messages?page=` | 消息列表 |
| GET | `/api/messages/unread-count` | 未读消息数 |
| PUT | `/api/messages/:id/read` | 标记已读 |
| POST | `/api/messages/read-all` | 全部已读 |
| DELETE | `/api/messages/:id` | 删除消息 |
| GET | `/api/dict/banks` | 银行列表 |
| GET | `/api/dict/cost-categories` | 费用类别 |
---
## 6. 第三方服务集成方案
### 6.1 ERP 审批引擎
```
集成方式:.NET 服务端 HTTP 调用
认证:ERP 内部 Token / Session(由 .NET 服务端维护)
容错:单套 ERP 超时 15s,失败不影响其他 ERP 查询
重试 3 次,间隔 2s/4s/8s 递增
```
### 6.2 消息推送
```
集成方式:复用 .NET 服务端已有推送模块
通道:App Push(原生通知栏)+ 站内消息(消息列表)
触发点:审批创建/完成/拒绝/转交、公告发布、DING 催办
OA 职责:调用 .NET 消息 API 构造消息体,推送执行由消息模块完成
```
### 6.3 原生能力(MethodChannel)
| 能力 | 平台 | 用途 |
|------|------|------|
| 相机 | Android/iOS | 外勤拍照(防伪水印)、发票拍照、头像拍摄 |
| 相册 | Android/iOS | 附件上传(图片/PDF/文件选择) |
| GPS 定位 | Android/iOS | 外勤签到、用车始发地 |
| 通讯录 | Android/iOS | 用车同行人选人、审批转交选人 |
| 地图选点 | Android/iOS | 用车目的地经纬度选点 |
| 文件分享 | Android/iOS | Excel 导出 → 系统分享面板 |
| PDF 查看 | Android/iOS | 附件 PDF 原生预览 |
### 6.4 支付
```
不涉及。OA 不做在线支付,财务核销是线下打款后录入电汇流水号和记账凭证号。
```
---
## 7. 部署架构
```mermaid
graph TB
subgraph 用户设备["用户设备"]
direction LR
M1["Android App
含 Flutter OA Module"]
M2["iOS App
含 Flutter OA Module"]
end
subgraph 内网["公司内网"]
direction TB
S1["IIS / Windows Server
.NET 4.8 服务端"]
DB1[("SQL Server 2019+
OA 业务库")]
subgraph ERP群["ERP 系统群"]
ERP1["ERP-A
审批 + 主数据"]
ERP2["ERP-B
审批 + 主数据"]
ERP3["ERP-C
审批 + 主数据"]
end
end
M1 -->|"HTTPS"| S1
M2 -->|"HTTPS"| S1
S1 --> DB1
S1 -->|"内网 HTTP"| ERP1
S1 -->|"内网 HTTP"| ERP2
S1 -->|"内网 HTTP"| ERP3
S1 -.->|"App Push
FCM / APNs"| M1
S1 -.->|"App Push
FCM / APNs"| M2
```
### 部署要点
| 组件 | 部署位置 | 说明 |
|------|---------|------|
| Flutter OA Module | 嵌入 Android/iOS App | 随 App 发版更新 |
| .NET 4.8 服务端 | Windows Server + IIS | 新增 OA Controller 部署到现有服务端 |
| SQL Server | 现有数据库实例 | 新增 OA 库或扩展现有库(新建 OA 表) |
| ERP 系统 | 内网已有 | OA 通过内网 HTTP 调用 |
| 推送服务 | FCM (Android) / APNs (iOS) | 已有消息模块管理,OA 不直接对接 |
### 配置项
```xml
```
---
## 8. 安全设计
| 维度 | 方案 |
|------|------|
| 传输安全 | HTTPS(移动端 → 服务端);内网 HTTP(服务端 → ERP) |
| 认证 | 复用宿主 App 的登录态;401 → 触发宿主重新登录 |
| 授权 | OA 端:OaUserPermission ACL;服务端:每个 API 校验权限点 |
| 数据隔离 | 列表查询按 `view_own/view_dept/view_all` 权限控制数据范围 |
| SQL 注入 | Dapper 参数化查询 |
| 审计 | OaPermissionChangeLog 记录所有权限变更 |
| 软删除 | 所有业务表 IsDeleted=1,禁止物理删除 |
---
## 9. ERP 适配器接口定义
### 9.1 IApprovalAdapter
```csharp
public interface IApprovalAdapter
{
// 创建审批实例,返回 ERP 实例 ID
Task SubmitAsync(string bizType, long bizId, object formData, List tags = null);
// tags: ["budget_exceeded", "standard_exceeded"] → ERP 自动插入特批节点
// 查询审批状态
Task GetStatusAsync(string instanceId);
// 查询审批时间线
Task> GetTimelineAsync(string bizType, long bizId, string instanceId);
// 执行审批动作
Task ExecuteActionAsync(string instanceId, string action, string opinion, long? transferToUserId);
// 撤回
Task WithdrawAsync(string instanceId);
// 查询待办列表
Task GetPendingListAsync(long approverId, string bizType, int page, int pageSize);
}
```
### 9.2 IBudgetAdapter
```csharp
public interface IBudgetAdapter
{
// 查余额(含冻结金额)
Task GetBalanceAsync(long projectId, long subjectId);
// 冻结(提交时),返回新 Version
Task FreezeAsync(long projectId, long subjectId, decimal amount);
// 扣减(审批通过时),携带 Version 乐观锁
Task DeductAsync(long projectId, long subjectId, decimal amount, long version);
// 释放(拒绝/撤回时)
Task ReleaseAsync(long projectId, long subjectId, decimal amount);
}
public class BudgetBalance
{
public bool HasBudget { get; set; } // false → OA 前端隐藏预算区块
public decimal AllocatedAmount { get; set; }
public decimal AvailableAmount { get; set; }
public decimal FrozenAmount { get; set; }
public decimal ActualAvailable => AvailableAmount - FrozenAmount;
}
```
### 9.3 IProjectAdapter / ISubjectAdapter / ICostCenterAdapter
```csharp
public interface IProjectAdapter
{
Task> GetProjectsAsync(); // 项目列表
Task> GetSubjectsAsync(long projectId); // 某项目下的预算科目
}
public interface ISubjectAdapter
{
Task> GetSubjectsAsync(long? projectId);
}
public interface ICostCenterAdapter
{
Task> GetCostCentersAsync();
}
public class IdName { public long Id; public string Name; }
```
### 9.4 IStandardAdapter
```csharp
public interface IStandardAdapter
{
Task CheckAsync(string expenseType, string cityLevel, string employeeLevel, decimal amount);
}
public class StandardCheckResult
{
public bool HasStandard { get; set; } // false → OA 不校验
public bool Pass { get; set; }
public decimal MaxAmount { get; set; }
public decimal ExceedAmount { get; set; }
}
```
### 9.5 IExchangeRateAdapter / ICustomerAdapter / ISupplierAdapter
```csharp
public interface IExchangeRateAdapter
{
Task GetRateAsync(string currencyCode);
// { CurrencyCode: "USD", Rate: 7.2456, Date: "2026-06-03" }
}
public interface ICustomerAdapter
{
Task> SearchAsync(string keyword);
Task CreateAsync(string customerName); // 外勤日志新客户自动创建
}
public interface ISupplierAdapter
{
Task> SearchAsync(string keyword);
}
```
### 9.6 NullAdapter 模式
```csharp
// ERP 无某能力时,使用 Null 实现——OA 前端自动隐藏对应区块
public class NullBudgetAdapter : IBudgetAdapter
{
public Task GetBalanceAsync(long p, long s)
=> Task.FromResult(new BudgetBalance { HasBudget = false });
public Task FreezeAsync(long p, long s, decimal a) => Task.FromResult(0L);
public Task DeductAsync(long p, long s, decimal a, long v) => Task.CompletedTask;
public Task ReleaseAsync(long p, long s, decimal a) => Task.CompletedTask;
}
```
## 10. 事前申请→报销:适配器调用链
### 10.1 事前申请提交流程
```mermaid
sequenceDiagram
participant OA as Flutter OA
participant NET as .NET 服务端
participant P as ProjectService
participant S as SubjectService
participant B as BudgetService
participant ST as StandardService
participant A as ApprovalService
OA->>NET: 打开表单
NET->>P: 项目列表
P-->>NET: [{id, name}]
NET->>S: 某项目下科目
S-->>NET: [{id, name}]
NET-->>OA: 级联数据
OA->>NET: 选完项目+科目
NET->>B: 查可用余额
B-->>NET: { hasBudget, available, frozen }
NET-->>OA: 预算余额 ¥XX
OA->>NET: 提交
NET->>ST: 校验费用标准
ST-->>NET: { pass, exceedAmount }
NET->>B: 冻结预算
B-->>NET: { version }
NET->>A: 创建审批实例(含 tags)
A-->>NET: { instanceId }
NET-->>OA: { approvalInstanceId, status }
```
### 10.2 费用报销提交流程
```mermaid
sequenceDiagram
participant OA as Flutter OA
participant NET as .NET 服务端
participant C as CostCenterService
participant E as ExchangeRateService
participant ST as StandardService
participant B as BudgetService
participant A as ApprovalService
OA->>NET: 打开表单
NET->>C: 成本中心列表
C-->>NET: [{id, name}]
NET-->>OA: 下拉数据
OA->>NET: 明细行选外币 USD
NET->>E: 查 USD 汇率
E-->>NET: { rate: 7.2456 }
NET-->>OA: 本币金额 = 原币 × 7.2456
OA->>NET: 提交
NET->>ST: 逐行校验费用标准
ST-->>NET: [{ line, pass, exceedAmount }]
alt 有超标行
NET-->>OA: 超标行标红,tags=["standard_exceeded"]
end
NET->>OA: 查未还借款 → 弹窗确认冲销
OA->>NET: 确认冲销明细
NET->>B: 扣减预算
B-->>NET: ok
NET->>A: 创建审批实例 { tags: [...] }
A-->>NET: { instanceId }
NET->>NET: 写 ExpenseApplicationMapping + LoanRepayment
NET-->>OA: { instanceId, status }
```
### 10.3 适配器覆盖矩阵
| 环节 | Approval | Budget | Project | Subject | CostCenter | Standard | ExchRate | Customer | Supplier |
|------|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
| 事前-填单 | | 余额 | ✅ | ✅ | | | | | |
| 事前-提交 | ✅ 创建 | ✅ 冻结 | | | | ✅ 校验 | | | |
| 事前-审批 | ✅ 流转 | | | | | | | | |
| 事前-通过 | | ✅ 扣减 | | | | | | | |
| 事前-变更 | ✅ 补充 | ✅ 追加冻结 | | | | ✅ | | | |
| 报销-填单 | | | | | ✅ | | ✅ | | ✅ |
| 报销-提交 | ✅ 创建 | ✅ 扣减 | | | | ✅ 校验 | | | |
| 报销-审批 | ✅ 流转 | | | | | | | | |
| 报销-撤回 | ✅ 撤回 | ✅ 释放 | | | | | | | |
## 11. ERP 能力缺位时的 OA 自治扩展
当 ERP 暂无某项能力,后续 OA 需要自建时,扩展路径标准化:
### 11.1 三步扩展法
```
步骤 1: 建表
ERP 无预算 → OA 新建 SysProjectBudget 表(DDL 已就绪)
ERP 无标准 → OA 新建 ExpenseStandard 表 + 管理页
步骤 2: 写实现
新建 OaLocalBudgetAdapter : IBudgetAdapter
新建 OaLocalStandardAdapter : IStandardAdapter
步骤 3: 切配置
appSettings: Oa:Adapter:Budget 从 Null 切为 OaLocal
重启 .NET 服务端生效
```
### 11.2 影响范围
| 层面 | 改动 |
|------|------|
| 数据库 | +1 张表(迁移脚本) |
| .NET 服务端 | +1 个 Adapter 实现类(~50 行) |
| web.config | 改 1 行配置 |
| Flutter 前端 | **零改动**(API 契约不变) |
| OA API | **零改动** |
### 11.3 预设可用性
| 能力 | 当前 | NullAdapter 行为 | 如需自建 |
|------|------|-----------------|---------|
| 预算 | 取决于 ERP | hasBudget=false → OA 隐藏余额 | +SysProjectBudget 表 |
| 费用标准 | 取决于 ERP | hasStandard=false → 不校验超标 | +ExpenseStandard 表+管理页 |
| 汇率 | 取决于 ERP | 只返回 CNY (rate=1) | +汇率表+维护页 |
| 项目 | 始终需要 | 空列表 → 无下拉选项 | +SysProject 管理页 |
| 成本中心 | 可空 | 空列表 → 可空不强制选 | +SysCostCenter 管理页 |
| 供应商 | 可空 | 空列表 → 自由文本输入 | +SysSupplier 管理页 |
---
> **文档版本**:v1.0 | 日期:2026-06-03