版本:v1.0 | 日期:2026-06-04 | 基于 PRD v1.0
| 项目 | 规格 |
|---|---|
| 数据库 | 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 |
软删除标记,0=正常,1=已删除。例外:OaPermissionChangeLog 审计日志不可删除 |
| 类型 | 精度 | 示例 |
|---|---|---|
| 金额 | DECIMAL(18,2) |
TotalAmount |
| 经纬度 | DECIMAL(10,6) |
CheckInLongitude |
| 税率 | DECIMAL(5,4) |
TaxRate |
| 工时 | DECIMAL(4,1) |
NetOtHours |
所有状态/类型字段使用 VARCHAR(20) 存储英文标识,前端国际化映射。完整取值见第 6 节。
格式 {前缀}-{YYYYMMDD}-{序号},序号按天重置高位补零至 3 位。.NET 服务端使用 sp_getapplock 原子生成。
| 业务 | 前缀 | 示例 |
|---|---|---|
| 事前申请 | 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 适配器(成本中心)
┌──────────────────────────────────────────────────────┐
│ OA 自管业务表 │
│ │
│ ExpenseApplication ──(1:N)── ExpenseAppDetail │
│ │ (明细行) │
│ │
│ ExpenseApplicationMapping ── 申请↔报销多对多 │
│ ├──(N:1)── Expense │
│ │ (费用明细, 含币种) │
│ │ └──(1:N)── ExpenseDetail │
│ └──(N:1)── ExpenseApplication │
│ │
│ 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多对多关联- 4 张附件表合并为 1 张 Attachment 聚合表
OA 独立权限系统的权限点定义表,存储所有可用权限编码及中文名称。权限点按模块分组,管理员通过快捷套餐或手动勾选为用户赋权。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| PermissionCode | VARCHAR(80) | ✅ | 权限点唯一编码,如 oa.expense.apply。UNIQUE 约束 |
| PermissionName | NVARCHAR(50) | ✅ | 权限点中文名称,如"发起报销",用于管理界面展示 |
| Module | VARCHAR(30) | ✅ | 所属功能模块,用于分组展示:expense / expense_apply / overtime / vehicle / outing_log / announcement / report / admin |
| SortOrder | INT | ✅ | 管理界面排序权重,值越小越靠前,默认 0 |
本表为字典表,不包含 CreateTime/UpdateTime/IsDeleted。权限点预置后一般不增删。
用户与权限点的多对多关联表。每个用户可拥有多个权限点,通过 IsDeleted 软删除实现权限的授予与回收。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| UserId | BIGINT | ✅ | ERP 用户 ID(非 FK 约束,关联 ERP 用户主数据) |
| PermissionId | BIGINT | ✅ | 权限点 ID,FK → OaPermission.Id |
| GrantedBy | BIGINT | ✅ | 授权操作人 ERP 用户 ID,用于审计追溯 |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE(),授权时间 |
| UpdateTime | DATETIME | 最后变更时间 | |
| IsDeleted | BIT | ✅ | DEFAULT 0。移除权限时设为 1(软删除,保留历史记录),重新授权时置回 0 |
索引:
UX_UserPermission UNIQUE (UserId, PermissionId) WHERE IsDeleted = 0 — 防止同一权限重复授权IX_UserPermission_UserId (UserId, IsDeleted) INCLUDE (PermissionId) — 按用户查权限列表记录管理员每次修改用户权限的完整快照。本表仅追加不修改(无 UpdateTime),不可软删除。用于变更追溯和合规审计。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| TargetUserId | BIGINT | ✅ | 被操作的目标用户 ERP 用户 ID |
| OperatorId | BIGINT | ✅ | 执行操作的管理员 ERP 用户 ID |
| ChangeType | VARCHAR(20) | ✅ | 变更类型:assign(赋权) / revoke(收权) / toggle_active(启停) |
| BeforeSnapshot | NVARCHAR(MAX) | ✅ | 变更前权限 JSON 快照(含权限列表 + IsActive 状态) |
| AfterSnapshot | NVARCHAR(MAX) | ✅ | 变更后权限 JSON 快照 |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE(),审计时间戳 |
本表无 UpdateTime 和 IsDeleted —— 审计日志不可修改、不可删除。
索引:IX_PermChangeLog_Target (TargetUserId, CreateTime DESC) INCLUDE (OperatorId, ChangeType)
员工在费用发生前提交的预估申请。支持多费用类型、关联项目与预算科目、多行预估明细。审批通过后可被多张报销单分批引用。
变更说明:ApplicantId、DeptId、ProjectId、BudgetSubjectId 为 ERP 对应实体 ID(非 FK)。ExpenseType → ExpenseTypes(逗号分隔多选)。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| ApplicationNo | VARCHAR(30) | ✅ | 单据唯一编号,格式 BXSQ-YYYYMMDD-XXX,提交时由 .NET 服务端原子生成 |
| ApplicantId | BIGINT | ✅ | 申请人 ERP 用户 ID |
| DeptId | BIGINT | ✅ | 申请人所属部门 ERP 部门 ID,提交时写入 |
| ApplicationDate | DATE | 业务申请日期(提交时写入当日,与 CreateTime 服务器时间区分) | |
| ProjectId | BIGINT | 关联项目,对应 ERP 项目主数据 ID。通过 .NET → ERP ProjectService 下拉选择,选定后联动查询该项目下的预算科目 | |
| BudgetSubjectId | BIGINT | 关联预算科目,对应 ERP 科目主数据 ID。通过 .NET → ERP SubjectService 加载。选定后联合 ProjectId 查询可用预算余额 | |
| EstimatedAmount | DECIMAL(18,2) | ✅ | 预估申请总金额 = 所有明细行 Quantity × UnitPrice 汇总,只读不手动修改,默认 0 |
| Urgency | VARCHAR(10) | ✅ | 紧急程度:normal(普通)/urgent(紧急)/critical(特急),默认 normal |
| ExpenseTypes | VARCHAR(200) | ✅ | 申请涵盖的费用大类,逗号分隔支持多选。如 "travel,office" 表示同时申请差旅费和办公费。选定的值会联动过滤明细行的 SysCostCategory 下拉选项(只展示绑定了该大类的费用小类)。值来自枚举 §6.3 |
| Purpose | NVARCHAR(500) | ✅ | 费用事由详细说明,限制 200 字 |
| Status | VARCHAR(20) | ✅ | 业务状态:draft(草稿)/pending(审批中)/approved(已通过)/rejected(已拒绝)/withdrawn(已撤回)。审批状态通过 .NET → ERP 实时查询 |
| UsageStatus | VARCHAR(20) | ✅ | 被报销引用的状态:unused(未引用)/partially_used(部分引用)/fully_used(已用完)。由报销单提交/删除时自动重算,默认 unused |
| IsTaxIncluded | BIT | ✅ | 预估金额是否含税:0=不含税,1=含税。影响后续报销核销时的金额对比 |
| ValidUntil | DATE | 申请有效期截止日。到期后定时 Job 自动释放冻结预算,UsageStatus 标记为 fully_used,不可再被报销引用 | |
| ReferenceNo | VARCHAR(50) | 关联的外部单号(合同号/询价单号),用于采购类申请追溯,非必填 | |
| ApprovalInstanceId | VARCHAR(50) | ERP 审批实例 ID,提交审批时由 .NET → ERP 创建后回写。撤回/驳回重新提交时覆盖为新值 | |
| PreviousInstanceIds | VARCHAR(MAX) | 历史审批实例 ID JSON 数组,撤回/驳回重新提交时追加旧 ApprovalInstanceId,用于追溯完整审批历史 | |
| — | — | — | 以下为费用类型专用字段(数据库全部允许 NULL,应用层按 ExpenseTypes 校验) |
| EstimatedStartDate | DATE | 预计行程开始日期,仅 ExpenseTypes 含 travel 或 meeting 时前端展示并必填 | |
| EstimatedEndDate | DATE | 预计行程结束日期,仅 ExpenseTypes 含 travel 或 meeting 时必填。校验:必须 ≥ StartDate | |
| IsOvernight | BIT | 是否过夜:1=需住宿,0=当天来回。仅 travel 时生效,决定住宿天数 | |
| TransportType | VARCHAR(20) | 交通工具:plane(飞机)/high_speed_rail(高铁)/train(普速火车)/self_drive(自驾)。仅 travel 时必填 | |
| EntertainmentTarget | NVARCHAR(200) | 招待对象单位名称,仅 ExpenseTypes 含 entertainment 时必填,用于税务合规 | |
| EntertainmentLevel | VARCHAR(20) | 招待对象层级:normal(普通)/important(重要)/vip(VIP)。影响人均消费标准 | |
| GuestCount | INT | 外部招待人数,仅 entertainment 时必填 | |
| CompanionCount | INT | 内部陪同人数,仅 entertainment 时必填。应用层校验:必须 ≤ GuestCount | |
| Venue | NVARCHAR(200) | 招待或会议举办地点,entertainment 或 meeting 时必填 | |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:
IX_ExpenseApp_List (ApplicantId, CreateTime DESC) INCLUDE (ApplicationNo, EstimatedAmount, Purpose, ExpenseTypes, UsageStatus)IX_ExpenseApp_Import (ApplicantId, UsageStatus) WHERE UsageStatus IN ('unused', 'partially_used') — 报销导入时快速过滤可用申请事前申请的逐行预估明细。每行包含费用类别、数量、单价,汇总金额写入主表 EstimatedAmount。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| ApplicationId | BIGINT | ✅ | 关联主表,FK → ExpenseApplication.Id(主表软删除时级联软删除) |
| ExpenseCategory | VARCHAR(20) | ✅ | 费用细分类别编码,值来源于 SysCostCategory.CategoryCode |
| Quantity | INT | ✅ | 数量,默认 1 |
| UnitPrice | DECIMAL(18,2) | ✅ | 单价 |
| Unit | VARCHAR(10) | 计量单位(张/间/人/天/套/个),辅助展示 | |
| EstimatedAmount | DECIMAL(18,2) | ✅ | 单项预估金额 = Quantity × UnitPrice,前端自动计算只读展示 |
| Remark | NVARCHAR(200) | 明细项说明备注 | |
| SortOrder | INT | ✅ | 排序号,默认 1,控制移动端渲染顺序 |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:IX_ExpenseAppDetail_AppId (ApplicationId, SortOrder) INCLUDE (ExpenseCategory, EstimatedAmount, Remark)
事前申请(支撑材料)、费用报销(发票影像)、外勤日志(现场照片)、公告(附件)四种业务附件合并为一张聚合表。通过 BizType 区分业务类型,DetailId 可绑定报销明细行(仅报销使用),SortOrder 用于外勤照片墙排序。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| BizType | VARCHAR(30) | ✅ | 业务类型:expense_apply(事前申请)/expense(费用报销)/outing_log(外勤日志)/announcement(公告) |
| BizId | BIGINT | ✅ | 父表 ID(多态,与 BizType 联合路由到具体业务表) |
| DetailId | BIGINT | 仅 BizType='expense' 使用,绑定 ExpenseDetail.Id,实现发票与明细行的关联 | |
| FileName | NVARCHAR(200) | ✅ | 原始文件名 |
| FileUrl | VARCHAR(500) | ✅ | 云端对象存储(OSS)绝对 URL |
| FileType | VARCHAR(20) | ✅ | 文件类型:image/pdf/doc/xls(通用)/ sign_in_photo(签到照)/ visit_photo(现场照)/ other(外勤专用) |
| FileSize | BIGINT | 文件字节数,用于上传大小校验 | |
| SortOrder | INT | 排序号,默认 0。外勤照片墙按此排序展示 | |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
应用层约束(前端 + .NET 校验):
索引:IX_Attachment_Biz (BizType, BizId, IsDeleted) INCLUDE (FileName, FileUrl, FileType, FileSize, DetailId, SortOrder)
一张报销单可导入多张事前申请,一张事前申请也可被多张报销单分批引用。每行记录一次导入关联,保存导入金额用于 UsageStatus 计算。应用层约束:单次导入的多张申请必须属于同一项目和同一预算科目(ProjectId + BudgetSubjectId 一致),以保证预算扣减的准确性。ImportedAmount 总和 ≤ 对应申请 EstimatedAmount - 已报总额。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| ExpenseId | BIGINT | ✅ | 报销单 ID,FK → Expense.Id |
| ApplicationId | BIGINT | ✅ | 事前申请 ID,FK → ExpenseApplication.Id |
| ImportedAmount | DECIMAL(18,2) | ✅ | 本次报销从该申请导入的金额。∑ImportedAmount 不能超过申请 EstimatedAmount |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
约束:UNIQUE (ExpenseId, ApplicationId) — 同一张报销单不能重复导入同一张申请
索引:IX_AppMapping_AppId (ApplicationId) INCLUDE (ExpenseId, ImportedAmount) — 查某申请的被引用情况
员工费用报销的核心主表。支持从事前申请拼单导入或直接新建,包含收款银行信息、财务核销标记。审批通过后由财务核销归档。
变更说明:删除 SourceApplicationId(改为 ExpenseApplicationMapping 多对多)。审批状态实时查询不缓存。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| ReportNo | VARCHAR(30) | ✅ | 单据唯一编号,格式 BX-YYYYMMDD-XXX,UNIQUE 约束 |
| ApplicantId | BIGINT | ✅ | 报销人 ERP 用户 ID |
| DeptId | BIGINT | ✅ | 报销人所属部门 ERP 部门 ID |
| ApplicationDate | DATE | 报销申请日期(提交时写入) | |
| CostCenterId | BIGINT | 成本中心 ERP ID,通过 .NET → ERP CostCenterService 下拉选择。可选,ERP 无数据时隐藏 | |
| ProjectId | BIGINT | 关联项目 ERP ID(直接新建时手动选,导入事前申请时自动带入) | |
| BudgetSubjectId | BIGINT | 关联预算科目 ERP ID | |
| TotalAmount | DECIMAL(18,2) | ✅ | 报销总金额 = 所有明细行 TotalAmount 汇总,只读不手动修改,默认 0 |
| Purpose | NVARCHAR(500) | ✅ | 报销事由说明 |
| BankName | NVARCHAR(100) | ✅ | 收款银行全称,前端支持下拉联想(数据源 .NET 字典 API),也可自由输入 |
| AccountName | NVARCHAR(50) | ✅ | 收款开户户名,默认填入当前用户姓名,可修改 |
| BankAccount | VARCHAR(50) | ✅ | 收款银行账号,前端校验 16-19 位数字格式 |
| IsInvoiceVerified | BIT | ✅ | 财务核销标记一:发票已在全国增值税发票查验平台验真,默认 0 |
| IsTaxIdMatched | BIT | ✅ | 财务核销标记二:发票抬头与公司税号一致,默认 0 |
| IsCategoryCompliant | BIT | ✅ | 财务核销标记三:报销类目与发票项目匹配合规,默认 0 |
| BankTransferNo | VARCHAR(50) | 财务核销时录入的银行电汇流水号 | |
| VoucherNo | VARCHAR(50) | 财务核销时录入的金蝶/财务系统记账凭证号 | |
| PaymentStatus | VARCHAR(20) | ✅ | 付款状态:unpaid(待付款)/paid(已付款)。仅 Status=approved 时允许流转,默认 unpaid |
| Status | VARCHAR(20) | ✅ | 业务状态:draft/pending/approved/rejected/withdrawn。审批状态通过 .NET → ERP 实时查询 |
| ApprovalInstanceId | VARCHAR(50) | ERP 审批实例 ID,提交审批时由 .NET → ERP 创建后回写 | |
| PreviousInstanceIds | VARCHAR(MAX) | 历史审批实例 ID JSON 数组 | |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:IX_Expense_List (ApplicantId, CreateTime DESC) INCLUDE (TotalAmount, ReportNo, PaymentStatus)
报销单的逐行费用明细。每行对应一张发票或一笔无发票费用。支持多币种(原币+汇率+本币),币种从 ERP 汇率表获取。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| ExpenseId | BIGINT | ✅ | 关联报销主表,FK → Expense.Id(主表软删除时级联软删除) |
| ExpenseDate | DATE | ✅ | 费用实际发生日期 |
| ExpenseType | VARCHAR(20) | ✅ | 费用细分类别编码,值来源于 SysCostCategory.CategoryCode 叶子节点。前端下拉选项受主表 ExpenseTypes(大类)和 SysCostCategory.ExpenseType 绑定关系联动过滤 |
| ExpenseDesc | NVARCHAR(200) | ✅ | 费用摘要描述 |
| Amount | DECIMAL(18,2) | ✅ | 原币不含税金额。多币种时为原币金额 |
| TaxAmount | DECIMAL(18,2) | ✅ | 进项税额,无发票时为 0,默认 0 |
| TotalAmount | DECIMAL(18,2) | ✅ | 原币价税合计 = Amount + TaxAmount |
| CurrencyCode | VARCHAR(10) | 原币币种代码,默认 CNY。选外币(如 USD/EUR/JPY)时从 .NET → ERP ExchangeRateService 获取当日汇率自动填入 ExchangeRate | |
| ExchangeRate | DECIMAL(10,4) | 原币→本币汇率,CNY 时为 1.0000。由 .NET 服务端从 ERP 汇率表查询后填入 | |
| BaseAmount | DECIMAL(18,2) | 折算后的本币金额 = TotalAmount × ExchangeRate。预算校验和报表统计均以本币为准 | |
| InvoiceNo | VARCHAR(50) | 发票号码,InvoiceType=none 时允许 NULL | |
| InvoiceCode | VARCHAR(50) | 发票代码 | |
| InvoiceType | VARCHAR(20) | ✅ | 发票类型:special(专票)/general(普票)/none(无发票)。无发票场景(单笔 ≤200 元小额零星)时选 none |
| TaxRate | DECIMAL(5,4) | 税率,如 0.0600 / 0.0900 / 0.1300 | |
| SortOrder | INT | ✅ | 排序号,默认 1 |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:IX_ExpenseDetail_ExpenseId (ExpenseId, SortOrder) INCLUDE (ExpenseDate, ExpenseType, ExpenseDesc, Amount, TotalAmount, InvoiceType)
员工加班申请,支持三种加班类型和三种补偿方式。后端自动扣除午休/晚餐盲区计算净工时。审批通过后可用于调休或加班费结算。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| ApplicationNo | VARCHAR(30) | ✅ | 单据唯一编号,格式 JB-YYYYMMDD-XXX,UNIQUE 约束 |
| 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.10~0.90,仅 CompensationType=mixed 时生效。如 0.30=30%调休+70%加班费 | |
| StartTime | DATETIME | 加班开始时间,提交时必填。校验:必须早于 EndTime | |
| EndTime | DATETIME | 加班结束时间,提交时必填 | |
| NetOtHours | DECIMAL(4,1) | ✅ | 实际净工时。由 .NET 服务端自动扣除午餐(12:00-13:00)和晚餐(18:00-18:30)盲区后计算,前端只读展示。≤0 时提交按钮置灰 |
| Reason | NVARCHAR(500) | ✅ | 加班原因说明 |
| Status | VARCHAR(20) | ✅ | 业务状态:draft/pending/approved/rejected/withdrawn。审批状态通过 .NET → ERP 实时查询 |
| ApprovalInstanceId | VARCHAR(50) | ERP 审批实例 ID | |
| PreviousInstanceIds | VARCHAR(MAX) | 历史审批实例 ID JSON 数组 | |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:IX_Overtime_List (ApplicantId, CreateTime DESC) INCLUDE (ApplicationNo, OtType, NetOtHours, CompensationType)
覆盖公车申请、审批、还车登记全生命周期。车辆池 OA 自管(SysVehicle),排期冲突检测、里程表记录和实际费用报账均在 OA 完成。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| ApplicationNo | VARCHAR(30) | ✅ | 单据唯一编号,格式 YC-YYYYMMDD-XXX,UNIQUE 约束 |
| ApplicantId | BIGINT | ✅ | 申请人 ERP 用户 ID |
| DeptId | BIGINT | ✅ | 所属部门 ERP 部门 ID |
| VehicleId | BIGINT | 申请调配的公车 ID,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 | 同行总人数,默认 1 | |
| StartTime | DATETIME | 预计出车时间,提交时必填 | |
| EndTime | DATETIME | 预计还车时间,提交时必填。校验:必须晚于 StartTime | |
| — | — | — | 以下为还车登记字段(审批通过后填写) |
| ActualReturnTime | DATETIME | 还车登记:实际归还时间 | |
| StartOdometer | DECIMAL(10,2) | 还车登记:出车前里程表读数 | |
| EndOdometer | DECIMAL(10,2) | 还车登记:还车后里程表读数。校验:必须 > StartOdometer | |
| ActualCost | DECIMAL(18,2) | 还车登记:路桥费/停车费等实际费用总额 | |
| CostRemark | NVARCHAR(500) | 还车登记:费用明细备注 | |
| Status | VARCHAR(20) | ✅ | 业务状态:draft/pending/approved/rejected/withdrawn/returned(已还车)。审批状态通过 .NET → ERP 实时查询 |
| ApprovalInstanceId | VARCHAR(50) | ERP 审批实例 ID | |
| PreviousInstanceIds | VARCHAR(MAX) | 历史审批实例 ID JSON 数组 | |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:
IX_Vehicle_List (ApplicantId, CreateTime DESC) INCLUDE (ApplicationNo, VehicleId, Purpose, Origin, Destination, StartTime)IX_Vehicle_Collision (VehicleId, StartTime, EndTime) WHERE Status IN ('pending', 'approved') — 排期冲突检测| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| ApplicationId | BIGINT | ✅ | 关联用车单主表,FK → Vehicle.Id |
| UserId | BIGINT | 内部同事 ERP 用户 ID(可空,添加外部人员时为空) | |
| PassengerName | NVARCHAR(50) | ✅ | 同行人姓名文本(ERP 用户取 RealName,外部人员自由输入) |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:IX_VehiclePassenger_AppId (ApplicationId, IsDeleted) INCLUDE (UserId, PassengerName)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| LicensePlate | NVARCHAR(20) | ✅ | 车牌号(含中文字符如粤B12345),UNIQUE 约束 |
| VehicleType | VARCHAR(20) | ✅ | 车辆类型:sedan(轿车)/suv(SUV)/mpv(商务车)/van(面包车) |
| Brand | NVARCHAR(50) | 品牌型号 | |
| Seats | INT | 核定座位数 | |
| DriverName | NVARCHAR(50) | 默认驾驶员姓名 | |
| Status | VARCHAR(20) | ✅ | 车辆状态:idle(空闲可用)/in_use(使用中)/maintenance(维修中)。审批通过自动变更为 in_use,还车后恢复 idle |
| IsActive | BIT | ✅ | 启用/停用标记,默认 1。停用的车辆不可被申请 |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
业务员外出拜访客户的工作记录。GPS 定位只读防篡改,照片强制拍摄并加水印。不走审批流,提交即完成。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| VisitNo | VARCHAR(30) | ✅ | 单据唯一编号,格式 VST-YYYYMMDD-XXX,UNIQUE 约束 |
| SalespersonId | BIGINT | ✅ | 业务员 ERP 用户 ID |
| DeptId | BIGINT | ✅ | 所属部门 ERP 部门 ID |
| CustomerId | BIGINT | 拜访客户 ERP 客户 ID(非 FK)。输入新客户名时,提交后由 .NET → ERP CustomerService 创建新客户并回填此字段 | |
| CustomerName | NVARCHAR(200) | ✅ | 客户公司全称冗余字段,前端输入时调 ERP 联想匹配,也支持自由输入 |
| ContactId | BIGINT | 客户联系人 ERP ID | |
| CheckInLongitude | DECIMAL(10,6) | GPS 硬件定位经度,提交时必填。前端设为只读,不可手动修改 | |
| CheckInLatitude | DECIMAL(10,6) | GPS 硬件定位纬度 | |
| CheckInAddress | NVARCHAR(500) | 逆地理编码出的街道地址,前端设为只读,不可手动修改 | |
| VisitSummary | NVARCHAR(2000) | ✅ | 今日工作核心总结 |
| NextPlan | NVARCHAR(500) | 后续推进计划 | |
| Status | VARCHAR(20) | ✅ | 状态:draft(草稿)/completed(已提交)。不走审批流,提交即完成 |
| LastViewedTime | DATETIME | 员工最后一次查看详情页的时间,用于判断是否有新点评(红点逻辑) | |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE(),服务器授时(用于防伪水印) |
| UpdateTime | 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 | ✅ | 主键,IDENTITY(1,1) |
| 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 | ✅ | DEFAULT GETDATE() |
| UpdateTime | DATETIME | ||
| IsDeleted | BIT | ✅ | DEFAULT 0 |
索引:IX_OutingLogComment_LogId (LogId, IsDeleted) INCLUDE (CommenterId, RatingStars, CommentText, CreateTime)
管理员发布的公司公告。支持按全员/部门/指定用户三种范围发布,可置顶、设有效期。管理员可查看已读/未读名单并 DING 催办。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| Title | NVARCHAR(200) | ✅ | 公告标题 |
| Content | NVARCHAR(MAX) | ✅ | 公告正文,支持 HTML/Markdown 格式 |
| Type | VARCHAR(20) | ✅ | 公告分类:notice(通知公告)/policy(人事与制度)/activity(放假与活动) |
| Status | VARCHAR(20) | ✅ | 发布状态:draft(草稿,仅创建者和管理员可见)/published(已发布,全员可见) |
| PublisherId | BIGINT | ✅ | 发布管理员 ERP 用户 ID |
| PublishTime | DATETIME | 实际发布时间,草稿时为 NULL,发布时写入 GETDATE() | |
| IsTop | BIT | ✅ | 是否全局置顶:1=置顶优先展示,0=正常排序,默认 0 |
| PrivateLevel | INT | ✅ | 可见范围级别:0=全员,1=按部门,2=按指定用户。默认 0 |
| ExpiryDate | DATETIME | 自动下架失效时间,不填则永不过期。过期后列表置灰标注"已过期" | |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
| 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'
当公告按部门或按指定用户发布时,记录目标范围。多态外键设计(SQL Server 不支持多态 FK,由应用层校验引用完整性)。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| AnnouncementId | BIGINT | ✅ | 关联公告主表,FK → Announcement.Id |
| TargetType | VARCHAR(10) | ✅ | 目标实体类型:dept(按部门)/user(按指定个人) |
| TargetId | BIGINT | ✅ | 多态外键:TargetType=dept 时指向 ERP 部门 ID,TargetType=user 时指向 ERP 用户 ID |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
| UpdateTime | DATETIME |
记录每个员工对每条公告的已读/未读状态。发布时根据 PrivateLevel 批量初始化 IsRead=0 记录,停留 ≥2 秒标记已读。管理员通过此表查看触达率并 DING 催办。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| AnnouncementId | BIGINT | ✅ | 关联公告,FK → Announcement.Id |
| UserId | BIGINT | ✅ | 被触达员工 ERP 用户 ID |
| IsRead | BIT | ✅ | 已读标记:0=未读,1=已读。停留 ≥2 秒自动标记,默认 0 |
| ReadTime | DATETIME | 员工实际阅读时间 | |
| IsUrged | BIT | ✅ | 是否已被管理员 DING 催办过,默认 0 |
| LastUrgeTime | DATETIME | 最后一次 DING 催办时间 | |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
| UpdateTime | DATETIME |
约束:UNIQUE (AnnouncementId, UserId) — 每个员工对每条公告仅有一条触达记录
索引:IX_Announcement_Ding (AnnouncementId, IsRead, IsUrged) INCLUDE (UserId, ReadTime)
费用报销和事前申请表单中"费用类别"下拉的数据源,支持二级分类。通过 BizScope 控制适用范围(事前/报销/通用),ExpenseType 可绑定费用大类以进一步过滤。与 ExpenseApplication.ExpenseTypes 的关系:后者是大类(如 travel),选定后联动过滤此表中 ExpenseType 匹配的叶子节点作为下拉选项。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Id | BIGINT | ✅ | 主键,IDENTITY(1,1) |
| CategoryName | NVARCHAR(50) | ✅ | 类别中文名称,如"交通费" |
| CategoryCode | VARCHAR(30) | ✅ | 类别编码,UNIQUE 约束 |
| ParentId | BIGINT | 上级类别 ID,FK → SysCostCategory.Id(自引用)。NULL 为一级分类,非 NULL 为叶子节点。仅叶子节点可用于明细录入 | |
| BizScope | VARCHAR(20) | ✅ | 适用业务范围:expense_apply(仅事前申请)/expense(仅报销)/both(通用),默认 both |
| ExpenseType | VARCHAR(20) | 绑定费用大类(如 travel/office),进一步细化过滤。NULL 表示通用,不限定大类 | |
| SortOrder | INT | ✅ | 排序权重,默认 0 |
| IsActive | BIT | ✅ | 启用/停用标记:1=启用,0=停用 |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
| 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 | ✅ | 主键,IDENTITY(1,1) |
| ImageUrl | VARCHAR(500) | ✅ | 轮播图片云端存储绝对 URL |
| Title | NVARCHAR(100) | 图片标题/alt 文本 | |
| LinkUrl | VARCHAR(500) | 点击跳转链接。为空时不可点击,仅全屏预览图片 | |
| SortOrder | INT | ✅ | 排序权重,值越小越靠前,默认 0 |
| IsActive | BIT | ✅ | 启用/停用标记:1=启用,0=停用 |
| CreateTime | DATETIME | ✅ | DEFAULT GETDATE() |
| 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 本地存储。审批状态、审批时间线、待办列表均通过 .NET → ERP 实时查询。业务表仅存两个字段用于关联和追溯:
| 字段 | 作用 | 更新时机 |
|---|---|---|
ApprovalInstanceId |
关联 ERP 审批实例 | 提交审批时写入;撤回/驳回重新提交时覆盖 |
PreviousInstanceIds |
追溯历史审批 | 撤回/驳回重新提交时追加旧 instanceId |
状态同步流程:
列表页 → 实时调 .NET → ERP 查询审批状态
详情页 → 实时调 .NET → ERP 查询审批状态和审批时间线
审批操作 → OA 调 .NET → ERP 执行审批动作
消息通知 → .NET 服务端消息模块在 ERP 审批事件后触发推送
UsageStatus 由 .NET 服务端在报销单状态变更时自动重算:
| 已报总额 | UsageStatus |
|---|---|
| SUM(ExpenseApplicationMapping.ImportedAmount) = 0 | unused |
| > 0 且 < EstimatedAmount | partially_used |
| ≥ EstimatedAmount | fully_used |
| 主表 | 子表 |
|---|---|
| ExpenseApplication | ExpenseAppDetail, Attachment (BizType='expense_apply') |
| Expense | ExpenseDetail, ExpenseApplicationMapping, Attachment (BizType='expense') |
| OutingLog | OutingLogComment, Attachment (BizType='outing_log') |
| Announcement | AnnouncementTarget, AnnouncementReadLog, Attachment (BizType='announcement') |
.NET 服务端使用 sp_getapplock + SELECT MAX(ApplicationNo) 原子生成,保证高并发下按天不重复。
-- 事前申请列表
CREATE NONCLUSTERED INDEX IX_ExpenseApp_List
ON ExpenseApplication (ApplicantId, CreateTime DESC)
INCLUDE (ApplicationNo, EstimatedAmount, Purpose, ExpenseTypes, UsageStatus);
-- 事前申请一键导入(仅已通过且尚未用完的)
CREATE NONCLUSTERED INDEX IX_ExpenseApp_Import
ON ExpenseApplication (ApplicantId, UsageStatus)
WHERE UsageStatus IN ('unused', 'partially_used');
-- 费用报销列表
CREATE NONCLUSTERED INDEX IX_Expense_List
ON Expense (ApplicantId, CreateTime DESC)
INCLUDE (TotalAmount, ReportNo, PaymentStatus);
-- 申请↔报销多对多
CREATE NONCLUSTERED INDEX IX_AppMapping_AppId
ON ExpenseApplicationMapping (ApplicationId)
INCLUDE (ExpenseId, ImportedAmount);
-- 加班列表
CREATE NONCLUSTERED INDEX IX_Overtime_List
ON Overtime (ApplicantId, CreateTime DESC)
INCLUDE (ApplicationNo, OtType, NetOtHours, CompensationType);
-- 用车列表 + 排期冲突检测
CREATE NONCLUSTERED INDEX IX_Vehicle_List
ON Vehicle (ApplicantId, 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);
-- 统一附件
CREATE NONCLUSTERED INDEX IX_Attachment_Biz
ON Attachment (BizType, BizId, IsDeleted)
INCLUDE (FileName, FileUrl, FileType, FileSize, DetailId, SortOrder);
-- OA 权限
CREATE UNIQUE NONCLUSTERED INDEX UX_UserPermission
ON OaUserPermission (UserId, PermissionId) WHERE IsDeleted = 0;
CREATE NONCLUSTERED INDEX IX_UserPermission_UserId
ON OaUserPermission (UserId, IsDeleted) INCLUDE (PermissionId);
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_CostCategory_Scope
ON SysCostCategory (BizScope, ExpenseType, ParentId, SortOrder)
INCLUDE (CategoryName, CategoryCode) WHERE IsActive = 1 AND IsDeleted = 0;
CREATE NONCLUSTERED INDEX IX_Banner_Active
ON SysBanner (IsActive, SortOrder)
INCLUDE (ImageUrl, Title, LinkUrl) WHERE IsDeleted = 0;
-- 外勤点评 + 用车同行人 + 报销明细 + 事前明细
CREATE NONCLUSTERED INDEX IX_OutingLogComment_LogId
ON OutingLogComment (LogId, IsDeleted)
INCLUDE (CommenterId, RatingStars, CommentText, CreateTime);
CREATE NONCLUSTERED INDEX IX_VehiclePassenger_AppId
ON VehiclePassenger (ApplicationId, IsDeleted)
INCLUDE (UserId, PassengerName);
CREATE NONCLUSTERED INDEX IX_ExpenseDetail_ExpenseId
ON ExpenseDetail (ExpenseId, SortOrder)
INCLUDE (ExpenseDate, ExpenseType, ExpenseDesc, Amount, TotalAmount, InvoiceType);
CREATE NONCLUSTERED INDEX IX_ExpenseAppDetail_AppId
ON ExpenseAppDetail (ApplicationId, SortOrder)
INCLUDE (ExpenseCategory, EstimatedAmount, Remark);
审批状态(draft/pending/approved/rejected/withdrawn)仅 draft 由 OA 本地直接写入,其余状态由 ERP 审批引擎管理,OA 通过 .NET → ERP 实时查询。
| 值 | 含义 | 适用表 | 写入方 |
|---|---|---|---|
draft |
草稿 | 全部业务表 | OA 本地 |
pending |
待审批 | ExpenseApplication, Expense, Overtime, Vehicle | OA 写入后 ERP 接管 |
approved |
已通过 | 同上 | ERP 管理 |
rejected |
已拒绝 | 同上 | ERP 管理 |
withdrawn |
已撤回 | 同上 | OA 本地 |
returned |
已还车 | Vehicle(独有) | OA 本地 |
completed |
已提交 | OutingLog(不走审批流) | OA 本地 |
published |
已发布 | Announcement(不走审批流) | OA 本地 |
| 值 | 含义 |
|---|---|
unpaid |
待付款 |
paid |
已付款 |
| 值 | 含义 |
|---|---|
travel |
差旅费(含交通/住宿/补助) |
entertainment |
业务招待费(宴请/礼品) |
procurement |
日常采购(含报价单/合同) |
activity |
活动经费(团建/庆典) |
office |
办公费(文具/耗材/快递) |
meeting |
会议费(内/外部会议) |
training |
培训费(外部培训/认证) |
| 值 | 含义 |
|---|---|
normal |
普通 |
urgent |
紧急 |
critical |
特急 |
| 值 | 含义 |
|---|---|
plane |
飞机 |
high_speed_rail |
高铁/动车 |
train |
火车(普速) |
self_drive |
自驾 |
| 值 | 含义 |
|---|---|
normal |
普通人员 |
important |
重要人员 |
vip |
VIP |
| 值 | 含义 |
|---|---|
unused |
未被报销引用 |
partially_used |
部分金额已报销 |
fully_used |
全部金额已报销,不可再被引用 |
| 值 | 含义 | InvoiceNo/InvoiceCode |
|---|---|---|
special |
增值税专用发票 | 必填 |
general |
增值税普通发票 | 必填 |
none |
无发票(小额零星 ≤200 元) | 允许 NULL |
| 值 | 含义 |
|---|---|
workday |
工作日加班 |
weekend |
休息日加班 |
holiday |
法定节假日加班 |
| 值 | 含义 |
|---|---|
overtime_pay |
全部结算加班费 |
comp_leave |
全部转为调休 |
mixed |
混合模式(按 CompLeaveRatio 分配) |
| 值 | 含义 |
|---|---|
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 |
| 字段 | 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 服务端)。
// 主表 + 关联的申请
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.PreviousInstanceIds
FROM Expense e
WHERE e.Id = @expenseId AND e.IsDeleted = 0;
-- 关联的申请
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 ed.Id, ed.ExpenseDate, ed.ExpenseType, ed.ExpenseDesc,
ed.Amount, ed.TaxAmount, ed.TotalAmount,
ed.CurrencyCode, ed.ExchangeRate, ed.BaseAmount,
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 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> GenerateNoAsync(
IDbConnection conn, IDbTransaction tx, string prefix, string tableName, 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);
var sql = $@"
SELECT MAX(ApplicationNo)
FROM {tableName} WITH (UPDLOCK, ROWLOCK)
WHERE ApplicationNo LIKE @pattern";
var maxNo = await conn.QuerySingleOrDefaultAsync<string>(sql, new { pattern }, tx);
int seq = 1;
if (maxNo != null)
seq = int.Parse(maxNo.Substring(maxNo.Length - 3)) + 1;
return $"{prefix}-{dateStr}-{seq:D3}";
}
文档版本:v1.0 | 日期:2026-06-04