# TBOSS OA 模块 — API 接口文档 > 版本:v1.0 | 日期:2026-06-04 | Base URL:`https://{host}/api` --- ## 1. 通用规范 ### 1.1 请求头 ``` Content-Type: application/json Authorization: Bearer {token} ← 由宿主 App 注入 X-User-Id: {erpUserId} ← 当前登录用户 ERP ID ``` ### 1.2 统一响应格式 ```json { "code": 0, "message": "success", "data": { ... }, "timestamp": 1717401600 } ``` ### 1.3 分页响应 ```json { "code": 0, "data": { "items": [ ... ], "total": 128, "page": 1, "pageSize": 20 } } ``` ### 1.4 错误码 | code | 含义 | 处理方式 | |------|------|---------| | 0 | 成功 | — | | 400 | 参数校验失败 | Toast 提示具体字段错误 | | 401 | 未授权 / Token 过期 | 触发宿主重新登录 | | 403 | 无 OA 操作权限 | Toast "无此操作权限" | | 404 | 资源不存在 | TDEmpty 或 Toast | | 409 | 并发冲突 (CONCURRENCY_CONFLICT / APPROVAL_CONFLICT) | TDDialog 提示刷新 | | 422 | 业务校验失败 | 展示具体错误信息 | | 500 | 服务器内部错误 | Toast "服务器繁忙,请稍后重试" | | 504 | ERP 审批接口超时 | Toast "审批服务暂不可用,请稍后重试" | ### 1.5 枚举值 所有状态/类型字段使用英文标识字符串,前端负责国际化映射。完整枚举取值见 `tboss-oa-database.md` 第 6 节。 --- ## 2. OA 权限 API ### 2.1 获取权限点列表 ``` GET /api/oa/permissions ``` **响应**: ```json { "code": 0, "data": { "permissions": [ { "id": 1, "code": "oa.expense.apply", "name": "发起报销", "module": "expense", "sortOrder": 1 }, { "id": 2, "code": "oa.expense.view_own", "name": "查看自己的报销单", "module": "expense", "sortOrder": 2 } ] } } ``` ### 2.2 获取用户权限 ``` GET /api/oa/user-permissions?userId={erpUserId} ``` **响应**: ```json { "code": 0, "data": { "userId": 1001, "permissions": ["oa.expense.apply", "oa.expense.view_own", "oa.announcement.view"], "presetRoles": ["employee"] } } ``` ### 2.3 保存用户权限 ``` PUT /api/oa/user-permissions ``` **请求**: ```json { "userId": 1001, "permissionCodes": [ "oa.expense.apply", "oa.expense.view_dept", "oa.expense.approve", "oa.outing_log.comment" ] } ``` **响应**: ```json { "code": 0, "message": "权限已更新" } ``` **错误**: | code | 场景 | |------|------| | 403 | 无 `oa.admin.permissions` 权限 | | 422 | 试图移除自己的管理员权限 / 移除最后一名管理员 | ### 2.4 权限变更日志 ``` GET /api/oa/permission-changelog?userId={erpUserId}&page=1&pageSize=20 ``` **响应**: ```json { "code": 0, "data": { "items": [ { "id": 1, "targetUserId": 1001, "operatorId": 2001, "operatorName": "管理员王五", "changeType": "assign", "beforePermissions": ["oa.expense.apply", "oa.expense.view_own"], "afterPermissions": ["oa.expense.apply", "oa.expense.view_own", "oa.expense.approve"], "createTime": "2026-06-03T10:30:00" } ], "total": 5, "page": 1 } } ``` --- ## 3. 业务单据 API ### 3.1 通用说明 `{bizType}` 取值:`expense-apply` | `expense` | `overtime` | `vehicle` ### 3.2 提交单据(新建 + 发起审批) ``` POST /api/oa/{bizType}/submit ``` #### 事前申请 **请求**: ```json { "id": null, "urgency": "normal", "expenseTypes": "travel,office", "purpose": "北京客户拜访差旅费预估", "projectId": 100, "budgetSubjectId": 5, "isTaxIncluded": false, "validUntil": "2026-07-03", "referenceNo": "HT-2026-0089", "applicationDate": "2026-06-03", "estimatedStartDate": "2026-06-10", "estimatedEndDate": "2026-06-12", "isOvernight": true, "transportType": "high_speed_rail", "entertainmentTarget": null, "entertainmentLevel": null, "guestCount": null, "companionCount": null, "venue": null, "details": [ { "expenseCategory": "transport", "quantity": 2, "unitPrice": 550.00, "unit": "张", "remark": "往返高铁票", "sortOrder": 1 }, { "expenseCategory": "hotel", "quantity": 2, "unitPrice": 400.00, "unit": "晚", "remark": "住宿", "sortOrder": 2 } ], "attachments": [ { "fileName": "出差审批截图.png", "fileUrl": "https://oss/...", "fileType": "image", "fileSize": 204800 } ] } ``` > **费用类型联动字段**:仅当 `expenseTypes` 含对应类型时前端展示并必填。`travel` 时需 `estimatedStartDate/EndDate/isOvernight/transportType`;`entertainment` 时需 `entertainmentTarget/Level/guestCount/companionCount/venue`(校验 `companionCount ≤ guestCount`);`meeting` 时需 `estimatedStartDate/EndDate/venue`。数据库全部 NULL 允许,应用层按 expenseTypes 动态校验。 **响应**: ```json { "code": 0, "data": { "id": 12345, "applicationNo": "BXSQ-20260603-001", "approvalInstanceId": "inst_erp_a_009832", "previousInstanceIds": [] } } ``` #### 费用报销 **请求**: ```json { "id": null, "applicationMappings": [ { "applicationId": 12300, "importedAmount": 5500.00 } ], "purpose": "北京客户拜访差旅费报销", "projectId": 100, "budgetSubjectId": 5, "costCenterId": 10, "applicationDate": "2026-06-03", "bankName": "中国工商银行", "accountName": "张三", "bankAccount": "6222021234567890123", "details": [ { "expenseDate": "2026-06-10", "expenseType": "transport", "expenseDesc": "G123 北京南-上海虹桥", "amount": 523.58, "taxAmount": 26.42, "currencyCode": "CNY", "exchangeRate": 1.0000, "invoiceNo": "12345678", "invoiceCode": "011001900111", "invoiceType": "special", "taxRate": 0.0900, "sortOrder": 1 } ], "attachments": [ { "fileName": "高铁票.jpg", "fileUrl": "https://oss/...", "fileType": "image", "fileSize": 307200, "detailIndex": 0 } ] } ``` > **导入申请**:`applicationMappings` 非必填,传入时项目/科目/成本中心自动从申请带入。直接新建时不传,需手动填写 `projectId`/`budgetSubjectId`。`costCenterId` 可选(ERP 无数据时不传)。`currencyCode` 默认 CNY,选外币时 `exchangeRate` 由服务端从 ERP 查询后填入,前端只传 `currencyCode`。`totalAmount`(价税合计)= amount + taxAmount 由服务端计算。`baseAmount` = totalAmount × exchangeRate 由服务端计算。 **响应**: ```json { "code": 0, "data": { "id": 12348, "reportNo": "BX-20260603-001", "approvalInstanceId": "inst_erp_a_009834", "previousInstanceIds": [] } } ``` #### 加班申请 **请求**: ```json { "id": null, "otType": "workday", "compensationType": "overtime_pay", "compLeaveRatio": null, "startTime": "2026-06-03T18:00:00", "endTime": "2026-06-03T22:00:00", "reason": "紧急上线支持,需加班完成部署" } ``` > **混合模式**:`compensationType=mixed` 时 `compLeaveRatio` 必填,范围 0.10~0.90(步长 0.10),如 0.30 表示 30% 调休 + 70% 加班费。`NetOtHours` 由服务端自动扣除午休(12:00-13:00)和晚餐(18:00-18:30)盲区后计算。`applicationDate` 由服务端填写提交当日。 **响应**: ```json { "code": 0, "data": { "id": 12346, "applicationNo": "JB-20260603-001", "netOtHours": 3.5, "approvalInstanceId": "inst_erp_a_009833", "previousInstanceIds": [] } } ``` #### 用车申请 **请求**: ```json { "id": null, "vehicleId": 3, "purpose": "business", "reason": "前往客户现场演示产品", "origin": "公司总部", "originLongitude": 121.473701, "originLatitude": 31.230416, "destination": "客户公司", "destLongitude": 121.510000, "destLatitude": 31.250000, "startTime": "2026-06-05T09:00:00", "endTime": "2026-06-05T17:00:00", "passengerCount": 3, "passengers": [ { "userId": 1002, "passengerName": "李四" }, { "userId": null, "passengerName": "客户代表" } ] } ``` > **排期冲突检测**:提交前应调 `GET /api/oa/vehicle/check-collision` 异步校验。冲突时前端锁死提交按钮。`applicationDate` 由服务端填写提交当日。 **响应**:同其他单据。 ### 3.3 存草稿 ``` PUT /api/oa/{bizType}/draft ``` 请求体与提交相同,`id` 为已有草稿 ID(编辑已有草稿)或 null(新建草稿)。不触发审批。 **响应**: ```json { "code": 0, "data": { "id": 12347 } } ``` ### 3.4 列表查询 ``` GET /api/oa/expense-apply/list?status=pending&page=1&pageSize=20 GET /api/oa/expense/list?status=all&page=1 GET /api/oa/overtime/list?status=approved&page=1 GET /api/oa/vehicle/list?status=all&page=1 ``` **参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | status | string | 否 | 审批单据:all / draft / pending / approved / rejected / withdrawn。报销额外:unpaid / paid。用车额外:returned | | page | int | 否 | 默认 1 | | pageSize | int | 否 | 默认 20 | | scope | string | 否 | own(默认) / dept / all — 根据权限自动限定 | **响应**(以报销列表为例): ```json { "code": 0, "data": { "items": [ { "id": 100, "reportNo": "BX-20260603-001", "totalAmount": 1580.00, "purpose": "差旅费报销", "applicationDate": "2026-06-03", "status": "approved", "paymentStatus": "unpaid", "applicantName": "张三", "deptName": "销售部", "createTime": "2026-06-03T10:00:00" } ], "total": 42, "page": 1, "pageSize": 20 } } ``` **事前申请列表响应**(增量字段): ```json { "id": 200, "applicationNo": "BXSQ-20260603-001", "estimatedAmount": 1900.00, "purpose": "差旅费预估", "expenseTypes": "travel,office", "applicationDate": "2026-06-03", "status": "pending", "usageStatus": "unused", "applicantName": "张三", "deptName": "销售部", "createTime": "2026-06-03T09:00:00" } ``` **加班列表响应**(增量字段): ```json { "id": 300, "applicationNo": "JB-20260603-001", "otType": "workday", "netOtHours": 3.5, "compensationType": "overtime_pay", "compLeaveRatio": null, "reason": "紧急上线支持", "applicationDate": "2026-06-03", "status": "approved", "applicantName": "张三", "deptName": "销售部", "createTime": "2026-06-03T18:30:00" } ``` **用车列表响应**(增量字段): ```json { "id": 400, "applicationNo": "YC-20260603-001", "vehicleId": 3, "licensePlate": "粤B12345", "purpose": "business", "origin": "公司总部", "destination": "客户公司", "startTime": "2026-06-05T09:00:00", "status": "approved", "applicantName": "张三", "deptName": "销售部", "createTime": "2026-06-03T11:00:00" } ``` ### 3.5 详情查询 ``` GET /api/oa/expense-apply/detail/12345 GET /api/oa/expense/detail/100 GET /api/oa/overtime/detail/12346 GET /api/oa/vehicle/detail/200 ``` **事前申请详情响应**: ```json { "code": 0, "data": { "id": 12345, "applicationNo": "BXSQ-20260603-001", "applicantId": 1001, "applicantName": "张三", "deptId": 10, "deptName": "销售部", "applicationDate": "2026-06-03", "urgency": "normal", "expenseTypes": "travel,office", "purpose": "北京客户拜访差旅费预估", "projectId": 100, "projectName": "华东市场拓展", "budgetSubjectId": 5, "budgetSubjectName": "差旅费", "estimatedAmount": 1900.00, "isTaxIncluded": false, "validUntil": "2026-07-03", "referenceNo": "HT-2026-0089", "estimatedStartDate": "2026-06-10", "estimatedEndDate": "2026-06-12", "isOvernight": true, "transportType": "high_speed_rail", "entertainmentTarget": null, "entertainmentLevel": null, "guestCount": null, "companionCount": null, "venue": null, "status": "pending", "usageStatus": "unused", "approvalInstanceId": "inst_erp_a_009832", "previousInstanceIds": [], "details": [ { "id": 600, "expenseCategory": "transport", "expenseCategoryName": "交通费", "quantity": 2, "unitPrice": 550.00, "unit": "张", "estimatedAmount": 1100.00, "remark": "往返高铁票", "sortOrder": 1 }, { "id": 601, "expenseCategory": "hotel", "expenseCategoryName": "住宿费", "quantity": 2, "unitPrice": 400.00, "unit": "晚", "estimatedAmount": 800.00, "remark": "住宿", "sortOrder": 2 } ], "attachments": [ { "id": 20, "fileName": "出差审批截图.png", "fileUrl": "https://oss/...", "fileType": "image", "fileSize": 204800 } ], "createTime": "2026-06-03T09:00:00", "updateTime": "2026-06-03T09:00:00" } } ``` **费用报销详情响应**: ```json { "code": 0, "data": { "id": 100, "reportNo": "BX-20260603-001", "applicantId": 1001, "applicantName": "张三", "deptId": 10, "deptName": "销售部", "applicationDate": "2026-06-03", "projectId": 100, "projectName": "华东市场拓展", "budgetSubjectId": 5, "budgetSubjectName": "差旅费", "costCenterId": 10, "costCenterName": "销售部-华东区", "totalAmount": 1580.00, "purpose": "差旅费报销", "bankName": "中国工商银行", "accountName": "张三", "bankAccount": "6222021234567890123", "isInvoiceVerified": false, "isTaxIdMatched": false, "isCategoryCompliant": false, "bankTransferNo": null, "voucherNo": null, "status": "approved", "paymentStatus": "unpaid", "approvalInstanceId": "inst_erp_a_009832", "previousInstanceIds": [], "applicationMappings": [ { "applicationId": 12300, "applicationNo": "BXSQ-20260520-003", "importedAmount": 5500.00 } ], "details": [ { "id": 500, "expenseDate": "2026-06-10", "expenseType": "transport", "expenseTypeName": "交通费", "expenseDesc": "G123 高铁票", "amount": 523.58, "taxAmount": 26.42, "totalAmount": 550.00, "currencyCode": "CNY", "exchangeRate": 1.0000, "baseAmount": 550.00, "invoiceNo": "12345678", "invoiceCode": "011001900111", "invoiceType": "special", "taxRate": 0.0900, "sortOrder": 1 } ], "attachments": [ { "id": 10, "fileName": "高铁票.jpg", "fileUrl": "https://oss/...", "fileType": "image", "fileSize": 307200, "detailId": 500 } ], "createTime": "2026-06-03T10:00:00", "updateTime": "2026-06-03T15:30:00" } } ``` **加班详情响应**: ```json { "code": 0, "data": { "id": 12346, "applicationNo": "JB-20260603-001", "applicantId": 1001, "applicantName": "张三", "deptId": 10, "deptName": "销售部", "otType": "workday", "compensationType": "overtime_pay", "compLeaveRatio": null, "startTime": "2026-06-03T18:00:00", "endTime": "2026-06-03T22:00:00", "netOtHours": 3.5, "reason": "紧急上线支持,需加班完成部署", "status": "approved", "approvalInstanceId": "inst_erp_a_009833", "previousInstanceIds": [], "createTime": "2026-06-03T18:30:00", "updateTime": "2026-06-03T18:30:00" } } ``` **用车详情响应**: ```json { "code": 0, "data": { "id": 200, "applicationNo": "YC-20260603-001", "applicantId": 1001, "applicantName": "张三", "deptId": 10, "deptName": "销售部", "vehicleId": 3, "licensePlate": "粤B12345", "vehicleType": "sedan", "brand": "丰田凯美瑞", "purpose": "business", "reason": "前往客户现场演示产品", "origin": "公司总部", "originLongitude": 121.473701, "originLatitude": 31.230416, "destination": "客户公司", "destLongitude": 121.510000, "destLatitude": 31.250000, "passengerCount": 3, "passengers": [ { "userId": 1002, "passengerName": "李四" }, { "userId": null, "passengerName": "客户代表" } ], "startTime": "2026-06-05T09:00:00", "endTime": "2026-06-05T17:00:00", "actualReturnTime": null, "startOdometer": null, "endOdometer": null, "actualCost": null, "costRemark": null, "status": "approved", "approvalInstanceId": "inst_erp_a_009835", "previousInstanceIds": [], "createTime": "2026-06-03T11:00:00", "updateTime": "2026-06-03T11:00:00" } } ``` ### 3.6 删除单据 ``` DELETE /api/oa/expense/100 DELETE /api/oa/{bizType}/{id} ``` **约束**:仅 `draft` 状态可删除。软删除(`IsDeleted=1`)。 **响应**: ```json { "code": 0, "message": "已删除" } ``` **错误**: | code | 场景 | |------|------| | 422 | 非 draft 状态不可删除 | ### 3.7 事前申请导入候选 ``` GET /api/oa/expense-apply/import-candidates?applicantId=1001 ``` **说明**:费用报销表单中"导入已通过的事前申请"的数据源。返回当前用户已通过且尚有可用额度(`UsageStatus IN ('unused', 'partially_used')`)的申请列表。 **响应**: ```json { "code": 0, "data": { "items": [ { "id": 12300, "applicationNo": "BXSQ-20260520-003", "estimatedAmount": 10000.00, "usedAmount": 4500.00, "availableAmount": 5500.00, "expenseTypes": "travel", "purpose": "北京出差预估", "projectId": 100, "projectName": "华东市场拓展", "budgetSubjectId": 5, "budgetSubjectName": "差旅费" } ] } } ``` ### 3.8 用车排期冲突检测 ``` GET /api/oa/vehicle/check-collision?vehicleId=3&startTime=2026-06-05T09:00:00&endTime=2026-06-05T17:00:00&excludeId= ``` **参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | vehicleId | long | ✅ | 车牌 ID | | startTime | datetime | ✅ | 出车时间 | | endTime | datetime | ✅ | 还车时间 | | excludeId | long | 否 | 编辑已有申请时排除自身 ID | **响应(无冲突)**: ```json { "code": 0, "data": { "hasCollision": false } } ``` **响应(有冲突)**: ```json { "code": 0, "data": { "hasCollision": true, "conflictApplicationNo": "YC-20260604-002", "conflictTimeRange": "2026-06-05 08:00 ~ 2026-06-05 12:00" } } ``` ### 3.9 还车登记 ``` POST /api/oa/vehicle/200/return ``` **权限**:申请人本人。 **请求**: ```json { "actualReturnTime": "2026-06-05T16:30:00", "startOdometer": 45678.50, "endOdometer": 45712.30, "actualCost": 85.00, "costRemark": "停车费30元,高速通行费55元" } ``` **校验**: - `endOdometer` ≥ `startOdometer` - 仅 `status=approved` 可还车 **响应**: ```json { "code": 0, "data": { "status": "returned", "mileage": 33.80, "actualCost": 85.00 } } ``` **后端操作**:更新 Vehicle 表还车字段 → `Status='returned'` → 车辆恢复 `SysVehicle.Status='idle'`。 ### 3.10 财务核销 ``` PUT /api/oa/expense/100/write-off ``` **权限**:需要 `oa.expense.mark_paid` 权限。 **请求**: ```json { "isInvoiceVerified": true, "isTaxIdMatched": true, "isCategoryCompliant": true, "bankTransferNo": "BT20260603001", "voucherNo": "PZ-2026-0603-0015" } ``` **校验**: - 三项合规检查必须全部为 true - `bankTransferNo` 和 `voucherNo` 必填 - 仅 `status=approved` 且 `paymentStatus=unpaid` 可核销 **响应**: ```json { "code": 0, "data": { "paymentStatus": "paid", "message": "核销完成,已归档" } } ``` **错误**: | code | 场景 | |------|------| | 422 | 合规检查未全部勾选 / 流水号或凭证号为空 | | 422 | 单据状态不满足核销条件 | ### 3.11 退回修改 ``` POST /api/oa/expense/100/return-for-revision ``` **权限**:需要 `oa.expense.mark_paid` 权限。 **请求**: ```json { "returnTo": "applicant", "reason": "发票验真未通过,请核实后重新提交" } ``` `returnTo` 取值:`applicant`(退回员工重填)/ `approver`(退回经理重审)。 **响应**: ```json { "code": 0, "message": "已退回修改" } ``` --- ## 4. 外勤日志 API ### 4.1 外勤日志列表 ``` GET /api/oa/outing-log/list?status=all&page=1&pageSize=20 ``` **参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | status | string | 否 | all / draft / completed | | page | int | 否 | 默认 1 | | pageSize | int | 否 | 默认 20 | | scope | string | 否 | own(默认) / dept / all — 根据权限自动限定 | **响应**: ```json { "code": 0, "data": { "items": [ { "id": 300, "visitNo": "VST-20260603-001", "salespersonId": 1001, "salespersonName": "张三", "deptName": "销售部", "customerId": 5001, "customerName": "北京科技有限公司", "checkInAddress": "北京市朝阳区建国路88号", "visitSummary": "与客户CTO进行了产品演示...", "status": "completed", "hasNewComment": true, "createTime": "2026-06-03T14:00:00" } ], "total": 15, "page": 1, "pageSize": 20 } } ``` > `hasNewComment`:`Comment.CreateTime > COALESCE(LastViewedTime, '1900-01-01')` 且评论者≠本人时为 true,前端展示橙色"新点评"标记。 ### 4.2 外勤日志详情 ``` GET /api/oa/outing-log/detail/300 ``` **响应**: ```json { "code": 0, "data": { "id": 300, "visitNo": "VST-20260603-001", "salespersonId": 1001, "salespersonName": "张三", "deptId": 10, "deptName": "销售部", "customerId": 5001, "customerName": "北京科技有限公司", "contactId": 6001, "contactName": "王总", "checkInLongitude": 116.407526, "checkInLatitude": 39.904030, "checkInAddress": "北京市朝阳区建国路88号", "visitSummary": "与客户CTO进行了产品演示,讨论了定制化需求...", "nextPlan": "下周三前送出详细方案和报价", "status": "completed", "attachments": [ { "id": 30, "fileName": "现场_20260603_143021.jpg", "fileUrl": "https://oss/...", "fileType": "sign_in_photo", "fileSize": 2048000, "sortOrder": 1 } ], "comments": [ { "id": 400, "commenterId": 2001, "commenterName": "王经理", "ratingStars": 4, "commentText": "拜访记录详实,客户需求梳理清晰。建议补充技术方案的可行性分析。", "createTime": "2026-06-03T16:00:00" } ], "createTime": "2026-06-03T14:00:00", "updateTime": "2026-06-03T14:00:00" } } ``` ### 4.3 存草稿 ``` PUT /api/oa/outing-log/draft ``` 请求体与提交相同(见 §4.4),`id` 为已有草稿 ID 或 null。不校验 GPS 和照片。 **响应**: ```json { "code": 0, "data": { "id": 301 } } ``` ### 4.4 提交外勤日志 ``` PUT /api/oa/outing-log/submit ``` **请求**: ```json { "id": null, "customerId": 5001, "customerName": "北京科技有限公司", "checkInLongitude": 116.407526, "checkInLatitude": 39.904030, "checkInAddress": "北京市朝阳区建国路88号", "visitSummary": "与客户CTO进行了产品演示,讨论了定制化需求...", "nextPlan": "下周三前送出详细方案和报价", "attachments": [ { "fileName": "现场_20260603_143021.jpg", "fileUrl": "https://oss/...", "fileType": "sign_in_photo", "fileSize": 2048000 } ] } ``` **响应**: ```json { "code": 0, "data": { "id": 300, "visitNo": "VST-20260603-001", "status": "completed" } } ``` **校验**: - GPS 定位不能为空 - 至少 1 张现场照片 - 客户名为空时自动创建 ERP 客户 ### 4.5 标记已查看 ``` PUT /api/oa/outing-log/300/view ``` 无请求体。更新 `LastViewedTime = NOW()`,使列表页"新点评"红点消失。 ### 4.6 主管点评 ``` POST /api/oa/outing-log/300/comment ``` **请求**: ```json { "ratingStars": 4, "commentText": "拜访记录详实,客户需求梳理清晰。建议补充技术方案的可行性分析。" } ``` **权限**:需要 `oa.outing_log.comment` 权限。 **响应**: ```json { "code": 0, "data": { "id": 400, "commenterId": 2001, "commenterName": "王经理", "ratingStars": 4, "commentText": "拜访记录详实,客户需求梳理清晰。建议补充技术方案的可行性分析。", "createTime": "2026-06-03T16:00:00" } } ``` ### 4.7 删除外勤日志 ``` DELETE /api/oa/outing-log/{id} ``` **约束**:仅 `draft` 状态可删除。软删除(`IsDeleted=1`)。级联软删除附件。 **响应**: ```json { "code": 0, "message": "已删除" } ``` --- ## 5. 审批代理 API ### 5.1 待审批列表 ``` GET /api/oa/approval/pending?userId=2001&bizType=&page=1&pageSize=20 ``` **参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | userId | long | ✅ | 审批人 ERP 用户 ID | | bizType | string | 否 | 为空时返回所有类型 | | page | int | 否 | | | pageSize | int | 否 | | **响应**: ```json { "code": 0, "data": { "items": [ { "bizType": "expense", "bizId": 100, "bizNo": "BX-20260603-001", "amount": 1580.00, "summary": "差旅费报销", "submitTime": "2026-06-03T10:00:00", "applicantId": 1001, "applicantName": "张三", "deptName": "销售部", "approvalLevel": 1 }, { "bizType": "overtime", "bizId": 12346, "bizNo": "JB-20260603-001", "amount": 3.5, "summary": "紧急上线支持", "submitTime": "2026-06-03T18:30:00", "applicantId": 1002, "applicantName": "李四", "deptName": "技术部", "approvalLevel": 2 } ], "total": 8, "page": 1 } } ``` ### 5.2 待审批数量 ``` GET /api/oa/approval/pending-count?userId=2001 ``` **响应**: ```json { "code": 0, "data": { "count": 8 } } ``` ### 5.3 审批时间线 ``` GET /api/oa/approval/timeline?bizType=expense&bizId=100 ``` **响应**: ```json { "code": 0, "data": { "bizType": "expense", "bizId": 100, "currentStatus": "pending", "currentApproverName": "王经理", "nodes": [ { "level": 0, "action": "submit", "approverName": "张三", "opinion": null, "approvalTime": "2026-06-03T10:00:00", "isValid": true }, { "level": 1, "action": "pending", "approverName": "王经理", "opinion": null, "approvalTime": null, "isValid": true } ] } } ``` ### 5.4 审批动作 ``` POST /api/oa/approval/action ``` **请求(同意)**: ```json { "instanceId": "inst_erp_a_009832", "action": "approve", "opinion": "同意报销" } ``` **请求(拒绝)**: ```json { "instanceId": "inst_erp_a_009832", "action": "reject", "opinion": "发票金额与申请金额不符,请核实后重新提交" } ``` **响应**: ```json { "code": 0, "message": "审批成功", "data": { "newStatus": "approved" } } ``` **校验**: - `action=reject` 时 `opinion` 必填且 ≥5 个汉字 - `action=transfer` 时 `transferToUserId` 必填 ### 5.5 撤回申请 ``` POST /api/oa/approval/withdraw ``` **请求**: ```json { "bizType": "expense", "bizId": 100, "instanceId": "inst_erp_a_009832" } ``` **响应**: ```json { "code": 0, "message": "已撤回" } ``` **后端操作**:.NET 服务端调 ERP 撤回 → OA 更新 `Status='withdrawn'`,旧 `ApprovalInstanceId` 追加到 `PreviousInstanceIds`。 ### 5.6 下属单据列表(经理版) ``` GET /api/oa/approval/subordinates?approverId=2001&bizType=expense&status=pending&page=1 ``` **响应**:结构与列表查询相同,额外返回申请人姓名/部门。 --- ## 6. 公告 API ### 6.1 公告列表 ``` GET /api/oa/announcement/list?type=all&page=1&pageSize=20 ``` **参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | type | string | 否 | all / notice / policy / activity。管理员额外支持 `draft`(我的草稿) | | page | int | 否 | 默认 1 | | pageSize | int | 否 | 默认 20 | **响应**: ```json { "code": 0, "data": { "items": [ { "id": 50, "title": "关于2026年端午节放假安排的通知", "type": "activity", "status": "published", "publisherId": 2001, "publisherName": "管理员王五", "deptName": "行政部", "publishTime": "2026-06-03T14:00:00", "isTop": true, "isRead": false, "isExpired": false, "expiryDate": "2026-06-22T00:00:00" } ], "total": 12, "page": 1, "pageSize": 20 } } ``` > 排序:`isTop` 优先 → 未过期按 `publishTime` 倒序 → 已过期置灰垫底。`isRead` 来自 `AnnouncementReadLog.IsRead`,`isExpired` = `expiryDate < NOW()`。 ### 6.2 公告详情 ``` GET /api/oa/announcement/detail/50 ``` **响应**: ```json { "code": 0, "data": { "id": 50, "title": "关于2026年端午节放假安排的通知", "content": "

