tboss-oa-database.md 51 KB

TBOSS OA 模块 — 数据库表结构设计

版本:v2.0 | 日期:2026-06-03 | 基于 tboss-oa-product-strategy.md

v2.0 核心变更:审批引擎复用 ERP、用户/组织/客户数据归 ERP 管理、新增 OA 独立权限模型。


1. 设计约定

1.1 技术栈

项目 规格
数据库 Microsoft SQL Server 2019+
后端 .NET Framework 4.8 + Dapper / ADO.NET
移动端 Flutter 3.38.10(通过 HTTP API 交互,不直连数据库)
字符集 Chinese_PRC_CI_AS

1.2 命名规范

范畴 规范 示例
表名 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

1.3 全局字段

每张 OA 自管表必须包含:

字段 类型 约束 说明
CreateTime DATETIME NOT NULL DEFAULT GETDATE() 服务器授时
UpdateTime DATETIME NULL 应用层 UPDATE 时显式赋 GETDATE()
IsDeleted BIT NOT NULL DEFAULT 0 软删除(审计日志除外)

1.4 精度规范

类型 精度 示例
金额 DECIMAL(18,2) TotalAmount
经纬度 DECIMAL(10,6) CheckInLongitude
税率 DECIMAL(5,4) TaxRate
工时 DECIMAL(4,1) NetOtHours

1.5 状态枚举

所有状态字段使用 VARCHAR(20) 存储英文标识,前端国际化映射。

1.6 单据编号

格式 {前缀}-{YYYYMMDD}-{序号},序号按天重置高位补零至 3 位。

业务 前缀 示例
事前申请 BXSQ BXSQ-20260603-001
费用报销 BX BX-20260603-001
加班申请 JB JB-20260603-001
用车申请 YC YC-20260603-001
外勤日志 VST VST-20260603-001

2. 数据库 ERD

                        ┌─────────────────────────────┐
                        │     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.SourceApplicationIdExpenseApplicationMapping 多对多关联
  • 新增 Loan/LoanRepayment(借款备用金)、ExpenseApplicationChange(申请变更追加)
  • 4 张附件表合并为 1 张 Attachment 聚合表

3. 表结构定义

3.1 OA 权限体系(新增)

3.1.1 OaPermission — 权限点字典

字段 类型 必填 约束 说明
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

3.1.2 OaUserPermission — 用户-权限关联(ACL)

字段 类型 必填 约束 说明
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 = 0
  • IX_UserPermission_UserId (UserId, IsDeleted) INCLUDE (PermissionId)

3.1.3 OaPermissionChangeLog — 权限变更审计

字段 类型 必填 约束 说明
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()

本表无 UpdateTimeIsDeleted — 审计日志不可修改不可删除。

索引IX_PermChangeLog_Target (TargetUserId, CreateTime DESC) INCLUDE (OperatorId, ChangeType)


3.2 事前申请与费用报销

3.2.1 ExpenseApplication — 事前申请主表

变更说明ApplicantIdDeptIdProjectIdBudgetSubjectId 不再 FK 到本地表,改为存储 ERP 对应实体 ID。ExpenseTypeExpenseTypes(逗号分隔,支持一次申请多费用类型)。新增审批字段、关联 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')

3.2.2 ExpenseApplicationChange — 申请变更/追加记录

已通过的事前申请可发起变更(追加金额、增删明细、延期),变更提交后触发补充审批。

字段 类型 必填 说明
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 的明细数据写回 ExpenseApplicationExpenseAppDetail,同时更新 EstimatedAmount


3.2.3 ExpenseAppDetail — 事前申请明细

字段 类型 必填 说明
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)

3.2.4 Attachment — 统一附件表

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 模式与审批/公告范围等模块的架构一致。

3.2.5 ExpenseApplicationMapping — 申请↔报销多对多关联

字段 类型 必填 说明
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。


3.2.6 Expense — 费用报销主表

变更说明:删除 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)

3.2.7 ExpenseDetail — 费用报销明细

字段 类型 必填 说明
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 汇率表自动填 ExchangeRateBaseAmount 由 .NET 服务端计算。报表和预算校验均以 BaseAmount(本币)为准。

分摊说明AllocationPercent = NULL 时不参与分摊。若一笔费用拆到多个部门/项目,前端生成多行(如一行 60%→DeptA,一行 40%→DeptB),AllocationDeptId + AllocationProjectId 指定分摊目标。


3.3 加班申请

3.3.1 Overtime

变更说明ApplicantIdDeptId 改为 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)


3.4 用车申请

3.4.1 Vehicle

变更说明ApplicantIdDeptId 改为 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')

3.4.2 VehiclePassenger — 同行人员

字段 类型 必填 说明
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)

3.4.3 SysVehicle — 车辆池(OA 自管,不变)

字段 类型 必填 说明
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

3.5 外勤日志

3.5.1 OutingLog

变更说明SalespersonIdDeptId 改为 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)

3.5.2 OutingLogComment — 主管点评

字段 类型 必填 说明
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)


3.6 公告管理

公告模块完全 OA 自闭环,无需审批。

