소스 검색

feat: 客户放行申请按部门配置填充公司和负责人

lfx 3 일 전
부모
커밋
73279902fa

+ 20 - 2
src/main/java/com/malk/minglei/service/impl/yida/CustomerReleaseFormPushHandler.java

@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
 import com.malk.minglei.config.YidaApplicationsProperties;
 import com.malk.minglei.dto.ErpYidaPushMainData;
 import com.malk.minglei.dto.ErpYidaPushRequest;
+import com.malk.minglei.service.yida.MingleiDocumentService;
 import com.malk.minglei.service.yida.YidaFormPushHandler;
 import com.malk.minglei.service.yida.YidaProcessOpenApiClient;
 import com.malk.server.common.McException;
@@ -40,6 +41,9 @@ public class CustomerReleaseFormPushHandler implements YidaFormPushHandler {
     private static final String APPLICANT_FIELD_ID = "employeeField_msh186rr";
     private static final String COMPANY_FIELD_ID = "selectField_mtv7uxgd";
     private static final String DEPARTMENT_FIELD_ID = "departmentSelectField_msh186ry";
+    private static final String FIRST_LEVEL_MANAGER_FIELD_ID = "employeeField_mt6thsdh";
+    private static final String SECOND_LEVEL_MANAGER_FIELD_ID = "employeeField_mt6thsdi";
+    private static final String THIRD_LEVEL_MANAGER_FIELD_ID = "employeeField_mt6v6alx";
     private static final String APPLY_DATE_FIELD_ID = "dateField_msh186rs";
     private static final String FORM_TITLE_FIELD_ID = "textField_mspcpz92";
     private static final String RELEASE_NO_FIELD_ID = "textField_mt7zi4fy";
@@ -60,6 +64,7 @@ public class CustomerReleaseFormPushHandler implements YidaFormPushHandler {
     private final YidaProcessOpenApiClient yidaProcessOpenApiClient;
     private final DDConf dingTalkConfiguration;
     private final YidaApplicationsProperties applications;
+    private final MingleiDocumentService mingleiDocumentService;
 
     /**
      * 创建客户放行审批推送处理器。
@@ -67,13 +72,16 @@ public class CustomerReleaseFormPushHandler implements YidaFormPushHandler {
      * @param yidaProcessOpenApiClient 宜搭流程 OpenAPI 客户端
      * @param dingTalkConfiguration 钉钉应用配置
      * @param applications 宜搭应用配置
+     * @param mingleiDocumentService 部门申请公司和负责人配置查询服务
      */
     public CustomerReleaseFormPushHandler(YidaProcessOpenApiClient yidaProcessOpenApiClient,
                                           DDConf dingTalkConfiguration,
-                                          YidaApplicationsProperties applications) {
+                                          YidaApplicationsProperties applications,
+                                          MingleiDocumentService mingleiDocumentService) {
         this.yidaProcessOpenApiClient = yidaProcessOpenApiClient;
         this.dingTalkConfiguration = dingTalkConfiguration;
         this.applications = applications;
+        this.mingleiDocumentService = mingleiDocumentService;
     }
 
     /**
@@ -141,7 +149,6 @@ public class CustomerReleaseFormPushHandler implements YidaFormPushHandler {
         Map<String, Object> formData = new LinkedHashMap<String, Object>();
         putIfNotBlank(formData, TITLE_FIELD_ID, title);
         putIfNotBlank(formData, APPLICANT_FIELD_ID, toStringList(operatorUserId, null));
-        putIfNotBlank(formData, COMPANY_FIELD_ID, firstNonBlank(value(main, "gs", "applyCompany", "company"), value(main, "xmfxent")));
         putIfNotBlank(formData, DEPARTMENT_FIELD_ID, toStringList(value(main, "bm", "applyDepartment", "department", "deptId"), null));
         putIfNotBlank(formData, APPLY_DATE_FIELD_ID, toDateMillis(firstNonBlank(value(main, "rq", "applyDate"), value(main, "xmfxdocdt"))));
         putIfNotBlank(formData, FORM_TITLE_FIELD_ID, title);
@@ -154,6 +161,17 @@ public class CustomerReleaseFormPushHandler implements YidaFormPushHandler {
             formData.put(DETAIL_TABLE_FIELD_ID, rows);
         }
         copyYidaFieldValues(main, formData);
+        Map<String, Object> departmentConfiguration = mingleiDocumentService.queryApplicantDepartmentConfiguration(operatorUserId);
+        // 公司和负责人以申请人的部门配置为准,避免 ERP 透传字段覆盖查询结果。
+        formData.remove(COMPANY_FIELD_ID);
+        formData.remove(FIRST_LEVEL_MANAGER_FIELD_ID);
+        formData.remove(SECOND_LEVEL_MANAGER_FIELD_ID);
+        formData.remove(THIRD_LEVEL_MANAGER_FIELD_ID);
+        putIfNotBlank(formData, DEPARTMENT_FIELD_ID, toStringList(departmentConfiguration.get("departmentId"), null));
+        putIfNotBlank(formData, COMPANY_FIELD_ID, departmentConfiguration.get("company"));
+        putIfNotBlank(formData, FIRST_LEVEL_MANAGER_FIELD_ID, toStringList(departmentConfiguration.get("firstLevelManagerUserIds"), null));
+        putIfNotBlank(formData, SECOND_LEVEL_MANAGER_FIELD_ID, toStringList(departmentConfiguration.get("secondLevelManagerUserIds"), null));
+        putIfNotBlank(formData, THIRD_LEVEL_MANAGER_FIELD_ID, toStringList(departmentConfiguration.get("thirdLevelManagerUserIds"), null));
         return formData;
     }
 

+ 40 - 2
src/main/java/com/malk/minglei/service/impl/yida/MingleiDocumentServiceImpl.java

@@ -32,6 +32,7 @@ public class MingleiDocumentServiceImpl implements MingleiDocumentService {
     private static final String MINGLEI_DOCUMENT_FORM_UUID = "FORM-8F527F696C5B4AF7AB7A53979527FBA23USO";
     private static final String DEPARTMENT_ID_FIELD_ID = "textField_msztwhs2";
     private static final String DEPARTMENT_NAME_FIELD_ID = "textField_mszr6tuz";
+    private static final String APPLICATION_COMPANY_FIELD_ID = "textField_mtgmtiwb";
     private static final String FIRST_LEVEL_DEPARTMENT_MANAGER_FIELD_ID = "employeeField_mszdqrl8";
     private static final String SECOND_LEVEL_DEPARTMENT_MANAGER_FIELD_ID = "employeeField_mszdqrl9";
     private static final String THIRD_LEVEL_DEPARTMENT_MANAGER_FIELD_ID = "employeeField_mszdqrla";
@@ -135,6 +136,30 @@ public class MingleiDocumentServiceImpl implements MingleiDocumentService {
                 1, PRECISE_QUERY_PAGE_SIZE));
     }
 
+    /**
+     * 读取申请人主部门(未设置时取首个所属部门)的申请公司和负责人配置。
+     *
+     * @param userId 申请人的钉钉用户 ID
+     * @return 部门 ID、申请公司及一至三级负责人的用户 ID 数组;缺失配置或负责人允许为空
+     */
+    @Override
+    public Map<String, Object> queryApplicantDepartmentConfiguration(String userId) {
+        String requestedUserId = trimToNull(userId);
+        require(requestedUserId, "userId must not be blank");
+        String departmentId = resolveDepartmentId(requestedUserId);
+        Map<String, Object> queryResult = executeQuery(DEPARTMENT_ID_FIELD_ID, departmentId, 1, PRECISE_QUERY_PAGE_SIZE);
+        Map formData = firstFormData(queryResult.get("records"));
+        Map<String, Object> result = new LinkedHashMap<String, Object>();
+        result.put("departmentId", departmentId);
+        if (formData != null) {
+            result.put("company", toStringValue(formData.get(APPLICATION_COMPANY_FIELD_ID)));
+            result.put("firstLevelManagerUserIds", employeeUserIds(formData.get(FIRST_LEVEL_DEPARTMENT_MANAGER_FIELD_ID + "_id")));
+            result.put("secondLevelManagerUserIds", employeeUserIds(formData.get(SECOND_LEVEL_DEPARTMENT_MANAGER_FIELD_ID + "_id")));
+            result.put("thirdLevelManagerUserIds", employeeUserIds(formData.get(THIRD_LEVEL_DEPARTMENT_MANAGER_FIELD_ID + "_id")));
+        }
+        return result;
+    }
+
     /**
      * 按查询范围读取部门成员 ID,并按成员首次出现顺序去重。
      *
@@ -272,11 +297,24 @@ public class MingleiDocumentServiceImpl implements MingleiDocumentService {
     }
 
     /**
-     * 从宜搭成员字段对应的 ID 数组中提取首个钉钉 userid。
+     * 从宜搭成员 ID 字段中提取全部钉钉 userid,并保序去重。
      *
      * @param employeeIds 宜搭成员字段对应的 ID 数组
-     * @return 钉钉 userid;字段未填写或无法识别时为 null
+     * @return 钉钉 userid 列表;字段未填写或无法识别时为空列表
      */
+    private List<String> employeeUserIds(Object employeeIds) {
+        Iterable<?> employees = employeeIds instanceof Iterable
+                ? (Iterable<?>) employeeIds : Collections.singletonList(employeeIds);
+        Set<String> userIds = new LinkedHashSet<String>();
+        for (Object employee : employees) {
+            String userId = firstEmployeeUserId(employee);
+            if (userId != null) {
+                userIds.add(userId);
+            }
+        }
+        return new ArrayList<String>(userIds);
+    }
+
     private String firstEmployeeUserId(Object employeeIds) {
         Object employee = firstValue(employeeIds);
         if (employee == null) {

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

@@ -35,6 +35,14 @@ public interface MingleiDocumentService {
      */
     Map<String, Object> queryDepartmentManagersByDepartmentId(String departmentId);
 
+    /**
+     * 按申请人的钉钉所属部门查询申请公司和一至三级负责人。
+     *
+     * @param userId 申请人的钉钉用户 ID
+     * @return departmentId、company 及 first/second/thirdLevelManagerUserIds;查无配置时仅返回部门 ID
+     */
+    Map<String, Object> queryApplicantDepartmentConfiguration(String userId);
+
     /**
      * 按查询范围获取指定部门中的钉钉用户 ID。
      *

+ 192 - 0
src/test/java/com/malk/minglei/service/impl/yida/CustomerReleaseDepartmentConfigurationTest.java

@@ -0,0 +1,192 @@
+package com.malk.minglei.service.impl.yida;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.malk.minglei.config.YidaApplicationsProperties;
+import com.malk.minglei.dto.ErpYidaPushMainData;
+import com.malk.minglei.dto.ErpYidaPushRequest;
+import com.malk.minglei.service.yida.MingleiDocumentService;
+import com.malk.minglei.service.yida.YidaProcessOpenApiClient;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.server.dingtalk.DDR_New;
+import com.malk.service.aliwork.YDClient;
+import com.malk.service.dingtalk.DDClient;
+import com.malk.service.dingtalk.DDClient_Contacts;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.ArgumentCaptor;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class CustomerReleaseDepartmentConfigurationTest {
+
+    @Test
+    void usesMainDepartmentConfigurationAndMemberIdsInsteadOfErpValues() {
+        Map<String, Object> user = new LinkedHashMap<String, Object>();
+        user.put("main_dept_id", 20L);
+        user.put("dept_id_list", Arrays.asList(10L, 20L));
+        Map<String, Object> configuration = new LinkedHashMap<String, Object>();
+        configuration.put("textField_mtgmtiwb", "Configured Company");
+        configuration.put("employeeField_mszdqrl8", Collections.singletonList("Display Name"));
+        configuration.put("employeeField_mszdqrl8_id", Arrays.asList("manager-1", "manager-1b"));
+        configuration.put("employeeField_mszdqrl9_id", Collections.singletonList(Collections.singletonMap("userId", "manager-2")));
+        configuration.put("employeeField_mszdqrla_id", Collections.singletonList("manager-3"));
+        try (Fixture fixture = new Fixture(user, configuration)) {
+            JSONObject form = fixture.start();
+            assertEquals("Configured Company", form.getString("selectField_mtv7uxgd"));
+            assertEquals(Collections.singletonList("20"), form.getJSONArray("departmentSelectField_msh186ry"));
+            assertEquals(Arrays.asList("manager-1", "manager-1b"), form.getJSONArray("employeeField_mt6thsdh"));
+            assertEquals(Collections.singletonList("manager-2"), form.getJSONArray("employeeField_mt6thsdi"));
+            assertEquals(Collections.singletonList("manager-3"), form.getJSONArray("employeeField_mt6v6alx"));
+            fixture.verifyDepartmentQuery("20");
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {"department", "dept_id_list"})
+    void fallsBackToFirstDepartmentWhenNoMainDepartmentIsPresent(String departmentKey) {
+        Map<String, Object> configuration = Collections.<String, Object>singletonMap("textField_mtgmtiwb", "Configured Company");
+        try (Fixture fixture = new Fixture(Collections.<String, Object>singletonMap(departmentKey,
+                Arrays.asList(10L, 20L)), configuration)) {
+            assertEquals("Configured Company", fixture.start().getString("selectField_mtv7uxgd"));
+            fixture.verifyDepartmentQuery("10");
+        }
+    }
+
+    @Test
+    void startsWhenSomeManagerLevelsAreEmpty() {
+        Map<String, Object> configuration = new LinkedHashMap<String, Object>();
+        configuration.put("textField_mtgmtiwb", "Configured Company");
+        configuration.put("employeeField_mszdqrl8_id", Collections.singletonList("manager-1"));
+        configuration.put("employeeField_mszdqrl9_id", Collections.emptyList());
+        try (Fixture fixture = new Fixture(Collections.<String, Object>singletonMap("main_dept_id", 20L), configuration)) {
+            JSONObject form = fixture.start();
+            assertEquals(Collections.singletonList("manager-1"), form.getJSONArray("employeeField_mt6thsdh"));
+            assertFalse(form.containsKey("employeeField_mt6thsdi"));
+            assertFalse(form.containsKey("employeeField_mt6v6alx"));
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {true, false})
+    void startsWithoutCompanyOrManagersWhenConfigurationIsMissingOrEmpty(boolean noRecord) {
+        Map<String, Object> configuration = noRecord ? null : new LinkedHashMap<String, Object>();
+        if (configuration != null) {
+            configuration.put("textField_mtgmtiwb", " ");
+            configuration.put("employeeField_mszdqrl8_id", Collections.emptyList());
+            configuration.put("employeeField_mszdqrl9", Collections.singletonList("Display Name Only"));
+        }
+        try (Fixture fixture = new Fixture(Collections.<String, Object>singletonMap("main_dept_id", 20L), configuration)) {
+            JSONObject form = fixture.start();
+            assertFalse(form.containsKey("selectField_mtv7uxgd"));
+            assertFalse(form.containsKey("employeeField_mt6thsdh"));
+            assertFalse(form.containsKey("employeeField_mt6thsdi"));
+            assertFalse(form.containsKey("employeeField_mt6v6alx"));
+        }
+    }
+
+    @Test
+    void doesNotStartWhenDepartmentConfigurationQueryFails() {
+        try (Fixture fixture = new Fixture(Collections.<String, Object>singletonMap("main_dept_id", 20L), null)) {
+            when(fixture.masterDataClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_form)))
+                    .thenThrow(new IllegalStateException("Master data unavailable"));
+            assertThrows(IllegalStateException.class, () -> fixture.handler.push(fixture.request));
+            verify(fixture.processClient, never()).startProcess(anyMap());
+        }
+    }
+
+    private static class Fixture implements AutoCloseable {
+        private final YDClient masterDataClient = mock(YDClient.class);
+        private final YidaProcessOpenApiClient processClient = mock(YidaProcessOpenApiClient.class);
+        private final DDClient_Contacts contactsClient = mock(DDClient_Contacts.class);
+        private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+        private final ErpYidaPushRequest request = new ErpYidaPushRequest();
+        private final CustomerReleaseFormPushHandler handler;
+
+        private Fixture(Map<String, Object> user, Map<String, Object> configuration) {
+            DDClient dingTalkClient = mock(DDClient.class);
+            when(dingTalkClient.getAccessToken()).thenReturn("access-token");
+            when(contactsClient.getUserInfoById("access-token", "applicant-1")).thenReturn(user);
+            DDConf ddConf = new DDConf();
+            ddConf.setAppKey("app-key");
+            ddConf.setAppSecret("app-secret");
+            YidaApplicationsProperties applications = new YidaApplicationsProperties();
+            applications.getCoordinationOffice().setAppType("APP_RJJL69QUIZVQSV7YC8TL");
+            applications.getCoordinationOffice().setSystemToken("office-token");
+            applications.getMasterData().setAppType("APP_KI0AOTH7OLFS2UAUHZXI");
+            applications.getMasterData().setSystemToken("master-data-token");
+            DDR_New queryResponse = mock(DDR_New.class);
+            when(queryResponse.getRecords()).thenReturn(configuration == null ? Collections.emptyList()
+                    : Collections.singletonList(Collections.singletonMap("formData", configuration)));
+            when(masterDataClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_form)))
+                    .thenReturn(queryResponse);
+            DDR_New startResponse = new DDR_New();
+            startResponse.setResult("process-1");
+            when(processClient.startProcess(anyMap())).thenReturn(startResponse);
+            MingleiDocumentService documentService = new MingleiDocumentServiceImpl(masterDataClient,
+                    dingTalkClient, contactsClient, ddConf, applications, new ObjectMapper());
+            context.registerBean(YidaProcessOpenApiClient.class, () -> processClient);
+            context.registerBean(DDConf.class, () -> ddConf);
+            context.registerBean(YidaApplicationsProperties.class, () -> applications);
+            context.registerBean(MingleiDocumentService.class, () -> documentService);
+            context.registerBean(CustomerReleaseFormPushHandler.class);
+            context.refresh();
+            handler = context.getBean(CustomerReleaseFormPushHandler.class);
+            ErpYidaPushMainData main = new ErpYidaPushMainData();
+            main.setExtraField("xmfxdocno", "CR-001");
+            main.setExtraField("selectField_mtv7uxgd", "Wrong ERP Company");
+            main.setExtraField("employeeField_mt6thsdh", Collections.singletonList("wrong-manager"));
+            main.setExtraField("employeeField_mt6thsdi", Collections.singletonList("wrong-manager"));
+            main.setExtraField("employeeField_mt6v6alx", Collections.singletonList("wrong-manager"));
+            request.setType("axmt140");
+            request.setOriginatorUserId("applicant-1");
+            request.setGs("ERP Company");
+            request.setBm("ERP Department");
+            request.setMainData(main);
+        }
+
+        @SuppressWarnings("rawtypes")
+        private JSONObject start() {
+            assertEquals("process-1", handler.push(request));
+            ArgumentCaptor<Map> body = ArgumentCaptor.forClass(Map.class);
+            verify(processClient).startProcess(body.capture());
+            assertEquals("applicant-1", body.getValue().get("userId"));
+            return JSONObject.parseObject(String.valueOf(body.getValue().get("formDataJson")));
+        }
+
+        private void verifyDepartmentQuery(String departmentId) {
+            verify(contactsClient).getUserInfoById("access-token", "applicant-1");
+            ArgumentCaptor<YDParam> parameter = ArgumentCaptor.forClass(YDParam.class);
+            verify(masterDataClient).queryData(parameter.capture(), eq(YDConf.FORM_QUERY.retrieve_search_form));
+            assertEquals("APP_KI0AOTH7OLFS2UAUHZXI", parameter.getValue().getAppType());
+            assertEquals("FORM-8F527F696C5B4AF7AB7A53979527FBA23USO", parameter.getValue().getFormUuid());
+            JSONObject filter = JSONObject.parseArray(parameter.getValue().getSearchFieldJson()).getJSONObject(0);
+            assertEquals("textField_msztwhs2", filter.getString("key"));
+            assertEquals(departmentId, filter.getString("value"));
+            assertEquals("eq", filter.getString("operator"));
+        }
+
+        @Override
+        public void close() {
+            context.close();
+        }
+    }
+}

+ 7 - 1
src/test/java/com/malk/minglei/service/impl/yida/CustomerReleaseFormPushHandlerTest.java

@@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.malk.minglei.config.YidaApplicationsProperties;
 import com.malk.minglei.dto.ErpYidaPushMainData;
 import com.malk.minglei.dto.ErpYidaPushRequest;
+import com.malk.minglei.service.yida.MingleiDocumentService;
 import com.malk.minglei.service.yida.YidaProcessOpenApiClient;
 import com.malk.server.dingtalk.DDR_New;
 import com.malk.server.dingtalk.DDConf;
@@ -72,7 +73,12 @@ class CustomerReleaseFormPushHandlerTest {
         request.setMainData(mainData);
         request.setDetailData(detailData);
 
-        CustomerReleaseFormPushHandler handler = new CustomerReleaseFormPushHandler(yidaClient, ddConf, applications);
+        MingleiDocumentService documentService = mock(MingleiDocumentService.class);
+        Map<String, Object> departmentConfiguration = new LinkedHashMap<String, Object>();
+        departmentConfiguration.put("departmentId", "dept-001");
+        departmentConfiguration.put("company", "ML");
+        when(documentService.queryApplicantDepartmentConfiguration("41603502201288023")).thenReturn(departmentConfiguration);
+        CustomerReleaseFormPushHandler handler = new CustomerReleaseFormPushHandler(yidaClient, ddConf, applications, documentService);
 
         assertEquals(Collections.singletonList("axmt140"), handler.getTypes());
         assertEquals("process-instance-1", handler.push(request));