放假时间

6月19日(周五)至6月21日(周日)...

", "type": "activity", "status": "published", "publisherId": 2001, "publisherName": "管理员王五", "deptName": "行政部", "publishTime": "2026-06-03T14:00:00", "isTop": true, "privateLevel": 0, "expiryDate": "2026-06-22T00:00:00", "isExpired": false, "attachments": [ { "id": 40, "fileName": "放假安排.pdf", "fileUrl": "https://oss/...", "fileType": "pdf", "fileSize": 512000 } ], "createTime": "2026-06-03T13:00:00", "updateTime": "2026-06-03T14:00:00" } } ``` ### 6.3 存草稿 ``` PUT /api/oa/announcement/draft ``` 请求体与发布相同(见 §6.4),不校验必填项。`Status='draft'`,仅创建者和管理员可见。 **响应**: ```json { "code": 0, "data": { "id": 51 } } ``` ### 6.4 发布公告 ``` POST /api/oa/announcement/publish ``` **请求(新建发布)**: ```json { "id": null, "title": "关于2026年端午节放假安排的通知", "content": "

放假时间

6月19日(周五)至6月21日(周日)...

", "type": "activity", "isTop": false, "expiryDate": "2026-06-22T00:00:00", "privateLevel": 0, "targets": [] } ``` **请求(按部门发布)**: ```json { "id": null, "title": "销售部Q2业绩考核通知", "content": "

