# TBOSS OA 模块 — 系统架构设计 > 版本:v1.0 | 日期:2026-06-04 | 基于 PRD v1.0 --- ## 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["预算适配器
BudgetService"] C9["项目/科目适配器
ProjectService / SubjectService"] C10["成本中心适配器
CostCenterService"] C11["汇率适配器
ExchangeRateService"] C12["客户适配器
CustomerService"] end C1 --> C2 C1 --> C3 C1 --> C4 C1 --> C5 C2 --> C12 C3 --> C6 C3 --> C8 C3 --> C9 C3 --> C10 C3 --> C11 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 集成适配器(V1 共 7 个) │ ├── IApprovalAdapter.cs # 审批:创建实例/查询状态/执行动作/撤回 │ ├── IBudgetAdapter.cs # 预算:余额查询/冻结/扣减/释放 │ ├── IProjectAdapter.cs # 项目:列表/级联查询 │ ├── ISubjectAdapter.cs # 预算科目:按项目查科目列表 │ ├── ICostCenterAdapter.cs # 成本中心:列表查询 │ ├── IExchangeRateAdapter.cs # 汇率:币种→汇率查询 │ ├── ICustomerAdapter.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); // 执行审批动作(action: approve / reject) Task ExecuteActionAsync(string instanceId, string action, string opinion); // 撤回 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 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->>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 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->>B: 扣减预算 B-->>NET: ok NET->>A: 创建审批实例 { tags } A-->>NET: { instanceId } NET->>NET: 写 ExpenseApplicationMapping NET-->>OA: { instanceId, status } ``` ### 10.3 适配器覆盖矩阵 | 环节 | Approval | Budget | Project | Subject | CostCenter | ExchRate | Customer | |------|:--:|:--:|:--:|:--:|:--:|:--:|:--:| | 事前-填单 | | 余额 | ✅ | ✅ | | | | | 事前-提交 | ✅ 创建 | ✅ 冻结 | | | | | | | 事前-审批 | ✅ 流转 | | | | | | | | 事前-通过 | | ✅ 扣减 | | | | | | | 报销-填单 | | | | | ✅ | ✅ | | | 报销-提交 | ✅ 创建 | ✅ 扣减 | | | | | | | 报销-审批 | ✅ 流转 | | | | | | | | 报销-撤回 | ✅ 撤回 | ✅ 释放 | | | | | | ## 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 | 只返回 CNY (rate=1) | +汇率表+维护页 | | 项目 | 始终需要 | 空列表 → 无下拉选项 | +SysProject 管理页 | | 成本中心 | 可空 | 空列表 → 可空不强制选 | +SysCostCenter 管理页 | --- > **文档版本**:v1.0 | 日期:2026-06-04