Kaynağa Gözat

接口开发

ht 2 saat önce
ebeveyn
işleme
094b602080

+ 63 - 3
README.md

@@ -34,6 +34,7 @@ src/main/java/com/malk/minglei
 |   |-- MjavaYidaConfiguration.java
 |   `-- YidaApplicationsProperties.java
 |-- controller
+|   |-- ApprovalTransferDepartmentController.java
 |   |-- EmployeeRosterController.java
 |   |-- ErpYidaPushController.java
 |   |-- GlobalExceptionHandler.java
@@ -41,10 +42,12 @@ src/main/java/com/malk/minglei
 |   `-- YidaProcessController.java
 |-- dto
 `-- service
+    |-- approval
     |-- employee
     |-- port
     |-- yida
     `-- impl
+        |-- approval
         |-- employee
         |-- port
         `-- yida
@@ -56,6 +59,7 @@ src/main/java/com/malk/minglei
 | --- | --- |
 | `controller` | 暴露 HTTP 接口,处理请求校验和响应包装 |
 | `dto` | 请求体、响应体和业务数据模型 |
+| `service.approval` | 钉钉审批实例查询、表单解析,以及通过宜搭审批矩阵(按"部门"字段匹配)解析转入部门各级别负责人与部门负责人 |
 | `service.employee` | 员工花名册同步、查询和钉钉人员数据处理 |
 | `service.port` | 明磊外部 T100 接口转发 |
 | `service.yida` | 宜搭流程、宜搭表单查询、ERP 推送分发 |
@@ -223,7 +227,62 @@ POST /api/yida/minglei/employee-roster/getUserRoster
 Content-Type: application/json
 ```
 
-### 4.5 外部 T100 接口转发
+### 4.5 审批转入部门主管查询
+
+```http
+POST /api/dingtalk/approval/transfer-department/managers
+Content-Type: application/json
+```
+
+请求示例:
+
+```json
+{
+  "processInstanceId": "YuRRlAutRFi4CcsrPkqvQg05031789886060"
+}
+```
+
+服务端处理链路:
+
+1. 调用钉钉审批开放接口 `GET https://api.dingtalk.com/v1.0/workflow/processInstances`,通过 `x-acs-dingtalk-access-token` 请求头传入应用凭证。
+2. 从 `result.formComponentValues` 中查找 `name` 为 `转入部门` 的表单组件;未匹配到时回退匹配 `bizAlias` 为 `transferDepartment` 的组件。
+3. 解析该组件的 `extValue`,取第一个元素的 `id` 作为转入部门 ID,`deptName` 或 `name` 作为部门名称。`extValue` 中的 `id` 可能是字符串或数字,服务端统一按字符串处理。
+4. 复用工程内 `MingleiDocumentService.queryDepartmentManagersByDepartmentId`(宜搭审批矩阵表单 `FORM-8F527F696C5B4AF7AB7A53979527FBA23USO`)按"部门"选择字段 `departmentSelectField_mszdqrks` 内嵌的部门 ID 精确匹配,查询各级别负责人,不再调用钉钉通讯录部门详情接口。
+
+> 注意:第 4 步改为按"部门"选择字段匹配(而非原先的"部门编号"文本字段 `textField_msztwhs2`),且返回结果新增"部门负责人"字段(审批矩阵 `employeeField_mualkf6a`)。
+
+响应中各级别负责人按字段分开返回,分别对应审批矩阵表单中的 `employeeField_mszdqrl8`(一级部门主管)、`employeeField_mszdqrl9`(二级部门主管)、`employeeField_mszdqrla`(三级部门主管)、`employeeField_mszdqrlb`(四级部门主管)、`employeeField_mualkf6a`(部门负责人)与 `employeeField_mszdqrl3`(部门分管领导)。
+
+成功响应:
+
+```json
+{
+  "code": 200,
+  "isSuccess": true,
+  "result": {
+    "departmentId": "972414534",
+    "departmentName": "合作伙伴",
+    "firstLevelManagerUserId": "manager-1",
+    "secondLevelManagerUserId": "manager-2",
+    "thirdLevelManagerUserId": "manager-3",
+    "fourthLevelManagerUserId": "manager-4",
+    "departmentHeadUserId": "manager-head",
+    "departmentLeaderUserId": "manager-5"
+  }
+}
+```
+
+各部门负责人字段为对应级别审批人的钉钉 `userid`,审批矩阵未配置该级别负责人时该字段为 `null`。`getAllManagerUserIds()` 按一级至四级部门主管、部门负责人、部门分管领导的顺序返回所有非空的负责人 ID。
+
+| 异常场景 | HTTP 状态码 | 说明 |
+| --- | --- | --- |
+| `processInstanceId` 为空 | `400` | 请求参数不合法 |
+| 审批实例无表单数据或未包含转入部门字段 | `400` | 表单结构与预期不一致 |
+| 转入部门 `extValue` 缺少部门 ID | `400` | 无法解析部门 ID |
+| 审批实例接口调用失败或宜搭审批矩阵查询失败 | `502` | 返回 `DINGTALK_APPROVAL_CALL_FAILED` 或 `YIDA_CALL_FAILED` 及上游错误码 |
+| 宜搭应用或钉钉凭证配置缺失 | `412` | 返回配置缺失信息 |
+
+### 4.6 外部 T100 接口转发
 
 ```http
 POST /api/minglei/ports/supplier-price-approvals
@@ -249,7 +308,7 @@ Content-Type: application/json
 }
 ```
 