3.6.1 Announcement

字段 类型 必填 说明
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'

3.6.2 AnnouncementTarget — 发布范围

字段 类型 必填 说明
Id BIGINT PK
AnnouncementId BIGINT FK → Announcement.Id
TargetType VARCHAR(10) dept / user
TargetId BIGINT ERP 部门 ID 或 ERP 用户 ID(多态,非 FK)
CreateTime DATETIME
UpdateTime DATETIME

3.6.3 AnnouncementReadLog — 触达审计

字段 类型 必填 说明
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)


3.7 借款/备用金

3.7.1 Loan — 借款/备用金主表

借款需走审批流。报销时可冲销未还借款(多退少补)。

字段 类型 必填 说明
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)

3.7.2 LoanRepayment — 还款/冲销记录

字段 类型 必填 说明
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)

还款逻辑

  • Sum(LoanRepayment.Amount) < Loan.Amount → outstanding
  • Sum >= Loan.Amount → fully_repaid
  • 报销提交时自动匹配未还借款 → 报销金额先冲销借款 → 余款退/补
  • 报销金额 < 借款余额 → 差额部分员工需现金/转账补还

3.8 基础字典

3.7.1 SysCostCategory — 费用类别字典

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 = 1
  • IX_CostCategory_Scope (BizScope, ExpenseType, ParentId, SortOrder) INCLUDE (CategoryName, CategoryCode) WHERE IsActive = 1 AND IsDeleted = 0

3.7.2 SysBanner — 工作台轮播图

字段 类型 必填 说明
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


4. 业务逻辑与约束

4.1 软删除

所有 OA 自管表统一使用 IsDeleted 软删除。SELECT/JOIN 必须加 WHERE IsDeleted = 0。 例外:OaPermissionChangeLog(审计日志不可删除)。

4.2 审批状态管理

审批数据不在 OA 本地存储。ApprovalInstanceId + ApprovalStatus + PreviousInstanceIds 三个字段的作用:

字段 作用 更新时机
ApprovalInstanceId 关联 ERP 审批实例 提交审批时写入;驳回重新提交时覆盖
ApprovalStatus 列表页秒开缓存 提交/审批操作后写入;下拉刷新时从 ERP 校准
PreviousInstanceIds 历史追溯 驳回/撤回重新提交时追加旧 instanceId

状态同步流程

列表页 → 读 ApprovalStatus(缓存,秒开)
       → 后台静默调 .NET → ERP 刷新
详情页 → 打开时实时调 .NET → ERP 校准
审批操作 → OA 调 .NET → ERP 执行 → 返回结果 → 写缓存

4.3 事前申请-报销联动

UsageStatus 由 .NET 服务端在报销单状态变更时自动重算(逻辑不变):

已报总额 UsageStatus
= 0 unused
> 0 且 < EstimatedAmount partially_used
≥ EstimatedAmount fully_used

4.4 单据编号生成

.NET 服务端使用 sp_getapplock + SELECT MAX(ApplicationNo) 原子生成,逻辑不变。

4.5 级联删除(软删除)

主表 子表 方式
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 同上

4.6 收款银行默认回写

报销单首次提交审批通过后(Status 变为 approved),.NET 服务端将收款银行信息回写到 ERP 用户扩展属性。

4.7 外勤日志新点评红点

OutingLogComment.CreateTime > COALESCE(LastViewedTime, '1900-01-01')CommenterId != SalespersonId 时显示"新点评"标记。

4.8 CHECK 约束

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))

4.9 公告触达初始化

管理员发布公告(Statusdraftpublished)时,.NET 服务端根据 PrivateLevel 批量写入 AnnouncementReadLogIsRead=0)。草稿状态不触发。新员工入职不回溯历史公告。

4.10 外勤日志新客户动态创建

业务员输入不存在的客户名称时,提交时 .NET 服务端在同一事务内:1) 调 ERP 创建客户 → 获取新 ID;2) 回写 OutingLog.CustomerId

4.11 ERP 集成:预算/项目/科目/成本/标准

以下能力全部通过 .NET 服务端适配器层对接 ERP,OA 本地不存储:

能力 适配器 OA 调用的典型场景
预算余额查询 BudgetService 事前申请表单:选项目+科目后展示可用余额
预算冻结/扣减/释放 BudgetService 提交审批/通过/拒绝/撤回时自动处理
项目列表/级联 ProjectService 表单下拉:选项目→加载下级的预算科目
预算科目列表 SubjectService 同上
成本中心列表 CostCenterService 报销表单成本中心下拉
费用标准查询 StandardService 提交校验:住宿是否超 500/晚、高铁是否二等座
汇率表查询 ExchangeRateService 报销明细选外币→自动填汇率

ERP 无某能力时:该 Adapter 返回空/不限,OA 前端自动隐藏对应区块。例如 ERP 无预算 → 不展示余额、不校验超支。

4.12 费用标准管控

员工提交报销时,.NET 服务端按费用类型+城市级别+员工级别查询 ERP 费用标准,超标明细行标红 + 自动触发超标特批审批链。标准表在 ERP 维护,常见维度:

