版本:v2.0 | 日期:2026-06-03 | 基于
tboss-oa-product-strategy.mdv2.0 核心变更:审批引擎复用 ERP、用户/组织/客户数据归 ERP 管理、新增 OA 独立权限模型。
| 项目 | 规格 |
|---|---|
| 数据库 | Microsoft SQL Server 2019+ |
| 后端 | .NET Framework 4.8 + Dapper / ADO.NET |
| 移动端 | Flutter 3.38.10(通过 HTTP API 交互,不直连数据库) |
| 字符集 | Chinese_PRC_CI_AS |
| 范畴 | 规范 | 示例 |
|---|---|---|
| 表名 | PascalCase,单数 | Expense, OaPermission |
| 列名 | PascalCase | Id, ApprovalInstanceId |
| 主键 | BIGINT IDENTITY(1,1) |
Id BIGINT IDENTITY(1,1) PRIMARY KEY |
| ERP 引用列 | 存储 ERP ID,非 FK 约束 | ApplicantId BIGINT(注释标注来源 ERP) |
| 约束名 | PK_表名 / UX_表名_列名 |
PK_Expense, UX_Expense_ReportNo |
| 索引名 | IX_表名_用途 |
IX_Expense_List |
每张 OA 自管表必须包含:
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
CreateTime |
DATETIME |
NOT NULL DEFAULT GETDATE() |
服务器授时 |
UpdateTime |
DATETIME |
NULL |
应用层 UPDATE 时显式赋 GETDATE() |
IsDeleted |
BIT |
NOT NULL DEFAULT 0 |
软删除(审计日志除外) |
| 类型 | 精度 | 示例 |
|---|---|---|
| 金额 | DECIMAL(18,2) |
TotalAmount |
| 经纬度 | DECIMAL(10,6) |
CheckInLongitude |
| 税率 | DECIMAL(5,4) |
TaxRate |
| 工时 | DECIMAL(4,1) |
NetOtHours |
所有状态字段使用 VARCHAR(20) 存储英文标识,前端国际化映射。
格式 {前缀}-{YYYYMMDD}-{序号},序号按天重置高位补零至 3 位。
| 业务 | 前缀 | 示例 |
|---|---|---|
| 事前申请 | BXSQ | BXSQ-20260603-001 |
| 费用报销 | BX | BX-20260603-001 |
| 加班申请 | JB | JB-20260603-001 |
| 用车申请 | YC | YC-20260603-001 |
| 外勤日志 | VST | VST-20260603-001 |
┌─────────────────────────────┐
│ OA 权限体系(独立) │
│ OaPermission │
│ OaUserPermission │
│ OaPermissionChangeLog │
└─────────────────────────────┘
[ERP 用户] ◄──── 所有业务表的 ApplicantId / SalespersonId
[ERP 部门] ◄──── 所有业务表的 DeptId
[ERP 客户] ◄──── OutingLog.CustomerId
[ERP 审批] ◄──── ApprovalInstanceId(审批类主表)
[ERP 预算] ◄──── BudgetService 适配器(余额/冻结/释放)
[ERP 项目] ◄──── ProjectService 适配器(列表/级联)
[ERP 科目] ◄──── SubjectService 适配器(预算科目)
[ERP 成本] ◄──── CostCenterService 适配器(成本中心)
[ERP 标准] ◄──── StandardService 适配器(费用标准)
┌──────────────────────────────────────────────────────┐
│ OA 自管业务表 │
│ │
│ ExpenseApplication ──(1:N)── ExpenseAppDetail │
│ │ (明细行) │
│ └──(1:N)── ExpenseApplicationChange (变更记录) │
│ │
│ Expense ──(1:N)── ExpenseDetail │
│ │ (费用明细, 含币种+分摊) │
│ │ │
│ ExpenseApplicationMapping ── 申请↔报销多对多 │
│ ├──(N:1)── Expense │
│ └──(N:1)── ExpenseApplication │
│ │
│ Loan ──(1:N)── LoanRepayment (借款↔还款) │
│ │ │
│ Overtime │
│ │
│ Vehicle ──(1:N)── VehiclePassenger │
│ │ │
│ └──(N:1)── SysVehicle (OA 自管车辆池) │
│ │
│ OutingLog ──(1:N)── OutingLogComment │
│ │
│ Announcement ──(1:N)── AnnouncementTarget │
│ └──(1:N)── AnnouncementReadLog │
│ │
│ Attachment ── 统一附件表 │
│ BizType: expense_apply/expense/outing_log/announcement │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ OA 自管基础表 │
│ SysVehicle — 车辆池 │
│ SysBanner — 工作台轮播图 │
│ SysCostCategory — 费用类别字典 │
└──────────────────────────────────────────────────────┘
关键变更:
- 审批/预算/项目/科目/成本中心/费用标准 → 全部通过 .NET 适配器对接 ERP,OA 本地不存储
Expense.SourceApplicationId→ExpenseApplicationMapping多对多关联- 新增 Loan/LoanRepayment(借款备用金)、ExpenseApplicationChange(申请变更追加)
- 4 张附件表合并为 1 张 Attachment 聚合表
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| Id | BIGINT | ✅ | PK, IDENTITY(1,1) | |
| PermissionCode | VARCHAR(80) | ✅ | UNIQUE | 如 oa.expense.apply |
| PermissionName | NVARCHAR(50) | ✅ | 如"发起报销" | |
| Module | VARCHAR(30) | ✅ | 所属模块:expense / overtime / vehicle / outing_log / announcement / report / admin | |
| SortOrder | INT | ✅ | DEFAULT 0 | 管理界面排序 |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() | |
| UpdateTime | DATETIME |
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| Id | BIGINT | ✅ | PK, IDENTITY(1,1) | |
| UserId | BIGINT | ✅ | ERP 用户 ID(非 FK) | |
| PermissionId | BIGINT | ✅ | FK → OaPermission.Id | |
| GrantedBy | BIGINT | ✅ | 授权人 ERP 用户 ID | |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() | |
| UpdateTime | DATETIME | |||
| IsDeleted | BIT | ✅ | DEFAULT 0 | 移除权限时软删除 |
索引:
UX_UserPermission UNIQUE (UserId, PermissionId) WHERE IsDeleted = 0IX_UserPermission_UserId (UserId, IsDeleted) INCLUDE (PermissionId)| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
| Id | BIGINT | ✅ | PK, IDENTITY(1,1) | |
| TargetUserId | BIGINT | ✅ | 被操作人 ERP 用户 ID | |
| OperatorId | BIGINT | ✅ | 操作人 ERP 用户 ID | |
| ChangeType | VARCHAR(20) | ✅ | assign / revoke / toggle_active |
|
| BeforeSnapshot | NVARCHAR(MAX) | ✅ | 变更前权限 JSON | |
| AfterSnapshot | NVARCHAR(MAX) | ✅ | 变更后权限 JSON | |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
本表无
UpdateTime和IsDeleted— 审计日志不可修改不可删除。
索引:IX_PermChangeLog_Target (TargetUserId, CreateTime DESC) INCLUDE (OperatorId, ChangeType)
变更说明:ApplicantId、DeptId、ProjectId、BudgetSubjectId 不再 FK 到本地表,改为存储 ERP 对应实体 ID。ExpenseType → ExpenseTypes(逗号分隔,支持一次申请多费用类型)。新增审批字段、关联 ERP 集成字段。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK, IDENTITY(1,1) |
| ApplicationNo | VARCHAR(30) | ✅ | UNIQUE,单号 BXSQ-YYYYMMDD-XXX |
| ApplicantId | BIGINT | ✅ | ERP 用户 ID |
| DeptId | BIGINT | ✅ | ERP 部门 ID |
| ApplicationDate | DATE | 业务申请日期(提交时写入) | |
| ProjectId | BIGINT | ERP 项目 ID | |
| BudgetSubjectId | BIGINT | ERP 预算科目 ID | |
| EstimatedAmount | DECIMAL(18,2) | ✅ | DEFAULT 0,明细行汇总 |
| Urgency | VARCHAR(10) | ✅ | DEFAULT 'normal' |
| ExpenseTypes | VARCHAR(200) | ✅ | 费用类型,逗号分隔支持多种。如 "travel,office" |
| Purpose | NVARCHAR(500) | ✅ | 费用事由(≤200字) |
| Status | VARCHAR(20) | ✅ | DEFAULT 'draft' |
| UsageStatus | VARCHAR(20) | ✅ | DEFAULT 'unused' |
| IsTaxIncluded | BIT | ✅ | DEFAULT 0 |
| ValidUntil | DATE | 申请有效期 | |
| ReferenceNo | VARCHAR(50) | 关联合同号/询价单号 | |
| EstimatedStartDate | DATE | 差旅/会议专用 | |
| EstimatedEndDate | DATE | 差旅/会议专用 | |
| IsOvernight | BIT | 差旅专用 | |
| TransportType | VARCHAR(20) | 差旅专用 | |
| EntertainmentTarget | NVARCHAR(200) | 招待专用 | |
| EntertainmentLevel | VARCHAR(20) | 招待专用 | |
| GuestCount | INT | 招待专用 | |
| CompanionCount | INT | 招待专用 | |
| Venue | NVARCHAR(200) | 招待/会议地点 | |
| ApprovalInstanceId | VARCHAR(50) | 新增:ERP 审批实例 ID | |
| ApprovalStatus | VARCHAR(20) | 新增:OA 本地缓存状态 | |
| PreviousInstanceIds | VARCHAR(MAX) | 新增:历史实例 ID JSON 数组 | |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:
IX_ExpenseApp_List (ApplicantId, Status, CreateTime DESC) INCLUDE (ApplicationNo, EstimatedAmount, Purpose, ExpenseType, UsageStatus)IX_ExpenseApp_Import (ApplicantId, Status, UsageStatus) WHERE Status = 'approved' AND UsageStatus IN ('unused', 'partially_used')已通过的事前申请可发起变更(追加金额、增删明细、延期),变更提交后触发补充审批。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| ApplicationId | BIGINT | ✅ | FK → ExpenseApplication.Id |
| ChangeType | VARCHAR(20) | ✅ | amount_increase / amount_decrease / detail_add / detail_remove / date_extend |
| BeforeSnapshot | NVARCHAR(MAX) | ✅ | 变更前 JSON 快照(含明细行) |
| AfterSnapshot | NVARCHAR(MAX) | ✅ | 变更后 JSON 快照 |
| ChangeReason | NVARCHAR(500) | ✅ | 变更事由 |
| Status | VARCHAR(20) | ✅ | DEFAULT 'pending'(pending / approved / rejected) |
| ApprovalInstanceId | VARCHAR(50) | ERP 审批实例 ID | |
| CreateTime | DATETIME | ✅ | |
| UpdateTime | DATETIME |
索引:IX_AppChange_AppId (ApplicationId, CreateTime DESC)
审批通过后:.NET 服务端将 AfterSnapshot 的明细数据写回 ExpenseApplication 和 ExpenseAppDetail,同时更新 EstimatedAmount。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK, IDENTITY(1,1) |
| ApplicationId | BIGINT | ✅ | FK → ExpenseApplication.Id(级联软删除) |
| ExpenseCategory | VARCHAR(20) | ✅ | 费用细分类别(SysCostCategory.CategoryCode) |
| Quantity | INT | ✅ | DEFAULT 1 |
| UnitPrice | DECIMAL(18,2) | ✅ | 单价 |
| Unit | VARCHAR(10) | 单位(张/间/人/天/套/个) | |
| EstimatedAmount | DECIMAL(18,2) | ✅ | 单项预估金额 = Quantity × UnitPrice |
| Remark | NVARCHAR(200) | 明细说明 | |
| SortOrder | INT | ✅ | DEFAULT 1 |
| CreateTime | DATETIME | ✅ | |
| UpdateTime | DATETIME |
索引:IX_ExpenseAppDetail_AppId (ApplicationId, SortOrder) INCLUDE (ExpenseCategory, EstimatedAmount, Remark)
4 种业务附件合并为一张聚合表,通过 BizType 区分业务类型。DetailId 仅报销场景使用(可选绑定到具体明细行),SortOrder 用于外勤照片墙排序。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK, IDENTITY(1,1) |
| BizType | VARCHAR(30) | ✅ | expense_apply / expense / outing_log / announcement |
| BizId | BIGINT | ✅ | 父表 ID(多态,非 FK,与 BizType 联合路由) |
| DetailId | BIGINT | 仅 BizType='expense' 使用,绑定 ExpenseDetail.Id | |
| FileName | NVARCHAR(200) | ✅ | 原始文件名 |
| FileUrl | VARCHAR(500) | ✅ | 云端存储 URL |
| FileType | VARCHAR(20) | ✅ | image / pdf / doc / xls / sign_in_photo / visit_photo / other |
| FileSize | BIGINT | 字节数 | |
| SortOrder | INT | DEFAULT 0,外勤照片墙排序 | |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:
IX_Attachment_Biz (BizType, BizId, IsDeleted) INCLUDE (FileName, FileUrl, FileType, FileSize, DetailId, SortOrder)各业务约束(应用层校验):
| BizType | 数量限制 | FileType 取值 | 额外约束 |
|---|---|---|---|
| expense_apply | ≤6 | image/pdf/doc/xls | 单图≤10MB, PDF≤20MB |
| expense | ≤9 | image/pdf/doc/xls | DetailId 可选绑定明细行;单图≤10MB |
| outing_log | 1~4 | sign_in_photo/visit_photo/other | ≤10MB;提交时≥1 张 |
| announcement | ≤5 | image/pdf/doc/xls | ≤20MB |
合并理由:4 张表结构 90% 相同,唯一差异(DetailId/SortOrder)均为 NULL 允许字段。合并后 CRUD 代码从 4 套降为 1 套,前端一个 AttachmentPicker 组件覆盖全部场景。多态 BizType+BizId 模式与审批/公告范围等模块的架构一致。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| ExpenseId | BIGINT | ✅ | FK → Expense.Id |
| ApplicationId | BIGINT | ✅ | FK → ExpenseApplication.Id |
| ImportedAmount | DECIMAL(18,2) | ✅ | 本次报销从该申请导入的金额 |
| CreateTime | DATETIME | ✅ |
约束:UNIQUE (ExpenseId, ApplicationId)
索引:IX_AppMapping_AppId (ApplicationId) INCLUDE (ExpenseId, ImportedAmount)
UsageStatus 新算法:
已报总额 = SUM(ExpenseApplicationMapping.ImportedAmount)
JOIN Expense ON Expense.Id = ExpenseApplicationMapping.ExpenseId
WHERE Expense.IsDeleted = 0 AND Expense.Status != 'draft'
AND ExpenseApplicationMapping.ApplicationId = @appId
根据已报总额与 EstimatedAmount 比较得出 unused / partially_used / fully_used。
变更说明:删除 SourceApplicationId(改为 ExpenseApplicationMapping 多对多关联)。新增 PaymentType 区分对公/对私。ERP 引用字段改为存储 ID。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| ReportNo | VARCHAR(30) | ✅ | UNIQUE,单号 BX-YYYYMMDD-XXX |
| ApplicantId | BIGINT | ✅ | ERP 用户 ID |
| DeptId | BIGINT | ✅ | ERP 部门 ID |
| ApplicationDate | DATE | 业务日期 | |
| CostCenterId | BIGINT | ERP 成本中心 ID | |
| ProjectId | BIGINT | ERP 项目 ID | |
| BudgetSubjectId | BIGINT | ERP 预算科目 ID | |
| TotalAmount | DECIMAL(18,2) | ✅ | DEFAULT 0 |
| PaymentType | VARCHAR(20) | ✅ | DEFAULT 'personal'(personal=对私报销 / corporate=对公付款) |
| SupplierName | NVARCHAR(200) | 对公付款时填供应商名称 | |
| Purpose | NVARCHAR(500) | ✅ | |
| BankName | NVARCHAR(100) | ✅ | |
| AccountName | NVARCHAR(50) | ✅ | |
| BankAccount | VARCHAR(50) | ✅ | |
| IsInvoiceVerified | BIT | ✅ | DEFAULT 0 |
| IsTaxIdMatched | BIT | ✅ | DEFAULT 0 |
| IsCategoryCompliant | BIT | ✅ | DEFAULT 0 |
| BankTransferNo | VARCHAR(50) | 电汇流水号 | |
| VoucherNo | VARCHAR(50) | 记账凭证号 | |
| PaymentStatus | VARCHAR(20) | ✅ | DEFAULT 'unpaid' |
| Status | VARCHAR(20) | ✅ | DEFAULT 'draft' |
| ApprovalInstanceId | VARCHAR(50) | ||
| ApprovalStatus | VARCHAR(20) | ||
| PreviousInstanceIds | VARCHAR(MAX) | ||
| CreateTime | DATETIME | ✅ | |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:IX_Expense_List (ApplicantId, Status, CreateTime DESC) INCLUDE (TotalAmount, ReportNo, PaymentStatus)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| ExpenseId | BIGINT | ✅ | FK → Expense.Id(级联软删除) |
| ExpenseDate | DATE | ✅ | 费用发生日期 |
| ExpenseType | VARCHAR(20) | ✅ | 费用细分类别(SysCostCategory.CategoryCode) |
| ExpenseDesc | NVARCHAR(200) | ✅ | 费用摘要 |
| Amount | DECIMAL(18,2) | ✅ | 不含税金额(多币种时为原币金额) |
| TaxAmount | DECIMAL(18,2) | ✅ | DEFAULT 0,进项税额 |
| TotalAmount | DECIMAL(18,2) | ✅ | 价税合计(多币种时为原币价税合计) |
| CurrencyCode | VARCHAR(10) | DEFAULT 'CNY',原币币种(USD/EUR/JPY/CNY...) | |
| ExchangeRate | DECIMAL(10,4) | 汇率(CNY 时为 1.0000) | |
| BaseAmount | DECIMAL(18,2) | 折算本币金额(= TotalAmount × ExchangeRate) | |
| InvoiceNo | VARCHAR(50) | 发票号码 | |
| InvoiceCode | VARCHAR(50) | 发票代码 | |
| InvoiceType | VARCHAR(20) | ✅ | special / general / none |
| TaxRate | DECIMAL(5,4) | 税率 | |
| AllocationPercent | DECIMAL(5,2) | DEFAULT NULL(NULL=100%不分摊,如 60.00=60%) | |
| AllocationDeptId | BIGINT | 分摊目标部门 ERP ID | |
| AllocationProjectId | BIGINT | 分摊目标项目 ERP ID | |
| SortOrder | INT | ✅ | DEFAULT 1 |
| CreateTime | DATETIME | ✅ | |
| UpdateTime | DATETIME |
索引:IX_ExpenseDetail_ExpenseId (ExpenseId, SortOrder) INCLUDE (ExpenseDate, ExpenseType, ExpenseDesc, Amount, TotalAmount, InvoiceType)
多币种说明:OA 表单选择币种后从 ERP 汇率表自动填 ExchangeRate,BaseAmount 由 .NET 服务端计算。报表和预算校验均以 BaseAmount(本币)为准。
分摊说明:AllocationPercent = NULL 时不参与分摊。若一笔费用拆到多个部门/项目,前端生成多行(如一行 60%→DeptA,一行 40%→DeptB),AllocationDeptId + AllocationProjectId 指定分摊目标。
变更说明:ApplicantId、DeptId 改为 ERP ID,新增审批字段。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| ApplicationNo | VARCHAR(30) | ✅ | UNIQUE,单号 JB-YYYYMMDD-XXX |
| ApplicantId | BIGINT | ✅ | ERP 用户 ID |
| DeptId | BIGINT | ✅ | ERP 部门 ID |
| OtType | VARCHAR(10) | ✅ | workday / weekend / holiday |
| CompensationType | VARCHAR(20) | ✅ | overtime_pay / comp_leave / mixed |
| CompLeaveRatio | DECIMAL(3,2) | 混合模式调休比例 (0.00~1.00) | |
| StartTime | DATETIME | ||
| EndTime | DATETIME | ||
| NetOtHours | DECIMAL(4,1) | ✅ | DEFAULT 0 |
| Reason | NVARCHAR(500) | ✅ | |
| Status | VARCHAR(20) | ✅ | DEFAULT 'draft' |
| ApprovalInstanceId | VARCHAR(50) | 新增 | |
| ApprovalStatus | VARCHAR(20) | 新增 | |
| PreviousInstanceIds | VARCHAR(MAX) | 新增 | |
| CreateTime | DATETIME | ✅ | |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:IX_Overtime_List (ApplicantId, Status, CreateTime DESC) INCLUDE (ApplicationNo, OtType, NetOtHours, CompensationType)
变更说明:ApplicantId、DeptId 改为 ERP ID。VehicleId 保持 FK 到 OA 自管的 SysVehicle。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| ApplicationNo | VARCHAR(30) | ✅ | UNIQUE,单号 YC-YYYYMMDD-XXX |
| ApplicantId | BIGINT | ✅ | ERP 用户 ID |
| DeptId | BIGINT | ✅ | ERP 部门 ID |
| VehicleId | BIGINT | FK → SysVehicle.Id(OA 自管) | |
| Purpose | VARCHAR(20) | ✅ | reception / business / official |
| Reason | NVARCHAR(500) | ✅ | |
| Origin | NVARCHAR(200) | ✅ | |
| OriginLongitude | DECIMAL(10,6) | ||
| OriginLatitude | DECIMAL(10,6) | ||
| Destination | NVARCHAR(200) | ✅ | |
| DestLongitude | DECIMAL(10,6) | ||
| DestLatitude | DECIMAL(10,6) | ||
| PassengerCount | INT | DEFAULT 1 | |
| StartTime | DATETIME | ||
| EndTime | DATETIME | ||
| ActualReturnTime | DATETIME | 还车登记 | |
| StartOdometer | DECIMAL(10,2) | ||
| EndOdometer | DECIMAL(10,2) | ||
| ActualCost | DECIMAL(18,2) | ||
| CostRemark | NVARCHAR(500) | ||
| Status | VARCHAR(20) | ✅ | DEFAULT 'draft'(含 returned) |
| ApprovalInstanceId | VARCHAR(50) | 新增 | |
| ApprovalStatus | VARCHAR(20) | 新增 | |
| PreviousInstanceIds | VARCHAR(MAX) | 新增 | |
| CreateTime | DATETIME | ✅ | |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:
IX_Vehicle_List (ApplicantId, Status, CreateTime DESC) INCLUDE (ApplicationNo, VehicleId, Purpose, Origin, Destination, StartTime)IX_Vehicle_Collision (VehicleId, StartTime, EndTime) WHERE Status IN ('pending', 'approved')| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| ApplicationId | BIGINT | ✅ | FK → Vehicle.Id |
| UserId | BIGINT | ERP 用户 ID(内部同事,可空) | |
| PassengerName | NVARCHAR(50) | ✅ | 同行人姓名 |
| CreateTime | DATETIME | ✅ | |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:IX_VehiclePassenger_AppId (ApplicationId, IsDeleted) INCLUDE (UserId, PassengerName)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| LicensePlate | NVARCHAR(20) | ✅ | UNIQUE |
| VehicleType | VARCHAR(20) | ✅ | sedan / suv / mpv / van |
| Brand | NVARCHAR(50) | ||
| Seats | INT | ||
| DriverName | NVARCHAR(50) | ||
| Status | VARCHAR(20) | ✅ | DEFAULT 'idle' |
| IsActive | BIT | ✅ | DEFAULT 1 |
| CreateTime | DATETIME | ✅ | |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
变更说明:SalespersonId、DeptId 改为 ERP ID。CustomerId 改为 ERP 客户 ID(非 FK)。外勤日志不走审批流,不需要审批字段。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| VisitNo | VARCHAR(30) | ✅ | UNIQUE,单号 VST-YYYYMMDD-XXX |
| SalespersonId | BIGINT | ✅ | ERP 用户 ID |
| DeptId | BIGINT | ✅ | ERP 部门 ID |
| CustomerId | BIGINT | 变更:ERP 客户 ID(非 FK) | |
| CustomerName | NVARCHAR(200) | ✅ | 客户名冗余字段 |
| ContactId | BIGINT | ERP 客户联系人 ID | |
| CheckInLongitude | DECIMAL(10,6) | ||
| CheckInLatitude | DECIMAL(10,6) | ||
| CheckInAddress | NVARCHAR(500) | 逆地理编码地址(只读) | |
| VisitSummary | NVARCHAR(2000) | ✅ | |
| NextPlan | NVARCHAR(500) | ||
| Status | VARCHAR(20) | ✅ | DEFAULT 'draft'(draft / completed) |
| CreateTime | DATETIME | ✅ | |
| UpdateTime | DATETIME | ||
| LastViewedTime | DATETIME | 员工最后查看时间(红点判断) | |
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:
IX_OutingLog_Sales (SalespersonId, CreateTime DESC) INCLUDE (CustomerId, CustomerName, CheckInAddress)IX_OutingLog_Dept (DeptId, CreateTime DESC) INCLUDE (SalespersonId, CustomerId, CustomerName, CheckInAddress, Status)| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| LogId | BIGINT | ✅ | FK → OutingLog.Id(级联软删除) |
| CommenterId | BIGINT | ✅ | 点评人 ERP 用户 ID |
| RatingStars | INT | 1-5 星,CHECK (RatingStars IS NULL OR (RatingStars >= 1 AND RatingStars <= 5)) | |
| CommentText | NVARCHAR(1000) | ✅ | 批示文字 |
| CreateTime | DATETIME | ✅ | |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:IX_OutingLogComment_LogId (LogId, IsDeleted) INCLUDE (CommenterId, RatingStars, CommentText, CreateTime)
公告模块完全 OA 自闭环,无需审批。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| Title | NVARCHAR(200) | ✅ | |
| Content | NVARCHAR(MAX) | ✅ | HTML/Markdown |
| Type | VARCHAR(20) | ✅ | notice / policy / activity |
| Status | VARCHAR(20) | ✅ | DEFAULT 'draft'(draft / published) |
| PublisherId | BIGINT | ✅ | ERP 用户 ID |
| PublishTime | DATETIME | 发布时间 | |
| IsTop | BIT | ✅ | DEFAULT 0 |
| PrivateLevel | INT | ✅ | DEFAULT 0(0=全员,1=按部门,2=按用户) |
| ExpiryDate | DATETIME | 过期时间(NULL=永不过期) | |
| CreateTime | DATETIME | ✅ | |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:IX_Announcement_List (Type, IsTop DESC, PublishTime DESC) INCLUDE (Title, PublisherId, Status, ExpiryDate) WHERE IsDeleted = 0 AND Status = 'published'
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| AnnouncementId | BIGINT | ✅ | FK → Announcement.Id |
| TargetType | VARCHAR(10) | ✅ | dept / user |
| TargetId | BIGINT | ✅ | ERP 部门 ID 或 ERP 用户 ID(多态,非 FK) |
| CreateTime | DATETIME | ✅ | |
| UpdateTime | DATETIME |
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| AnnouncementId | BIGINT | ✅ | FK → Announcement.Id |
| UserId | BIGINT | ✅ | ERP 用户 ID |
| IsRead | BIT | ✅ | DEFAULT 0 |
| ReadTime | DATETIME | ||
| IsUrged | BIT | ✅ | DEFAULT 0 |
| LastUrgeTime | DATETIME | ||
| CreateTime | DATETIME | ✅ | |
| UpdateTime | DATETIME |
约束:UNIQUE (AnnouncementId, UserId)
索引:IX_Announcement_Ding (AnnouncementId, IsRead, IsUrged) INCLUDE (UserId, ReadTime)
借款需走审批流。报销时可冲销未还借款(多退少补)。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| LoanNo | VARCHAR(30) | ✅ | UNIQUE,单号 JK-YYYYMMDD-XXX |
| ApplicantId | BIGINT | ✅ | ERP 用户 ID |
| DeptId | BIGINT | ✅ | ERP 部门 ID |
| LoanType | VARCHAR(20) | ✅ | imprest(备用金)/ travel_advance(差旅借款) |
| Amount | DECIMAL(18,2) | ✅ | 借款金额 |
| Purpose | NVARCHAR(500) | ✅ | 借款事由 |
| RepaymentStatus | VARCHAR(20) | ✅ | DEFAULT 'outstanding' |
| Status | VARCHAR(20) | ✅ | DEFAULT 'draft' |
| ApprovalInstanceId | VARCHAR(50) | ERP 审批实例 ID | |
| ApprovalStatus | VARCHAR(20) | ||
| PreviousInstanceIds | VARCHAR(MAX) | ||
| CreateTime | DATETIME | ✅ | |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:IX_Loan_List (ApplicantId, Status, CreateTime DESC) INCLUDE (LoanNo, Amount, LoanType, RepaymentStatus)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| LoanId | BIGINT | ✅ | FK → Loan.Id |
| ExpenseId | BIGINT | 关联报销单(报销冲销时填) | |
| RepaymentType | VARCHAR(20) | ✅ | expense_offset(报销冲销)/ cash_return(现金还款)/ bank_transfer(银行转账) |
| Amount | DECIMAL(18,2) | ✅ | 本次还款金额 |
| CreateTime | DATETIME | ✅ |
索引:IX_LoanRepayment_LoanId (LoanId) INCLUDE (ExpenseId, RepaymentType, Amount)
还款逻辑:
OA 自管,为报销/申请表单的费用类别下拉提供数据源,支持二级分类。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| CategoryName | NVARCHAR(50) | ✅ | |
| CategoryCode | VARCHAR(30) | ✅ | UNIQUE |
| ParentId | BIGINT | FK → SysCostCategory.Id(自引用) | |
| BizScope | VARCHAR(20) | ✅ | DEFAULT 'both'(expense_apply / expense / both) |
| ExpenseType | VARCHAR(20) | 绑定费用大类 | |
| SortOrder | INT | ✅ | DEFAULT 0 |
| IsActive | BIT | ✅ | DEFAULT 1 |
| CreateTime | DATETIME | ✅ | |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:
IX_CostCategory_Parent (ParentId, SortOrder) INCLUDE (CategoryName, CategoryCode) WHERE IsActive = 1IX_CostCategory_Scope (BizScope, ExpenseType, ParentId, SortOrder) INCLUDE (CategoryName, CategoryCode) WHERE IsActive = 1 AND IsDeleted = 0| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | PK |
| ImageUrl | VARCHAR(500) | ✅ | |
| Title | NVARCHAR(100) | ||
| LinkUrl | VARCHAR(500) | 可空,为空则不可点击 | |
| SortOrder | INT | ✅ | DEFAULT 0 |
| IsActive | BIT | ✅ | DEFAULT 1 |
| CreateTime | DATETIME | ✅ | |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:IX_Banner_Active (IsActive, SortOrder) INCLUDE (ImageUrl, Title, LinkUrl) WHERE IsDeleted = 0
所有 OA 自管表统一使用 IsDeleted 软删除。SELECT/JOIN 必须加 WHERE IsDeleted = 0。
例外:OaPermissionChangeLog(审计日志不可删除)。
审批数据不在 OA 本地存储。ApprovalInstanceId + ApprovalStatus + PreviousInstanceIds 三个字段的作用:
| 字段 | 作用 | 更新时机 |
|---|---|---|
ApprovalInstanceId |
关联 ERP 审批实例 | 提交审批时写入;驳回重新提交时覆盖 |
ApprovalStatus |
列表页秒开缓存 | 提交/审批操作后写入;下拉刷新时从 ERP 校准 |
PreviousInstanceIds |
历史追溯 | 驳回/撤回重新提交时追加旧 instanceId |
状态同步流程:
列表页 → 读 ApprovalStatus(缓存,秒开)
→ 后台静默调 .NET → ERP 刷新
详情页 → 打开时实时调 .NET → ERP 校准
审批操作 → OA 调 .NET → ERP 执行 → 返回结果 → 写缓存
UsageStatus 由 .NET 服务端在报销单状态变更时自动重算(逻辑不变):
| 已报总额 | UsageStatus |
|---|---|
| = 0 | unused |
| > 0 且 < EstimatedAmount | partially_used |
| ≥ EstimatedAmount | fully_used |
.NET 服务端使用 sp_getapplock + SELECT MAX(ApplicationNo) 原子生成,逻辑不变。
| 主表 | 子表 | 方式 |
|---|---|---|
| ExpenseApplication | ExpenseAppDetail, Attachment (BizType='expense_apply') | 主表软删除时同步子表软删除 |
| Expense | ExpenseDetail, ExpenseApplicationMapping, Attachment (BizType='expense') | 同上 |
| OutingLog | OutingLogComment, Attachment (BizType='outing_log') | 同上 |
| Announcement | AnnouncementTarget, AnnouncementReadLog, Attachment (BizType='announcement') | 同上 |
| Loan | LoanRepayment | 同上 |
报销单首次提交审批通过后(Status 变为 approved),.NET 服务端将收款银行信息回写到 ERP 用户扩展属性。
OutingLogComment.CreateTime > COALESCE(LastViewedTime, '1900-01-01') 且 CommenterId != SalespersonId 时显示"新点评"标记。
Overtime:
CHECK (StartTime IS NULL OR EndTime IS NULL OR StartTime < EndTime)CHECK (CompLeaveRatio IS NULL OR (CompLeaveRatio > 0 AND CompLeaveRatio < 1))Vehicle:
CHECK (StartTime IS NULL OR EndTime IS NULL OR StartTime < EndTime)CHECK (EndOdometer IS NULL OR StartOdometer IS NULL OR EndOdometer > StartOdometer)OutingLogComment:
CHECK (RatingStars IS NULL OR (RatingStars >= 1 AND RatingStars <= 5))管理员发布公告(Status 从 draft → published)时,.NET 服务端根据 PrivateLevel 批量写入 AnnouncementReadLog(IsRead=0)。草稿状态不触发。新员工入职不回溯历史公告。
业务员输入不存在的客户名称时,提交时 .NET 服务端在同一事务内:1) 调 ERP 创建客户 → 获取新 ID;2) 回写 OutingLog.CustomerId。
以下能力全部通过 .NET 服务端适配器层对接 ERP,OA 本地不存储:
| 能力 | 适配器 | OA 调用的典型场景 |
|---|---|---|
| 预算余额查询 | BudgetService | 事前申请表单:选项目+科目后展示可用余额 |
| 预算冻结/扣减/释放 | BudgetService | 提交审批/通过/拒绝/撤回时自动处理 |
| 项目列表/级联 | ProjectService | 表单下拉:选项目→加载下级的预算科目 |
| 预算科目列表 | SubjectService | 同上 |
| 成本中心列表 | CostCenterService | 报销表单成本中心下拉 |
| 费用标准查询 | StandardService | 提交校验:住宿是否超 500/晚、高铁是否二等座 |
| 汇率表查询 | ExchangeRateService | 报销明细选外币→自动填汇率 |
ERP 无某能力时:该 Adapter 返回空/不限,OA 前端自动隐藏对应区块。例如 ERP 无预算 → 不展示余额、不校验超支。
员工提交报销时,.NET 服务端按费用类型+城市级别+员工级别查询 ERP 费用标准,超标明细行标红 + 自动触发超标特批审批链。标准表在 ERP 维护,常见维度:
{ expenseType, cityLevel, employeeLevel, maxAmount, maxQuantity }
例:{ hotel, 一线城市, 普通员工, 500, 1 }
报销提交时 .NET 服务端自动查询该员工未还借款(Loan WHERE ApplicantId=@uid AND RepaymentStatus IN ('outstanding','partially_repaid') AND Status='approved'):
RepaymentStatus 保持 outstanding-- 事前申请列表
CREATE NONCLUSTERED INDEX IX_ExpenseApp_List
ON ExpenseApplication (ApplicantId, Status, CreateTime DESC)
INCLUDE (ApplicationNo, EstimatedAmount, Purpose, ExpenseType, UsageStatus);
-- 事前申请一键导入
CREATE NONCLUSTERED INDEX IX_ExpenseApp_Import
ON ExpenseApplication (ApplicantId, Status, UsageStatus)
WHERE Status = 'approved' AND UsageStatus IN ('unused', 'partially_used');
-- 费用报销列表
CREATE NONCLUSTERED INDEX IX_Expense_List
ON Expense (ApplicantId, Status, CreateTime DESC)
INCLUDE (TotalAmount, ReportNo, PaymentStatus);
-- 报销关联申请
CREATE NONCLUSTERED INDEX IX_Expense_SourceApp
ON Expense (SourceApplicationId) WHERE SourceApplicationId IS NOT NULL;
-- 加班列表
CREATE NONCLUSTERED INDEX IX_Overtime_List
ON Overtime (ApplicantId, Status, CreateTime DESC)
INCLUDE (ApplicationNo, OtType, NetOtHours, CompensationType);
-- 用车列表
CREATE NONCLUSTERED INDEX IX_Vehicle_List
ON Vehicle (ApplicantId, Status, CreateTime DESC)
INCLUDE (ApplicationNo, VehicleId, Purpose, Origin, Destination, StartTime);
-- 排期冲突
CREATE NONCLUSTERED INDEX IX_Vehicle_Collision
ON Vehicle (VehicleId, StartTime, EndTime)
WHERE Status IN ('pending', 'approved');
-- 外勤日志-业务员
CREATE NONCLUSTERED INDEX IX_OutingLog_Sales
ON OutingLog (SalespersonId, CreateTime DESC)
INCLUDE (CustomerId, CustomerName, CheckInAddress);
-- 外勤日志-部门
CREATE NONCLUSTERED INDEX IX_OutingLog_Dept
ON OutingLog (DeptId, CreateTime DESC)
INCLUDE (SalespersonId, CustomerId, CustomerName, CheckInAddress, Status);
-- 公告列表
CREATE NONCLUSTERED INDEX IX_Announcement_List
ON Announcement (Type, IsTop DESC, PublishTime DESC)
INCLUDE (Title, PublisherId, Status, ExpiryDate)
WHERE IsDeleted = 0 AND Status = 'published';
-- 公告触达审计
CREATE NONCLUSTERED INDEX IX_Announcement_Ding
ON AnnouncementReadLog (AnnouncementId, IsRead, IsUrged)
INCLUDE (UserId, ReadTime);
-- OA 用户权限
CREATE UNIQUE NONCLUSTERED INDEX UX_UserPermission
ON OaUserPermission (UserId, PermissionId) WHERE IsDeleted = 0;
-- 权限变更审计
CREATE NONCLUSTERED INDEX IX_PermChangeLog_Target
ON OaPermissionChangeLog (TargetUserId, CreateTime DESC)
INCLUDE (OperatorId, ChangeType);
-- 费用类别级联
CREATE NONCLUSTERED INDEX IX_CostCategory_Parent
ON SysCostCategory (ParentId, SortOrder)
INCLUDE (CategoryName, CategoryCode) WHERE IsActive = 1;
-- 轮播图
CREATE NONCLUSTERED INDEX IX_Banner_Active
ON SysBanner (IsActive, SortOrder)
INCLUDE (ImageUrl, Title, LinkUrl) WHERE IsDeleted = 0;
-- 统一附件查询
CREATE NONCLUSTERED INDEX IX_Attachment_Biz
ON Attachment (BizType, BizId, IsDeleted)
INCLUDE (FileName, FileUrl, FileType, FileSize, DetailId, SortOrder);
-- 申请↔报销多对多
CREATE NONCLUSTERED INDEX IX_AppMapping_AppId
ON ExpenseApplicationMapping (ApplicationId)
INCLUDE (ExpenseId, ImportedAmount);
-- 借款列表
CREATE NONCLUSTERED INDEX IX_Loan_List
ON Loan (ApplicantId, Status, CreateTime DESC)
INCLUDE (LoanNo, Amount, LoanType, RepaymentStatus);
-- 还款记录
CREATE NONCLUSTERED INDEX IX_LoanRepayment_LoanId
ON LoanRepayment (LoanId)
INCLUDE (ExpenseId, RepaymentType, Amount);
-- 申请变更记录
CREATE NONCLUSTERED INDEX IX_AppChange_AppId
ON ExpenseApplicationChange (ApplicationId, CreateTime DESC);
-- 外勤点评
CREATE NONCLUSTERED INDEX IX_OutingLogComment_LogId
ON OutingLogComment (LogId, IsDeleted)
INCLUDE (CommenterId, RatingStars, CommentText, CreateTime);
| 值 | 含义 | 适用表 |
|---|---|---|
draft |
草稿 | ExpenseApplication, Expense, Overtime, Vehicle, Announcement |
pending |
待审批 | ExpenseApplication, Expense, Overtime, Vehicle |
approved |
已通过 | ExpenseApplication, Expense, Overtime, Vehicle |
rejected |
已拒绝 | ExpenseApplication, Expense, Overtime, Vehicle |
withdrawn |
已撤回 | ExpenseApplication, Expense, Overtime, Vehicle |
returned |
已还车 | Vehicle(仅此表) |
completed |
已提交 | OutingLog |
published |
已发布 | Announcement |
| 值 | 含义 |
|---|---|
unpaid |
未付款 |
paying |
付款中 |
paid |
已付款 |
| 值 | 含义 |
|---|---|
travel |
差旅费 |
entertainment |
业务招待费 |
procurement |
日常采购 |
activity |
活动经费 |
office |
办公费 |
meeting |
会议费 |
training |
培训费 |
例:"travel,office" 表示一次申请同时涵盖差旅费和办公费。
| 值 | 含义 |
|---|---|
normal |
普通 |
urgent |
紧急 |
critical |
特急 |
| 值 | 含义 |
|---|---|
plane |
飞机 |
high_speed_rail |
高铁/动车 |
train |
火车(普速) |
self_drive |
自驾 |
| 值 | 含义 |
|---|---|
normal |
普通人员 |
important |
重要人员 |
vip |
VIP |
| 值 | 含义 |
|---|---|
unused |
未被报销引用 |
partially_used |
部分已报销 |
fully_used |
已全部报销 |
| 值 | 含义 | InvoiceNo/InvoiceCode |
|---|---|---|
special |
增值税专用发票 | 必填 |
general |
增值税普通发票 | 必填 |
none |
无发票 | 允许 NULL |
| 值 | 含义 |
|---|---|
workday |
工作日加班 |
weekend |
休息日加班 |
holiday |
法定节假日加班 |
| 值 | 含义 |
|---|---|
overtime_pay |
全部结算加班费 |
comp_leave |
全部转为调休 |
mixed |
混合模式 |
| 值 | 含义 |
|---|---|
reception |
客户接待 |
business |
商务出行 |
official |
公务 |
| 值 | 含义 |
|---|---|
sedan |
轿车 |
suv |
SUV |
mpv |
商务车 |
van |
面包车 |
| 值 | 含义 |
|---|---|
idle |
空闲可用 |
in_use |
使用中 |
maintenance |
维修中 |
| 值 | 含义 |
|---|---|
notice |
通知公告 |
policy |
人事与制度 |
activity |
放假与活动 |
| PrivateLevel | 含义 | TargetType | 含义 |
|---|---|---|---|
0 |
全员 | — | — |
1 |
按部门 | dept |
TargetId → ERP 部门 ID |
2 |
按用户 | user |
TargetId → ERP 用户 ID |
| 值 | 含义 |
|---|---|
expense_apply |
仅事前申请端 |
expense |
仅费用报销端 |
both |
两端通用 |
| 值 | 含义 | 适用 BizType |
|---|---|---|
image |
图片(JPG/PNG/WebP) | 通用 |
pdf |
PDF 文档 | expense_apply / expense / announcement |
doc |
Word 文档 | expense_apply / expense / announcement |
xls |
Excel 表格 | expense_apply / expense / announcement |
sign_in_photo |
外勤签到照(水印含授时+GPS) | outing_log |
visit_photo |
外勤拜访现场照 | outing_log |
other |
其他 | outing_log |
| 值 | 含义 |
|---|---|
personal |
对私报销(还给员工) |
corporate |
对公付款(付给供应商) |
| 值 | 含义 |
|---|---|
imprest |
备用金 |
travel_advance |
差旅借款 |
| 值 | 含义 |
|---|---|
outstanding |
未还 |
partially_repaid |
部分已还 |
fully_repaid |
已还清 |
written_off |
已核销 |
| 值 | 含义 |
|---|---|
expense_offset |
报销冲销 |
cash_return |
现金还款 |
bank_transfer |
银行转账 |
| 值 | 含义 |
|---|---|
amount_increase |
追加金额 |
amount_decrease |
减少金额 |
detail_add |
新增明细行 |
detail_remove |
删除明细行 |
date_extend |
延期 |
| 字段 | 0 的含义 | 1 的含义 |
|---|---|---|
IsDeleted |
正常 | 已软删除 |
IsActive |
停用/禁用 | 启用 |
IsRead |
未读 | 已读 |
IsTop |
普通 | 置顶 |
IsUrged |
未催办 | 已催办 |
IsInvoiceVerified |
未查验 | 已查验 |
IsTaxIdMatched |
未匹配 | 已匹配 |
IsCategoryCompliant |
不合规 | 合规 |
| 值 | 对应权限点前缀 |
|---|---|
expense |
oa.expense.* |
expense_apply |
oa.expense_apply.* |
overtime |
oa.overtime.* |
vehicle |
oa.vehicle.* |
outing_log |
oa.outing_log.* |
announcement |
oa.announcement.* |
report |
oa.report.* |
admin |
oa.admin.* |
| 值 | 含义 |
|---|---|
assign |
赋予权限 |
revoke |
移除权限 |
toggle_active |
启用/禁用 |
以下示例基于 Dapper + ADO.NET(.NET Framework 4.8 服务端)。ERP 侧的数据(用户/部门/客户/审批)不在此处查询,均通过 API 获取。
const string sql = @"
SELECT
e.Id, e.ReportNo, e.TotalAmount, e.Purpose,
e.BankName, e.AccountName, e.BankAccount,
e.IsInvoiceVerified, e.IsTaxIdMatched, e.IsCategoryCompliant,
e.BankTransferNo, e.VoucherNo,
e.Status, e.PaymentStatus, e.ApplicationDate, e.CreateTime,
e.ApprovalInstanceId, e.ApprovalStatus, e.PreviousInstanceIds,
e.SourceApplicationId,
ea.ApplicationNo AS SourceApplicationNo
FROM Expense e
LEFT JOIN ExpenseApplication ea ON ea.Id = e.SourceApplicationId AND ea.IsDeleted = 0
WHERE e.Id = @expenseId AND e.IsDeleted = 0;
-- 明细行(含币种+分摊)
SELECT ed.Id, ed.ExpenseDate, ed.ExpenseType, ed.ExpenseDesc,
ed.Amount, ed.TaxAmount, ed.TotalAmount,
ed.CurrencyCode, ed.ExchangeRate, ed.BaseAmount,
ed.AllocationPercent, ed.AllocationDeptId, ed.AllocationProjectId,
ed.InvoiceNo, ed.InvoiceCode, ed.InvoiceType, ed.TaxRate, ed.SortOrder,
cc.CategoryName AS ExpenseTypeName
FROM ExpenseDetail ed
LEFT JOIN SysCostCategory cc ON cc.CategoryCode = ed.ExpenseType AND cc.IsDeleted = 0
WHERE ed.ExpenseId = @expenseId
ORDER BY ed.SortOrder;
-- 关联的事前申请(多对多)
SELECT eam.ApplicationId, eam.ImportedAmount,
ea.ApplicationNo, ea.EstimatedAmount, ea.ExpenseTypes
FROM ExpenseApplicationMapping eam
INNER JOIN ExpenseApplication ea ON ea.Id = eam.ApplicationId AND ea.IsDeleted = 0
WHERE eam.ExpenseId = @expenseId;
-- 附件
SELECT Id, FileName, FileUrl, FileType, FileSize, DetailId, SortOrder
FROM Attachment
WHERE BizType = 'expense' AND BizId = @expenseId AND IsDeleted = 0
ORDER BY SortOrder, CreateTime;
";
// 注意:申请人姓名、部门名称通过 .NET API GET /api/user/{ApplicantId} 获取
// 审批时间线通过 .NET API GET /api/oa/approval/timeline?bizType=expense&bizId={id} 获取
public async Task UpdateUsageStatusAsync(
IDbConnection conn, IDbTransaction tx, long applicationId)
{
const string sql = @"
UPDATE ea
SET ea.UsageStatus =
CASE
WHEN ISNULL(SUM(eam.ImportedAmount), 0) = 0 THEN 'unused'
WHEN ISNULL(SUM(eam.ImportedAmount), 0) >= ea.EstimatedAmount THEN 'fully_used'
ELSE 'partially_used'
END,
ea.UpdateTime = GETDATE()
FROM ExpenseApplication ea
LEFT JOIN ExpenseApplicationMapping eam ON eam.ApplicationId = ea.Id
LEFT JOIN Expense e ON e.Id = eam.ExpenseId AND e.IsDeleted = 0 AND e.Status != 'draft'
WHERE ea.Id = @applicationId
GROUP BY ea.Id, ea.EstimatedAmount;";
await conn.ExecuteAsync(sql, new { applicationId }, tx);
}
public async Task<string> GenerateApplicationNoAsync(
IDbConnection conn, IDbTransaction tx, string prefix, DateTime date)
{
var dateStr = date.ToString("yyyyMMdd");
var pattern = $"{prefix}-{dateStr}-%";
var lockKey = $"AppNo_{prefix}_{dateStr}";
await conn.ExecuteAsync("sp_getapplock @Resource, @LockMode, @LockOwner", new
{
Resource = lockKey, LockMode = "Exclusive", LockOwner = "Transaction"
}, tx);
const string sql = @"
SELECT MAX(ApplicationNo)
FROM ExpenseApplication WITH (UPDLOCK, ROWLOCK)
WHERE ApplicationNo LIKE @pattern";
var maxNo = await conn.QuerySingleOrDefaultAsync<string>(sql, new { pattern }, tx);
int seq = 1;
if (maxNo != null)
{
var seqStr = maxNo.Substring(maxNo.Length - 3);
seq = int.Parse(seqStr) + 1;
}
return $"{prefix}-{dateStr}-{seq:D3}";
}
// 注意:以上逻辑适用于所有单据类型,替换表名和前缀即可
审批数据由 ERP 引擎管理,OA 本地不存储。经理端"待我审批"通过 .NET 服务端 API 获取:
GET /api/oa/approval/pending?userId={erpUserId}&bizType=&page=1
响应示例:
{
"items": [
{
"bizType": "expense",
"bizId": 12345,
"bizNo": "BX-20260603-001",
"amount": 1580.00,
"summary": "差旅费报销",
"submitTime": "2026-06-03T10:30:00",
"applicantName": "张三",
"deptName": "销售部"
}
],
"total": 8
}
// 判断用户是否有某个权限点
public async Task<bool> HasPermissionAsync(
IDbConnection conn, long erpUserId, string permissionCode)
{
const string sql = @"
SELECT COUNT(1)
FROM OaUserPermission up
INNER JOIN OaPermission p ON p.Id = up.PermissionId
WHERE up.UserId = @erpUserId
AND p.PermissionCode = @permissionCode
AND up.IsDeleted = 0";
var count = await conn.ExecuteScalarAsync<int>(sql, new { erpUserId, permissionCode });
return count > 0;
}
// 获取用户所有权限点
public async Task<List<string>> GetUserPermissionsAsync(
IDbConnection conn, long erpUserId)
{
const string sql = @"
SELECT p.PermissionCode
FROM OaUserPermission up
INNER JOIN OaPermission p ON p.Id = up.PermissionId
WHERE up.UserId = @erpUserId AND up.IsDeleted = 0";
var permissions = await conn.QueryAsync<string>(sql, new { erpUserId });
return permissions.ToList();
}
文档版本:v1.0 | 日期:2026-06-03