...

", "type": "notice", "isTop": true, "privateLevel": 1, "targets": [ { "targetType": "dept", "targetId": 10 } ] } ``` **请求(按指定用户发布)**: ```json { "id": null, "title": "销售部Q3考核安排", "content": "

...

", "type": "notice", "isTop": false, "privateLevel": 2, "targets": [ { "targetType": "user", "targetId": 1001 }, { "targetType": "user", "targetId": 1002 } ] } ``` **响应**: ```json { "code": 0, "data": { "id": 50, "status": "published", "publishTime": "2026-06-03T14:00:00", "targetCount": 156 } } ``` **后端操作**:发布时异步初始化 `AnnouncementReadLog`(IsRead=0)。 ### 6.5 标记已读 ``` POST /api/oa/announcement/50/read ``` 无请求体。停留 ≥2s 时调用。无权限要求。 ### 6.6 DING 催办 ``` POST /api/oa/announcement/50/ding ``` **权限**:需要 `oa.announcement.manage` 权限。 **响应**: ```json { "code": 0, "data": { "urgedCount": 5 } } ``` **后端操作**:查 `AnnouncementReadLog WHERE IsRead=0` → 更新 `IsUrged=1` → 调消息模块推送。 ### 6.7 已读/未读统计 ``` GET /api/oa/announcement/read-stats/50 ``` **响应**: ```json { "code": 0, "data": { "readCount": 45, "unreadCount": 5, "readUsers": [ { "userId": 1001, "userName": "张三", "deptName": "销售部", "readTime": "2026-06-03T14:05:00" } ], "unreadUsers": [ { "userId": 1005, "userName": "赵六", "deptName": "技术部" } ] } } ``` --- ## 7. 报表 API ### 7.1 通用格式 ``` GET /api/oa/report/{type}?range=month&startDate=2026-06-01&endDate=2026-06-30&userId={erpUserId}&deptId={deptId} ``` **通用参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | range | string | 否 | month / quarter / year。与 startDate+endDate 互斥,优先使用自定义日期 | | startDate | string | 否 | 自定义开始日期 YYYY-MM-DD | | endDate | string | 否 | 自定义结束日期 YYYY-MM-DD | | userId | long | 否 | 个人报表时传入;部门/全公司报表时不传 | | deptId | long | 否 | 部门报表时传入 | | scope | string | 否 | own(默认) / dept / all — 根据角色权限自动限定 | **type 取值**:`expense-apply` | `expense` | `overtime` | `vehicle` | `outing-log` > **数据来源**:数值卡片和图表均来自 OA 本地业务表,.NET 按角色权限 + 时间范围聚合。审批状态统计通过 ERP 实时查询补充。预置时间段(month/quarter/year)缓存 5 分钟,自定义日期实时查询。 **通用响应结构**: ```json { "code": 0, "data": { "cards": { ... }, "chart": { ... }, "memberBreakdown": [ ... ], "detailList": { "items": [ ... ], "total": 0, "page": 1 } } } ``` > `memberBreakdown` 仅经理角色返回(部门横向对比柱状图数据)。`detailList` 仅经理/财务/管理员返回(底部明细列表,分页)。 ### 7.2 事前申请明细报表 ``` GET /api/oa/report/expense-apply?range=year&userId=1001 ``` **专属参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | status | string | 否 | all / approved / rejected / withdrawn | **响应**: ```json { "code": 0, "data": { "cards": { "yearTotalAmount": 28500.00, "monthCount": 3, "approvedCount": 2, "approvedAmount": 15000.00 }, "chart": { "labels": ["2025-07", "2025-08", "...", "2026-06"], "appliedAmounts": [1200, 3400, 0, 0, 5600, 2300, 4500, 1800, 2900, 0, 3800, 2000], "approvedAmounts": [1200, 3000, 0, 0, 0, 2300, 4000, 1800, 2900, 0, 3800, 2000] }, "memberBreakdown": [ { "userId": 1001, "userName": "张三", "appliedAmount": 15000.00, "approvedAmount": 12000.00 }, { "userId": 1002, "userName": "李四", "appliedAmount": 8000.00, "approvedAmount": 8000.00 } ], "detailList": { "items": [ { "id": 12345, "applicationNo": "BXSQ-20260603-001", "applicantName": "张三", "deptName": "销售部", "estimatedAmount": 1900.00, "expenseTypes": "travel,office", "status": "pending", "createTime": "2026-06-03T09:00:00" } ], "total": 42, "page": 1 } } } ``` ### 7.3 费用报销明细报表 ``` GET /api/oa/report/expense?range=year&userId=1001 ``` **专属参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | status | string | 否 | all / approved / rejected | | paymentStatus | string | 否 | all / unpaid / paid | **响应**: ```json { "code": 0, "data": { "cards": { "yearTotalPaid": 28500.00, "monthCount": 3, "pendingCount": 1, "approvedUnpaidCount": 2 }, "chart": { "labels": ["2025-07", "2025-08", "...", "2026-06"], "appliedAmounts": [1200, 3400, 0, 0, 5600, 2300, 4500, 1800, 2900, 0, 3800, 2000], "approvedAmounts": [1200, 3000, 0, 0, 5600, 2300, 4000, 1800, 2900, 0, 3800, 2000] }, "memberBreakdown": [ { "userId": 1001, "userName": "张三", "appliedAmount": 12000.00, "approvedAmount": 12000.00 } ], "detailList": { "items": [ { "id": 100, "reportNo": "BX-20260603-001", "...": "..." } ], "total": 42, "page": 1 } } } ``` ### 7.4 加班明细报表 ``` GET /api/oa/report/overtime?range=year&deptId=10 ``` **专属参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | otType | string | 否 | all / workday / weekend / holiday | **响应**: ```json { "code": 0, "data": { "cards": { "monthTotalHours": 42.5, "monthCount": 8, "compLeaveHours": 24.0, "overtimePayCount": 3 }, "chart": { "labels": ["2025-07", "...", "2026-06"], "workdayHours": [10, 8, 12, 5, 0, 8, 15, 6, 0, 10, 4, 8], "weekendHours": [5, 0, 8, 0, 0, 0, 0, 4, 0, 0, 8, 0], "holidayHours": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, "memberBreakdown": [ { "userId": 1001, "userName": "张三", "totalHours": 18.5 }, { "userId": 1002, "userName": "李四", "totalHours": 24.0 } ], "detailList": { "items": [ { "id": 12346, "applicationNo": "JB-20260603-001", "...": "..." } ], "total": 15, "page": 1 } } } ``` ### 7.5 用车明细报表 ``` GET /api/oa/report/vehicle?range=year&deptId=10 ``` **专属参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | vehicleId | long | 否 | 按车牌筛选 | | purpose | string | 否 | all / reception / business / official | **响应**: ```json { "code": 0, "data": { "cards": { "monthCount": 5, "totalMileage": 1250.80, "tollParkingCost": 385.00, "unreturnedCount": 1 }, "chart": { "labels": ["2025-07", "...", "2026-06"], "tripCounts": [3, 2, 5, 1, 4, 2, 6, 3, 0, 2, 5, 4], "costs": [120, 80, 250, 50, 180, 90, 300, 150, 0, 100, 220, 180] }, "memberBreakdown": [ { "userId": 1001, "userName": "张三", "tripCount": 8, "totalCost": 450.00 } ], "detailList": { "items": [ { "id": 200, "applicationNo": "YC-20260603-001", "...": "..." } ], "total": 25, "page": 1 } } } ``` ### 7.6 外勤日志明细报表 ``` GET /api/oa/report/outing-log?range=year&deptId=10 ``` **专属参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | customerName | string | 否 | 客户名模糊搜索 | **响应**: ```json { "code": 0, "data": { "cards": { "monthVisitCount": 12, "uniqueCustomerCount": 8, "avgRatingStars": 4.2, "uncommentedCount": 3 }, "chart": { "labels": ["2025-07", "...", "2026-06"], "visitCounts": [5, 8, 12, 6, 10, 7, 15, 9, 4, 11, 8, 12], "avgRatings": [4.0, 4.5, 3.8, 4.2, 4.0, 4.3, 3.9, 4.5, 4.0, 4.1, 4.2, 4.2] }, "memberBreakdown": [ { "userId": 1001, "userName": "张三", "visitCount": 25, "avgRating": 4.0 } ], "detailList": { "items": [ { "id": 300, "visitNo": "VST-20260603-001", "...": "..." } ], "total": 80, "page": 1 } } } ``` ### 7.7 导出 Excel ``` POST /api/oa/report/{type}/export ``` **权限**:需要 `oa.report.export` 权限(财务/管理员角色)。 **请求**: ```json { "range": "quarter", "startDate": "2026-04-01", "endDate": "2026-06-30", "deptId": 10, "status": "all", "paymentStatus": "all" } ``` > 参数与对应报表查询接口一致。`range` 与 `startDate+endDate` 互斥。 **响应**:`Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`,二进制文件流。Flutter 收到后通过 MethodChannel 唤起系统分享面板。 --- ## 8. 工作台 API ### 8.1 工作台汇总数据 ``` GET /api/oa/home/summary ``` **说明**:工作台首页的快捷看板数据。根据当前用户 OA 权限自动判定角色,返回对应数据范围(本人/本部门/全公司)。 **权限**:登录即可访问,数据范围按角色自动限定。 **响应(员工版)**: ```json { "code": 0, "data": { "role": "employee", "banners": [ { "id": 1, "imageUrl": "https://oss/...", "title": "Q2优秀员工表彰", "linkUrl": "tboss://oa/announcement/detail/50", "sortOrder": 1 } ], "cards": { "monthTotalPaid": 28500.00, "monthSubmitCount": 5 } } } ``` **响应(经理版)**: ```json { "code": 0, "data": { "role": "manager", "banners": [ "..." ], "pendingApprovalCount": 8, "cards": { "monthTotalPaid": 128500.00, "monthSubmitCount": 42, "deptPendingCount": 8 } } } ``` **响应(财务/管理员版)**: ```json { "code": 0, "data": { "role": "finance", "banners": [ "..." ], "cards": { "monthTotalPaid": 525000.00, "unpaidTotal": 185000.00, "weekRejectedCount": 3 } } } ``` > 数据缓存 5 分钟。管理员版额外在金刚区展示"发布公告"入口(由前端根据 role 或权限判断)。员工/经理/财务/管理员角色判定优先级见 PRD §3.2。 --- ## 9. ERP 集成 API(.NET 服务端转发) ### 9.1 预算查询 ``` GET /api/oa/budget/balance?projectId=100&subjectId=5 ``` **响应**: ```json { "code": 0, "data": { "hasBudget": true, "availableAmount": 150000.00, "frozenAmount": 20000.00 } } ``` ERP 无预算时返回 `{ "hasBudget": false }`,OA 前端隐藏预算区块。 ### 9.2 项目/科目/成本中心 ``` GET /api/oa/projects # 项目列表 GET /api/oa/subjects?projectId= # 某项目下的预算科目 GET /api/oa/cost-centers # 成本中心列表 ``` ### 9.3 汇率查询 ``` GET /api/oa/exchange-rate?currencyCode=USD ``` **响应**: ```json { "code": 0, "data": { "currencyCode": "USD", "rate": 7.2456, "date": "2026-06-03" } } ``` --- ## 10. 基础数据 API ### 10.1 费用类别字典 ``` GET /api/oa/cost-categories?bizScope=both&expenseType=travel ``` **参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | bizScope | string | 否 | expense_apply / expense / both(默认 both) | | expenseType | string | 否 | 按费用大类过滤(travel/entertainment/office 等),不传返回全部 | **说明**:事前申请和费用报销表单中"费用类别"下拉的数据源。返回启用的叶子节点(`ParentId IS NOT NULL`),`ExpenseType` 用于联动过滤(如事前申请选了"差旅费"大类,则仅返回绑定了 travel 的费用小类)。 **响应**: ```json { "code": 0, "data": { "categories": [ { "id": 10, "categoryName": "交通费", "categoryCode": "transport", "parentId": 1, "expenseType": "travel", "sortOrder": 1 }, { "id": 11, "categoryName": "住宿费", "categoryCode": "hotel", "parentId": 1, "expenseType": "travel", "sortOrder": 2 }, { "id": 20, "categoryName": "办公用品", "categoryCode": "office_supplies", "parentId": 5, "expenseType": "office", "sortOrder": 1 } ] } } ``` ### 10.2 车辆管理 ``` GET /api/oa/vehicles # 列表 POST /api/oa/vehicles # 添加 PUT /api/oa/vehicles/3 # 修改 ``` **GET 响应**: ```json { "code": 0, "data": { "vehicles": [ { "id": 1, "licensePlate": "粤B12345", "vehicleType": "sedan", "brand": "丰田凯美瑞", "seats": 5, "driverName": "陈司机", "status": "idle", "isActive": true } ] } } ``` ### 10.3 轮播图 ``` GET /api/oa/banners ``` **响应**: ```json { "code": 0, "data": { "banners": [ { "id": 1, "imageUrl": "https://oss/...", "title": "2026年Q2优秀员工表彰", "linkUrl": "tboss://oa/announcement/detail/50", "sortOrder": 1 } ] } } ``` ### 10.4 文件上传 ``` POST /api/oa/upload Content-Type: multipart/form-data ``` **说明**:统一文件上传端点。文件上传至云端对象存储(OSS),返回绝对 URL。前端拿到 URL 后在提交表单时写入 `attachments` 数组。 **参数**: | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | file | file | ✅ | 文件本体 | | bizType | string | ✅ | expense_apply / expense / outing_log / announcement | **校验**: | bizType | 数量限制 | 类型限制 | 大小限制 | |---------|:--:|------|:--:| | expense_apply | ≤9 | 图片/PDF | 图片≤10MB, PDF≤20MB | | expense | ≤9 | 图片 | ≤10MB | | outing_log | 1~9 | 仅图片 | ≤10MB | | announcement | ≤5 | PDF/图片/Word/Excel | ≤20MB | > 数量限制由前端在上传入口处控制(满额隐藏上传按钮),后端做二次校验。 **响应**: ```json { "code": 0, "data": { "fileUrl": "https://oss.example.com/oa/2026/06/abc123.jpg", "fileName": "现场_20260603_143021.jpg", "fileType": "image", "fileSize": 2048000 } } ``` **错误**: | code | 场景 | |------|------| | 422 | 文件大小超限 / 类型不支持 / 数量超限 | --- ## 11. 复用已有 API(参考) 以下 API 为 .NET 服务端已有,OA 模块直接调用。此处仅列接口签名,详细文档见原系统。 | 方法 | URL | 说明 | 关键参数 | |------|-----|------|---------| | GET | `/api/user/{id}` | 用户信息 | id: ERP 用户 ID | | GET | `/api/user/search?q=&page=` | 用户搜索 | q: 姓名/工号 | | GET | `/api/dept/tree` | 部门树 | — | | PUT | `/api/user/avatar` | 上传头像 | multipart/form-data | | GET | `/api/customer/search?q=` | 客户联想 | q: 客户名称 | | GET | `/api/messages?page=` | 消息列表 | page | | GET | `/api/messages/unread-count` | 未读消息数 | — | | PUT | `/api/messages/:id/read` | 标记已读 | id | | POST | `/api/messages/read-all` | 全部已读 | — | | DELETE | `/api/messages/:id` | 删除消息 | id | | GET | `/api/dict/banks` | 银行列表 | — | | GET | `/api/dict/cost-categories` | 费用类别 | — | --- > **文档版本**:v1.0 | 日期:2026-06-04