{ expenseType, cityLevel, employeeLevel, maxAmount, maxQuantity }
例:{ hotel, 一线城市, 普通员工, 500, 1 }

4.13 借款冲销

报销提交时 .NET 服务端自动查询该员工未还借款(Loan WHERE ApplicantId=@uid AND RepaymentStatus IN ('outstanding','partially_repaid') AND Status='approved'):

  • 报销金额 ≥ 借款余额 → 自动生成 LoanRepayment (expense_offset) 冲销全部借款,余额退还给员工
  • 报销金额 < 借款余额 → 冲销部分借款,剩余借款仍需归还,RepaymentStatus 保持 outstanding

5. 索引汇总

-- 事前申请列表
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);

6. 枚举取值

6.1 审批状态(OA 缓存 — ApprovalStatus)

含义 适用表
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

6.2 付款状态(PaymentStatus)

含义
unpaid 未付款
paying 付款中
paid 已付款

6.3 费用类型(ExpenseTypes — 逗号分隔,支持多选)

含义
travel 差旅费
entertainment 业务招待费
procurement 日常采购
activity 活动经费
office 办公费
meeting 会议费
training 培训费

例:"travel,office" 表示一次申请同时涵盖差旅费和办公费。

6.4 紧急程度(Urgency)

含义
normal 普通
urgent 紧急
critical 特急

6.5 交通工具(TransportType)

含义
plane 飞机
high_speed_rail 高铁/动车
train 火车(普速)
self_drive 自驾

6.6 招待对象层级(EntertainmentLevel)

含义
normal 普通人员
important 重要人员
vip VIP

6.7 申请使用状态(UsageStatus)

含义
unused 未被报销引用
partially_used 部分已报销
fully_used 已全部报销

6.8 发票类型(InvoiceType)

含义 InvoiceNo/InvoiceCode
special 增值税专用发票 必填
general 增值税普通发票 必填
none 无发票 允许 NULL

6.9 加班类型(OtType)

含义
workday 工作日加班
weekend 休息日加班
holiday 法定节假日加班

6.10 补偿方式(CompensationType)

含义
overtime_pay 全部结算加班费
comp_leave 全部转为调休
mixed 混合模式

6.11 用车目的(Purpose — Vehicle)

含义
reception 客户接待
business 商务出行
official 公务

6.12 车辆类型(VehicleType)

含义
sedan 轿车
suv SUV
mpv 商务车
van 面包车

6.13 车辆状态(SysVehicle.Status)

含义
idle 空闲可用
in_use 使用中
maintenance 维修中

6.14 公告分类(Announcement.Type)

含义
notice 通知公告
policy 人事与制度
activity 放假与活动

6.15 公告范围(PrivateLevel / TargetType)

PrivateLevel 含义 TargetType 含义
0 全员
1 按部门 dept TargetId → ERP 部门 ID
2 按用户 user TargetId → ERP 用户 ID

6.16 费用类别适用范围(BizScope)

含义
expense_apply 仅事前申请端
expense 仅费用报销端
both 两端通用

6.17 附件文件类型(Attachment.FileType)

含义 适用 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

6.18 对公/对私(PaymentType)

含义
personal 对私报销(还给员工)
corporate 对公付款(付给供应商)

6.19 借款类型(LoanType)

含义
imprest 备用金
travel_advance 差旅借款

6.20 还款状态(RepaymentStatus)

含义
outstanding 未还
partially_repaid 部分已还
fully_repaid 已还清
written_off 已核销

6.21 还款方式(RepaymentType)

含义
expense_offset 报销冲销
cash_return 现金还款
bank_transfer 银行转账

6.22 申请变更类型(ChangeType)

含义
amount_increase 追加金额
amount_decrease 减少金额
detail_add 新增明细行
detail_remove 删除明细行
date_extend 延期

6.23 BOOLEAN 语义

字段 0 的含义 1 的含义
IsDeleted 正常 已软删除
IsActive 停用/禁用 启用
IsRead 未读 已读
IsTop 普通 置顶
IsUrged 未催办 已催办
IsInvoiceVerified 未查验 已查验
IsTaxIdMatched 未匹配 已匹配
IsCategoryCompliant 不合规 合规

6.24 权限模块(OaPermission.Module)

对应权限点前缀
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.*

6.25 权限变更类型(OaPermissionChangeLog.ChangeType)

含义
assign 赋予权限
revoke 移除权限
toggle_active 启用/禁用

7. 常用查询 / 代码示例

以下示例基于 Dapper + ADO.NET(.NET Framework 4.8 服务端)。ERP 侧的数据(用户/部门/客户/审批)不在此处查询,均通过 API 获取。

7.1 报销单详情查询(OA 本地)

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} 获取

7.2 UsageStatus 联动更新

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);
}

7.3 单据编号原子生成

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}";
}
// 注意:以上逻辑适用于所有单据类型,替换表名和前缀即可

7.4 待审批列表(调 .NET API,非本地 SQL 查询)

审批数据由 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
}

7.5 OA 权限校验示例

// 判断用户是否有某个权限点
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