-### 4.6 ERP 推送宜搭
+### 4.7 ERP 推送宜搭
 
 ```http
 POST /api/erp/yida/push
@@ -522,11 +581,12 @@ mvn clean test
 - 宜搭流程处理器分发。
 - ERP 推送到不同宜搭表单处理器的字段映射。
 - 外部 T100 请求组装和响应转发。
+- 钉钉审批实例详情调用、转入部门解析和宜搭审批矩阵(按"部门"字段匹配)各级别部门负责人与部门负责人提取。
 
 当前验证结果:
 
 ```text
-Tests run: 36, Failures: 0, Errors: 0, Skipped: 1
+Tests run: 52, Failures: 0, Errors: 0, Skipped: 1
 ```
 
 ## 9. 扩展规范

+ 86 - 1
src/main/java/com/malk/minglei/service/impl/yida/MingleiDocumentServiceImpl.java

@@ -37,6 +37,15 @@ public class MingleiDocumentServiceImpl implements MingleiDocumentService {
     private static final String THIRD_LEVEL_DEPARTMENT_MANAGER_FIELD_ID = "employeeField_mszdqrla";
     private static final String FOURTH_LEVEL_DEPARTMENT_MANAGER_FIELD_ID = "employeeField_mszdqrlb";
     private static final String DEPARTMENT_LEADER_FIELD_ID = "employeeField_mszdqrl3";
+    /**
+     * 审批矩阵表单中"部门"选择字段 ID,其存储值的 {@code _id} 即钉钉部门 ID。
+     * 审批矩阵按此字段精确匹配转入部门,替代原先的"部门编号"文本字段。
+     */
+    private static final String DEPARTMENT_FIELD_ID = "departmentSelectField_mszdqrks";
+    /**
+     * 审批矩阵表单中"部门负责人"成员字段 ID(宜搭线上表单新增字段)。
+     */
+    private static final String DEPARTMENT_HEAD_FIELD_ID = "employeeField_mualkf6a";
     private static final String[] DEPARTMENT_MANAGER_FIELD_IDS = {
             FIRST_LEVEL_DEPARTMENT_MANAGER_FIELD_ID,
             SECOND_LEVEL_DEPARTMENT_MANAGER_FIELD_ID,
@@ -119,6 +128,27 @@ public class MingleiDocumentServiceImpl implements MingleiDocumentService {
         throw new IllegalArgumentException("One of dept_id, user_id or dept_name must be provided");
     }
 
+    /**
+     * 按"部门"选择字段中内嵌的部门 ID 精确查询审批矩阵,返回各级别负责人 userid。
+     *
+     * <p>与 {@link #queryDocuments(MingleiDocumentQueryRequest)} 的区别在于:本方法按部门选择字段
+     * {@code departmentSelectField_mszdqrks}(其 {@code _id} 为钉钉部门 ID)匹配,而非按"部门编号"文本字段;
+     * 并且返回的结果额外包含"部门负责人"字段,以语义化 key 暴露,解耦调用方与具体字段 ID。
+     *
+     * @param departmentId 钉钉部门 ID,与审批矩阵"部门"字段的 {@code _id} 精确匹配
+     * @return 仅包含一级至四级部门负责人、部门负责人和部门分管领导的单层结果
+     */
+    @Override
+    public Map<String, Object> queryDepartmentManagersByDepartmentId(String departmentId) {
+        String normalizedDepartmentId = trimToNull(departmentId);
+        if (normalizedDepartmentId == null) {
+            throw new IllegalArgumentException("departmentId must not be blank");
+        }
+
+        Map<String, Object> queryResult = executeQuery(DEPARTMENT_FIELD_ID, normalizedDepartmentId, 1, PRECISE_QUERY_PAGE_SIZE, true);
+        return extractApprovalTransferManagers(queryResult);
+    }
+
     /**
      * 按查询范围读取部门成员 ID,并按成员首次出现顺序去重。
      *
@@ -180,6 +210,10 @@ public class MingleiDocumentServiceImpl implements MingleiDocumentService {
     }
 
     private Map<String, Object> executeQuery(String fieldId, String fieldValue, int page, int size) {
+        return executeQuery(fieldId, fieldValue, page, size, false);
+    }
+
+    private Map<String, Object> executeQuery(String fieldId, String fieldValue, int page, int size, boolean departmentField) {
         validateConfiguration();
 
         YDParam parameter = new YDParam();
@@ -189,7 +223,9 @@ public class MingleiDocumentServiceImpl implements MingleiDocumentService {
         parameter.setCurrentPage(page);
         parameter.setPageSize(size);
         if (fieldId != null) {
-            parameter.setSearchFieldJson(createExactSearchFieldJson(fieldId, fieldValue));
+            parameter.setSearchFieldJson(departmentField
+                    ? createDepartmentSearchFieldJson(fieldId, fieldValue)
+                    : createExactSearchFieldJson(fieldId, fieldValue));
         }
 
         DDR_New response = yidaClient.queryData(parameter, YDConf.FORM_QUERY.retrieve_search_form);
@@ -231,6 +267,31 @@ public class MingleiDocumentServiceImpl implements MingleiDocumentService {
         return result;
     }
 
+    /**
+     * 从精准查询命中的首条单据提取审批转入所需的各级别负责人,并以语义化 key 暴露。
+     *
+     * <p>相较 {@link #extractDepartmentManagers(Map)},本方法额外提取"部门负责人"字段,且不直接返回
+     * 宜搭字段 ID,避免调用方硬编码字段 ID 而产生漂移。
+     *
+     * @param queryResult 宜搭列表查询结果
+     * @return 包含一级至四级部门负责人、部门负责人和部门分管领导的语义化结果
+     */
+    private Map<String, Object> extractApprovalTransferManagers(Map<String, Object> queryResult) {
+        Map formData = firstFormData(queryResult.get("records"));
+        if (formData == null) {
+            return Collections.emptyMap();
+        }
+
+        Map<String, Object> result = new LinkedHashMap<String, Object>();
+        result.put("firstLevelManagerUserId", firstEmployeeUserId(formData.get(FIRST_LEVEL_DEPARTMENT_MANAGER_FIELD_ID + "_id")));
+        result.put("secondLevelManagerUserId", firstEmployeeUserId(formData.get(SECOND_LEVEL_DEPARTMENT_MANAGER_FIELD_ID + "_id")));
+        result.put("thirdLevelManagerUserId", firstEmployeeUserId(formData.get(THIRD_LEVEL_DEPARTMENT_MANAGER_FIELD_ID + "_id")));
+        result.put("fourthLevelManagerUserId", firstEmployeeUserId(formData.get(FOURTH_LEVEL_DEPARTMENT_MANAGER_FIELD_ID + "_id")));
+        result.put("departmentHeadUserId", firstEmployeeUserId(formData.get(DEPARTMENT_HEAD_FIELD_ID + "_id")));
+        result.put("departmentLeaderUserId", firstEmployeeUserId(formData.get(DEPARTMENT_LEADER_FIELD_ID + "_id")));
+        return result;
+    }
+
     private Map firstFormData(Object records) {
         if (!(records instanceof Iterable)) {
             return null;
@@ -329,6 +390,30 @@ public class MingleiDocumentServiceImpl implements MingleiDocumentService {
         }
     }
 
+    /**
+     * 按宜搭部门选择字段(DepartmentSelectField)的等值查询格式生成 searchFieldJson。
+     *
+     * <p>部门字段存储为 {@code [{"_id":"部门ID","label":"部门名"}]},需以部门 ID 作为 value,
+     * 并使用 {@code DepartmentSelectField} 组件类型;沿用报表字段配置约定 {@code dataType=STRING}。
+     *
+     * @param fieldId 宜搭部门字段 ID
+     * @param value 需要精确匹配的部门 ID
+     * @return 宜搭搜索条件 JSON
+     */
+    private String createDepartmentSearchFieldJson(String fieldId, String value) {
+        Map<String, String> condition = new LinkedHashMap<String, String>();
+        condition.put("key", fieldId);
+        condition.put("value", value);
+        condition.put("type", "STRING");
+        condition.put("operator", "eq");
+        condition.put("componentName", "DepartmentSelectField");
+        try {
+            return objectMapper.writeValueAsString(Collections.singletonList(condition));
+        } catch (JsonProcessingException exception) {
+            throw new IllegalStateException("Unable to create Yida department search condition", exception);
+        }
+    }
+
     private String firstDepartmentId(Object departmentIds) {
         if (departmentIds instanceof Iterable) {
             for (Object departmentId : (Iterable<?>) departmentIds) {

+ 8 - 0
src/main/java/com/malk/minglei/service/yida/MingleiDocumentService.java

@@ -27,6 +27,14 @@ public interface MingleiDocumentService {
      */
     Map<String, Object> queryDocuments(MingleiDocumentQueryRequest request);
 
+    /**
+     * 按"部门"选择字段中内嵌的部门 ID 精确查询审批矩阵,返回各级别负责人 userid。
+     *
+     * @param departmentId 钉钉部门 ID,与审批矩阵"部门"字段的 _id 精确匹配
+     * @return 包含一级至四级部门负责人、部门负责人和部门分管领导的语义化结果
+     */
+    Map<String, Object> queryDepartmentManagersByDepartmentId(String departmentId);
+
     /**
      * 按查询范围获取指定部门中的钉钉用户 ID。
      *