pruple_boy 1 rok temu
rodzic
commit
3b41eaf3c2

+ 38 - 3
mjava-aiwei/src/main/java/com/malk/aiwei/controller/TBxYDController.java

@@ -84,6 +84,34 @@ public class TBxYDController {
         return McR.success(awClint.doCheck(taskId, false));
     }
 
+    /**
+     * 通过模板创建项目
+     */
+    @PostMapping("project/create")
+    McR createProject(HttpServletRequest request) {
+
+        Map<String, ?> data = UtilServlet.getParamMap(request);
+        log.info("通过模板创建项目, {}", data);
+        McException.assertParamException_Null(data, "projectCode, templateId");
+
+        awClint.createProject(UtilMap.getString(data, "projectCode"), UtilMap.getString(data, "templateId"));
+        return McR.success();
+    }
+    
+    /**
+     * 分配项目角色
+     */
+    @PostMapping("project/role")
+    McR updateRoleProject(HttpServletRequest request) {
+
+        Map<String, ?> data = UtilServlet.getParamMap(request);
+        log.info("分配项目角色, {}", data);
+        McException.assertParamException_Null(data, "projectId");
+
+        awClint.updateProjectRole(UtilMap.getString(data, "projectId"));
+        return McR.success();
+    }
+
     //////// test ////////
 
     @Autowired
@@ -97,11 +125,18 @@ public class TBxYDController {
 
 
     @GetMapping("t/task")
-    McR tb(String taskId) {
+    McR task(String taskId) {
 
         log.info("taskId, {}", taskId);
-        List<Map> taskList = tbClient.queryTaskDetail(taskId, "", "");
-        return McR.success(taskList);
+        List<Map> tList = tbClient.queryTaskDetail(taskId, "", "");
+        return McR.success(tList);
+    }
+
+    @GetMapping("t/template")
+    McR template(String name) {
+
+        List<Map> tList = tbClient.templateSearch(null, name);
+        return McR.success(tList);
     }
 
     @PostMapping("test")

+ 6 - 1
mjava-aiwei/src/main/java/com/malk/aiwei/server/AWServer.java

@@ -18,11 +18,16 @@ public class AWServer {
 
     public static final String TASK_CHECK_LINK = "预检项";
     public static final String TASK_CHECK_STATUS = "预检项检查状态";
+    public static final String TASK_TIPS = "未配置预检项";
 
     public static final String TASK_APPROVE_ATTACHMENT = "交付件";
     public static final String TASK_APPROVE_LINK = "交付件审批流程";
 
-    public static final String WORKFLOW_APPROVE = "已完成";
+    // ppExt: 注意不同任务类型名称唯一性, 如通用已完成不要使用
+    public static final String WORKFLOW_APPROVE = "已提交";
+
+    public static final String PROJECT_PM_ROLE = "项目管理员";
+    public static final String PROJECT_PM_NAME = "项目经理";
 
     /**
      * 艾为网关接口

+ 15 - 0
mjava-aiwei/src/main/java/com/malk/aiwei/service/AWClint.java

@@ -23,4 +23,19 @@ public interface AWClint {
      * 检查项回调
      */
     void checked(Map data);
+
+    /**
+     * 同步项目主数据
+     */
+    Map syncProject(String projectCode);
+
+    /**
+     * 通过模板创建项目
+     */
+    void createProject(String projectCode, String templateId);
+
+    /**
+     * 分配项目角色 prd 若是一人直接指定, 多人情况下忽略
+     */
+    void updateProjectRole(String projectId);
 }

+ 225 - 3
mjava-aiwei/src/main/java/com/malk/aiwei/service/impl/AWImplClient.java

@@ -215,7 +215,7 @@ public class AWImplClient implements AWClint {
         Map taskData = _getTaskFieldMap(taskId, AWServer.TASK_CODE, AWServer.TASK_CHECK_LINK, AWServer.TASK_ROLE, AWServer.TASK_STAGE);
         String checkLink = UtilMap.getString(taskData, AWServer.TASK_CHECK_LINK);
         /// prd 预检项持续维护需要重新匹配, 任务号和主数据判定为非法, 无需重新刷新
-        if (isTask && (StringUtils.isNotBlank(checkLink) && !"未配置预检项".equals(checkLink))) {
+        if (isTask && (StringUtils.isNotBlank(checkLink) && !AWServer.TASK_TIPS.equals(checkLink))) {
             return UtilMap.map("result", "链接已加载");
         }
         Map rTask = UtilMap.getMap(taskData, "task");
@@ -235,7 +235,7 @@ public class AWImplClient implements AWClint {
                     .searchFieldJson(JSON.toJSONString(UtilMap.map("selectField_lrncf4hk, radioField_lrnddfq6", tCode, "启用")))
                     .build());
             if (tList.size() == 0) {
-                result = "未配置预检项";
+                result = AWServer.TASK_TIPS;
             } else {
 //                List<Map> pList = ydService.queryDataList_FormData("FORM-84EF78C7DBA047E58A8C8511106F91D5WNVI", UtilMap.map("textField_lqxtykce", pCode));
                 List<Map> pList = ydService.queryDataList_FormData("FORM-141E21DF183846028E21727CE43CD1C75CLZ", UtilMap.map("textField_lqxtykce", pCode));
@@ -254,6 +254,11 @@ public class AWImplClient implements AWClint {
                 Map body = TBConf.assembleCustomFieldName(AWServer.TASK_CHECK_LINK, result);
                 tbClient.updateTaskCustomField(taskId, creatorId, body);
             }
+            // prd 未配置预检项更新为已检查, 避免完成任务完成触发必填校验
+            if (AWServer.TASK_TIPS.equals(result)) {
+                Map body = TBConf.assembleCustomFieldName(AWServer.TASK_CHECK_STATUS, "已检查");
+                tbClient.updateTaskCustomField(taskId, creatorId, body);
+            }
             return UtilMap.map("result", result);
         }
         McException.assertAccessException(ObjectUtil.isNull(rProject), result);
@@ -265,7 +270,11 @@ public class AWImplClient implements AWClint {
         Map<String, String> extra = (Map) tbClient.idMapQuery(creatorId, "dingTalk-user", ddConf.getCorpId()).get(0).get("extra");
         formData.putAll(UtilMap.map("textField_lrndwu09, textField_lrndwu0a, textField_lrndwu0b, employeeField_lrndwu0e", rTask.get("content"), taskData.get(AWServer.TASK_STAGE), taskData.get(AWServer.TASK_ROLE), Arrays.asList(UtilMap.map("value, name", extra.get("userId"), extra.get("userId")))));
         // 返回检查项
-        formData.put("tableField_lqxxgj4s", tList);
+        formData.put("tableField_lqxxgj4s", tList.stream().map(item -> {
+//            item.put("associationFormField_lrrsitxu", YDConf.associationForm("APP_H7WUJTKB448F9IBDC6C4", "FORM-7B63BB056145452F8BC0A2C52492DE00QVBH", UtilMap.getString(item, "instanceId"), UtilMap.getString(item, "textField_lrnd3h0r"), "", false));
+            item.put("associationFormField_lrrnem5r", YDConf.associationForm("APP_R5EBUF2FPN3Y8DRF93M4", "FORM-6E2C0D1197264B8AA23EB3FECAE7344B00BN", UtilMap.getString(item, "instanceId"), UtilMap.getString(item, "textField_lrnd3h0r"), "", false));
+            return item;
+        }).collect(Collectors.toList()));
         return formData;
     }
 
@@ -284,4 +293,217 @@ public class AWImplClient implements AWClint {
         Map body2 = TBConf.assembleCustomFieldName(AWServer.TASK_CHECK_LINK, result);
         tbClient.updateTaskCustomField(String.valueOf(data.get("taskId")), String.valueOf(data.get("creatorId")), body2);
     }
+
+    /**
+     * 同步项目主数据 [兼容新增]
+     */
+    @Override
+    public Map syncProject(String projectCode) {
+
+        // 新增场景下, 提供实例ID回写TB项目编号
+        Map formData = null;
+
+        // fixme: test生产环境, 人员组件不能同步, 更新后人员会为空. todo 测试环境建立模拟项目主数据单据
+        // 查询项目主数据 [艾为应用]
+        YDParam ydParam = YDParam.builder()
+                .appType("APP_QBWQITQBSPJNYTUTNPDK")
+                .systemToken("8F966HB12J27MQJM6V4IQDYHYTPA2G4GTZGCLN1")
+                .formUuid("FORM-Q4A664A1V158R81L662N9507PLND288T7O6ELN")
+                .build();
+        if (StringUtils.isNotBlank(projectCode)) {
+            ydParam.setSearchFieldJson(JSON.toJSONString(UtilMap.map("textField_le6o88w0", projectCode)));
+        }
+        List<Map> mapBaseList = ydService.queryAllFormData(ydParam);
+        log.info("项目主数据, {}", mapBaseList.size());
+        McException.assertAccessException(mapBaseList.isEmpty(), "未匹配项目主数据");
+
+        // 查询项目角色
+        YDParam ydParam1 = YDParam.builder()
+//                .formUuid("FORM-5BE21392886E46DF955D1EBC100ADA429NON")
+                .formUuid("FORM-3C7396A12ADB48A8833EBD90089C93833R21")
+                .build();
+        List<Map> mapRool = (List<Map>) ydClient.queryData(ydParam1, YDConf.FORM_QUERY.retrieve_list).getData();
+
+        // 匹配数据组件ID
+        for (Map item : mapBaseList) {
+            Map baseFormData = (Map) item.get("formData");
+
+            // 项目主数据
+            formData = new HashMap();
+            formData.put("textField_lqxtykcd", baseFormData.get("textField_lo2c1f0l"));      //项目名称
+            formData.put("textField_lrj7vnxb", baseFormData.get("textField_le6o88w0"));      //项目号
+            formData.put("textField_lrj7vnxc", baseFormData.get("textField_le6o88w1"));      //项目简称
+            formData.put("textareaField_lrj7vnxl", baseFormData.get("textField_le6o88w2"));  //项目描述
+            formData.put("textField_lrj7vnxf", item.get("formInstanceId"));                  //项目实例ID
+
+            // 项目角色详情
+            List<Map> details = new ArrayList<>();
+            mapRool.forEach(rool -> {
+                Map roolFormData = (Map) rool.get("formData");
+                //创建详情的角色名称和成员组件Map
+                Map row = new HashMap();
+//                Object roolItems = baseFormData.get(roolFormData.get("textField_lrj7t5mn"));
+//                Object projectRool = roolFormData.get("textField_lqxu439g");
+                Object roolItems = baseFormData.get(roolFormData.get("textField_lr7bgi76"));
+                Object projectRool = roolFormData.get("textField_lqxu439g");
+
+                if (roolItems != null && projectRool != null) {
+//                    row.put("employeeField_lqxtykch", baseFormData.get(roolFormData.get("textField_lrj7t5mn")));
+//                    row.put("selectField_lqxu6bgf", roolFormData.get("textField_lqxu439g"));
+                    row.put("employeeField_lqxtykch", baseFormData.get(roolFormData.get("textField_lr7bgi76")));
+                    row.put("selectField_lqxu6bgf", roolFormData.get("textField_lqxu439g"));
+                    details.add(row);
+                }
+            });
+            formData.put("tableField_lqxtykcf", details);
+
+            //通过项目号获取项目是否已存在
+            YDParam ydParam2 = YDParam.builder()
+//                    .formUuid("FORM-84EF78C7DBA047E58A8C8511106F91D5WNVI")
+                    .formUuid("FORM-141E21DF183846028E21727CE43CD1C75CLZ")
+                    .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_lrj7vnxb", baseFormData.get("textField_le6o88w0"))))
+                    .build();
+            List<Map> projectMaps = (List<Map>) ydClient.queryData(ydParam2, YDConf.FORM_QUERY.retrieve_search_form).getData();
+            YDParam ydParam3 = YDParam.builder()
+//                    .formUuid("FORM-84EF78C7DBA047E58A8C8511106F91D5WNVI")
+                    .formUuid("FORM-141E21DF183846028E21727CE43CD1C75CLZ")
+                    .formDataJson(JSON.toJSONString(formData))
+                    .updateFormDataJson(JSON.toJSONString(formData))
+                    .build();
+            if (projectMaps.size() > 0) {
+                ydParam3.setFormInstanceId(String.valueOf(projectMaps.get(0).get("formInstanceId")));
+                ydClient.operateData(ydParam3, YDConf.FORM_OPERATION.update);
+                // 保留tb项目号, 避免冲重复通过模板参加项目
+                formData.put("textField_lqxtykce", UtilMap.getMap(projectMaps.get(0), "formData").get("textField_lqxtykce"));
+            } else {
+                String formInstId = (String) ydClient.operateData(ydParam3, YDConf.FORM_OPERATION.create);
+                formData.put("formInstanceId", formInstId);
+            }
+            log.info("同步项目主数据", projectCode);
+        }
+        return formData;
+    }
+
+
+    @Autowired
+    private TBConf tbConf;
+
+    /**
+     * 通过模板创建项目 [ppExt: 过滤宜搭项目档案\TB项目, 避免重复创建, 若判定为更新下执行项目成员更新]
+     */
+    @Override
+    public void createProject(String projectCode, String templateId) {
+
+        log.info("通过模板创建项目, {}, {}", projectCode, templateId);
+
+        // 虽然先创建TB, 可以一次性同步项目数据, 但避免执行异常, 因此成功创建TB项目再回写
+        Map formData = syncProject(projectCode);
+        log.info("项目主数据, {}", formData);
+
+        String projectId = UtilMap.getString(formData, "textField_lqxtykce");
+        if (StringUtils.isBlank(projectId)) {
+            // 通过模板创建项目, 创建项目并更新项目TB项目ID
+            Map result = tbClient.projectCreateWithTemplate(projectCode, templateId, tbConf.getOperatorId());
+            projectId = UtilMap.getString(result, "id");
+            ydClient.operateData(YDParam.builder()
+                    .formInstanceId(UtilMap.getString(formData, "formInstanceId"))
+                    .updateFormDataJson(JSON.toJSONString(UtilMap.map("textField_lqxtykce", projectId)))
+                    .build(), YDConf.FORM_OPERATION.update);
+        }
+        // prd TB项目成员, 项目经理设置为管理者
+        List<String> pmUserId = new ArrayList<>();
+        List<String> roleIds = new ArrayList<>();
+        List<Map> details = (List<Map>) formData.get("tableField_lqxtykcf");
+        for (Map row : details) {
+            // 同步项目主数据, 会重新匹配项目成员, 因此无需 employeeField_xxx_id 取值
+            List<String> userIds = (List<String>) UtilMap.getList(row, "employeeField_lqxtykch");
+            if (userIds.isEmpty()) {
+                continue;
+            }
+            if (AWServer.PROJECT_PM_NAME.equals(row.get("selectField_lqxu6bgf"))) {
+                pmUserId.addAll(userIds);
+            }
+            roleIds.addAll(userIds);
+        }
+        // 钉钉人员ID转TB人员ID
+        roleIds = _convertUserId(roleIds, false);
+        tbClient.createProjectMember(projectId, roleIds, tbConf.getOperatorId());
+        // todo 删除项目成员
+
+        // 指定项目经理为编辑权限
+        if (!pmUserId.isEmpty()) {
+            tbClient.updateProjectMember(_convertUserId(pmUserId, false), Arrays.asList(_getProjectRoleId(projectId, AWServer.PROJECT_PM_ROLE)), projectId);
+        }
+        log.info("TB项目信息, {}, {}", projectId, roleIds.size());
+    }
+
+    // todo 提取方法, 并封装为 ...roleName
+    private String _getProjectRoleId(String projectId, String roleName) {
+        List<Map> roles = tbClient.queryProjectRoles(projectId);
+        Optional optional = roles.stream().filter(item -> roleName.equals(item.get("name"))).findAny();
+        McException.assertAccessException(!optional.isPresent(), roleName + ": 项目角色不存在");
+        return UtilMap.getString((Map) optional.get(), "id");
+    }
+
+    /**
+     * 分配项目角色 prd 若是一人直接指定, 多人情况下忽略
+     */
+    @Override
+    public void updateProjectRole(String projectId) {
+
+        // todo 1000递归查询
+        // 项目档案
+//        List<Map> pList = ydService.queryDataList_FormData("FORM-84EF78C7DBA047E58A8C8511106F91D5WNVI", UtilMap.map("textField_lqxtykce", projectId));
+        List<Map> pList = ydService.queryDataList_FormData("FORM-141E21DF183846028E21727CE43CD1C75CLZ", UtilMap.map("textField_lqxtykce", projectId));
+        McException.assertAccessException(pList.isEmpty(), "未匹配到项目主数据");
+        List<Map> rList = (List<Map>) pList.get(0).get("tableField_lqxtykcf");
+
+        // 任务编码字段ID
+        List<Map> customField = tbClient.queryProjectCustomField(projectId, null);
+
+        List<Map> taskList = tbClient.queryProjectTaskList(projectId, null);
+        for (Map task : taskList) {
+
+            // 获取资源名称对应的项目角色
+            Optional optional = customField.stream().filter(item -> AWServer.TASK_ROLE.equals(item.get("name"))).findAny();
+            if (!optional.isPresent()) {
+                continue;
+            }
+            List<Map> customfields = (List<Map>) task.get("customfields");
+            String roleName = TBConf.getTaskFieldValue_First(customfields, UtilMap.getString((Map) optional.get(), "id"));
+            // 获取角色在项目主数据对应成员
+            optional = rList.stream().filter(item -> roleName.equals(item.get("selectField_lqxu6bgf"))).findAny();
+            if (!optional.isPresent()) {
+                continue;
+            }
+            List<String> roleIds = (List<String>) UtilMap.getList((Map) optional.get(), "employeeField_lqxtykch_id");
+            if (roleIds.size() == 1) {
+                tbClient.updateTaskExecutor(UtilMap.getString(task, "id"), tbConf.getOperatorId(), _convertUserId(roleIds.get(0), false), false, false);
+            }
+        }
+    }
+
+    // TB与宜搭userId转换 todo 提取 tbService
+    private String _convertUserId(String userId, boolean isTBID) {
+        List<Map> tbMap = tbClient.idMapQuery(userId, isTBID);
+        // 过滤未匹配人员信息 [ppExt: TB人员未匹配, 不执行修改也无报错]
+        if (tbMap.isEmpty()) {
+            return "";
+        }
+        if (!isTBID) {
+            return UtilMap.getString(tbMap.get(0), "tbId");
+        } else {
+            Map extra = UtilMap.getMap(tbMap.get(0), "extra");
+            return UtilMap.getString(extra, "userId");
+        }
+    }
+
+    // TB与宜搭userId转换 todo 提取 tbService
+    private List<String> _convertUserId(List<String> userIds, boolean isTBID) {
+        return userIds.stream().distinct()
+                .map(id -> _convertUserId(id, isTBID))
+                .filter(id -> StringUtils.isNotBlank(id))
+                .collect(Collectors.toList());
+    }
 }
+

+ 7 - 7
mjava-aiwei/src/main/resources/application-dev.yml

@@ -58,9 +58,9 @@ dingtalk:
   operator: ""   # OA管理员账号
 
 # aliwork
-aliwork:
-  appType: "APP_H7WUJTKB448F9IBDC6C4"
-  systemToken: "DHA66081DN6GRFNC6GTRW5NIJS082ZF0UN9PLLF"
+#aliwork:
+#  appType: "APP_H7WUJTKB448F9IBDC6C4"
+#  systemToken: "DHA66081DN6GRFNC6GTRW5NIJS082ZF0UN9PLLF"
 
 # teambition
 teambition:
@@ -79,8 +79,8 @@ teambition:
 #  token:
 #  operator: ""   # OA管理员账号
 #
-## aliwork
-#aliwork:
-#  appType: "APP_R5EBUF2FPN3Y8DRF93M4"
-#  systemToken: "ON566NC1VNIHPANP9TNVHB3TBIWS3E0TUZ5RLF3"
+# aliwork
+aliwork:
+  appType: "APP_R5EBUF2FPN3Y8DRF93M4"
+  systemToken: "ON566NC1VNIHPANP9TNVHB3TBIWS3E0TUZ5RLF3"
 

Plik diff jest za duży
+ 838 - 853
mjava-gewu/src/main/resources/static/json/personnel.json


+ 4 - 1
mjava/src/main/java/com/malk/server/teambition/TBR.java

@@ -21,11 +21,14 @@ public class TBR<T> extends VenR {
     // 成功状态标记
     private final static String SUC_CODE = "200";
 
+    // ppExt: 204为重复修改或tb设置了不允许修改规则
+    private final static String NOR_CODE = "204";
+
     /**
      * 断言错误信息
      */
     @Override
     public void assertSuccess() {
-        McException.assertException(!code.equals(SUC_CODE), code, errorMessage, "TB");
+        McException.assertException(!(code.equals(SUC_CODE) || code.equals(NOR_CODE)), code, errorMessage, "TB");
     }
 }

+ 33 - 8
mjava/src/main/java/com/malk/service/teambition/TBClient.java

@@ -10,6 +10,7 @@ public interface TBClient {
      *
      * @param ptIds 项目模版ID集合,逗号组合
      * @param q     模糊查询名字
+     * @apiNote https://open.teambition.com/docs/apis/6321c6d1912d20d3b5a4a30f
      */
     List<Map> templateSearch(String ptIds, String q);
 
@@ -27,8 +28,11 @@ public interface TBClient {
      * @param refer 常见的 refer 类型: dingTalk-corp 钉钉企业 dingTalk-team 钉钉部门 dingTalk-user 钉钉用户
      * @apiNote https://open.teambition.com/docs/apis/6321c6ce912d20d3b5a48e5f
      */
+    @Deprecated
     List<Map> idMapQuery(String tbId, String refer, String refId);
 
+    List<Map> idMapQuery(String userId, boolean isTbID);
+
     /**
      * 更新企业成员
      *
@@ -45,10 +49,11 @@ public interface TBClient {
 
     /**
      * 通过模板创建项目
+     *
+     * @apiNote https://open.teambition.com/docs/apis/6321c6d0912d20d3b5a49753
      */
     Map projectCreateWithTemplate(String name, String templateId, String operatorId);
 
-
     /**
      * 查询项目
      *
@@ -66,14 +71,12 @@ public interface TBClient {
     Map projectUpdate(String projectId, Map<String, String> data, String operatorId);
 
     /**
-     * 创建项目成员
+     * 创建项目成员 [新版本] ppExt: 成员ID不能为空\不能重复, 重复创建会增量更新但不会删除成员
      *
-     * @param projectId  项目ID, 路径参数
-     * @param userIds    项目成员userId列表
-     * @param operatorId 操作人 ID
-     * @apiNote https://open.teambition.com/docs/apis/6321c6d0912d20d3b5a498c0
+     * @apiNote https://open.teambition.com/docs/apis/6363bcfa912d20d3b56faf07
      */
-    List<Map> createProjectMember(List<String> userIds, String projectId, String operatorId);
+    List<Map> createProjectMember(String projectId, List<String> userIds, String operatorId);
+
 
     /**
      * 获取项目角色列表
@@ -88,6 +91,7 @@ public interface TBClient {
      *
      * @param roleIds 角色ID列表 [通过 获取项目角色列表 获取]
      * @return { result:  { role } }   项目角色: 0=成员, 1=管理员, 2=拥有者
+     * @apiNote https://open.teambition.com/docs/apis/6321c6d0912d20d3b5a49942
      */
     List<Map> updateProjectMember(List<String> userIds, List<String> roleIds, String projectId);
 
@@ -102,6 +106,20 @@ public interface TBClient {
      */
     List<Map> queryTaskDetail(String taskId, String shortIds, String parentTaskId);
 
+    /**
+     * 搜索任务列表
+     *
+     * @apiNote https://open.teambition.com/docs/apis/6363bcfb912d20d3b56fb13f
+     */
+    List<Map> queryTaskGroupList(String projectId, Map param);
+
+    /**
+     * 查询项目任务
+     *
+     * @apiNote https://open.teambition.com/docs/apis/6321c6d1912d20d3b5a49ec1
+     */
+    List<Map> queryProjectTaskList(String projectId, Map param);
+
     /**
      * 更新任务截止时间
      *
@@ -109,6 +127,13 @@ public interface TBClient {
      */
     Map updateTaskDueDate(String taskId, String dueDate, String operatorId);
 
+    /**
+     * 更新任务执行者 [ppExt: 1. 人员未匹配, 不执行修改; 2. 非项目成员页面上不能添加, 但接口可写入]
+     *
+     * @apiNote https://open.teambition.com/docs/apis/6321c6d2912d20d3b5a4a63d
+     */
+    Map updateTaskExecutor(String taskId, String operatorId, String executorId, boolean disableActivity, boolean disableNotification);
+
     /**
      * 更新任务自定义字段值
      * -
@@ -140,7 +165,7 @@ public interface TBClient {
     List<Map> queryProjectCustomField(String projectId, Map body);
 
     /**
-     * 搜索项目工作流状态
+     * 搜索项目工作流状态 [ppExt: 注意不同任务类型名称唯一性, 如通用已完成不要使用]
      *
      * @apiNote https://open.teambition.com/api/v3/project/{projectId}/taskflowstatus/search
      */

+ 46 - 21
mjava/src/main/java/com/malk/service/teambition/impl/TBClientImpl.java

@@ -1,6 +1,7 @@
 package com.malk.service.teambition.impl;
 
 import com.auth0.jwt.algorithms.Algorithm;
+import com.malk.server.dingtalk.DDConf;
 import com.malk.server.teambition.TBConf;
 import com.malk.server.teambition.TBR;
 import com.malk.service.teambition.TBClient;
@@ -111,10 +112,8 @@ public class TBClientImpl implements TBClient {
 
     @Override
     public List<Map> projectDetail(String projectIds, String name, String sourceId) {
-        Map param = new HashMap();
-        param.put("projectIds", projectIds);
-        param.put("name", name);
-        if (StringUtils.isNotBlank(sourceId)) param.put("sourceId", sourceId);
+        Map param = UtilMap.map("projectIds, name", projectIds, name);
+        UtilMap.putNotEmpty(param, "sourceId", sourceId);
         TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/v3/project/query", initHeaderToken(), param, TBR.class);
         return (List<Map>) tbr.getResult();
     }
@@ -127,10 +126,7 @@ public class TBClientImpl implements TBClient {
 
     @Override
     public List<Map> userSearch(String query) {
-        Map param = new HashMap();
-        param.put("orgId", tbConf.getTenantId());
-        param.put("query", query);
-        param.put("pageSize", TBConf.PAGE_SIZE_LIMIT);
+        Map param = UtilMap.map("orgId, query, pageSize", tbConf.getTenantId(), query, TBConf.PAGE_SIZE_LIMIT);
         TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/org/member/search", initHeaderToken_Bearer(), param, TBR.class);
         return (List<Map>) tbr.getResult();
     }
@@ -142,6 +138,17 @@ public class TBClientImpl implements TBClient {
         return (List<Map>) tbr.getResult();
     }
 
+    @Autowired
+    private DDConf ddConf;
+
+    @Override
+    public List<Map> idMapQuery(String userId, boolean isTbID) {
+        Map param = UtilMap.map(" refer, refId", "dingTalk-user", ddConf.getCorpId());
+        param.put(isTbID ? "tbId" : "extraUserId", userId);
+        TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/idmap/query", initHeaderToken_Bearer(), param, TBR.class);
+        return (List<Map>) tbr.getResult();
+    }
+
     @Override
     public Map updateUser(String operatorId, List<Map> members) {
         Map body = new HashMap();
@@ -153,25 +160,24 @@ public class TBClientImpl implements TBClient {
     }
 
     @Override
-    public List<Map> createProjectMember(List<String> userIds, String projectId, String operatorId) {
-        Map body = new HashMap();
-        body.put("userIds", userIds);
-        TBR tbr = (TBR) UtilHttp.doPost(tbConf.getApiHost() + "/v3/project/".concat(projectId).concat("/member/create"), initHeaderToken(operatorId), body, TBR.class);
+    public List<Map> createProjectMember(String projectId, List<String> userIds, String operatorId) {
+        Map body = UtilMap.map("userIds", userIds);
+        TBR tbr = (TBR) UtilHttp.doPost(tbConf.getApiHost() + "/v3/project/".concat(projectId).concat("/member/create-v2"), initHeaderToken(operatorId), body, TBR.class);
         return (List<Map>) tbr.getResult();
     }
 
     @Override
     public List<Map> updateProjectMember(List<String> userIds, List<String> roleIds, String projectId) {
-        Map body = new HashMap();
-        body.put("userIds", userIds);
-        body.put("roleIds", roleIds);
+        Map body = UtilMap.map("userIds", userIds);
+        body.put("roleIds", roleIds); // 合并字段避免map拆分异常, todo 待优化 map 方法
         TBR tbr = (TBR) UtilHttp.doPost(tbConf.getApiHost() + "/v3/project/".concat(projectId).concat("/member/role/assign"), initHeaderToken(), body, TBR.class);
         return (List<Map>) tbr.getResult();
     }
 
     @Override
     public List<Map> queryProjectRoles(String projectId) {
-        TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/v3/project/" + projectId + "/role", initHeaderToken(), null, TBR.class);
+        Map param = UtilMap.map("pageSize", TBConf.PAGE_SIZE_LIMIT);
+        TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/v3/project/" + projectId + "/role", initHeaderToken(), param, TBR.class);
         return (List<Map>) tbr.getResult();
     }
 
@@ -182,14 +188,33 @@ public class TBClientImpl implements TBClient {
         return (List<Map>) tbr.getResult();
     }
 
+    @Override
+    public List<Map> queryTaskGroupList(String projectId, Map param) {
+        TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/v3/project/" + projectId + "/stage/search", initHeaderToken(), param, TBR.class);
+        return (List<Map>) tbr.getResult();
+    }
+
+    @Override
+    public List<Map> queryProjectTaskList(String projectId, Map param) {
+        param = UtilMap.put(param, "pageSize", TBConf.PAGE_SIZE_LIMIT);
+        TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/v3/project/" + projectId + "/task/query", initHeaderToken(), param, TBR.class);
+        return (List<Map>) tbr.getResult();
+    }
+
     @Override
     public Map updateTaskDueDate(String taskId, String dueDate, String operatorId) {
-        Map body = new HashMap();
-        body.put("dueDate", dueDate);
+        Map body = UtilMap.map("dueDate", dueDate);
         TBR tbr = (TBR) UtilHttp.doPut(tbConf.getApiHost() + "/v3/task/" + taskId + "/dueDate", initHeaderToken(operatorId), body, TBR.class);
         return (Map) tbr.getResult();
     }
 
+    @Override
+    public Map updateTaskExecutor(String taskId, String operatorId, String executorId, boolean disableActivity, boolean disableNotification) {
+        Map body = UtilMap.map("executorId, disableActivity, disableNotification", executorId, disableActivity, disableNotification);
+        TBR tbr = (TBR) UtilHttp.doPut(tbConf.getApiHost() + "/v3/task/" + taskId + "/executor", initHeaderToken(operatorId), body, TBR.class);
+        return (Map) tbr.getResult();
+    }
+
     @Override
     public Map updateTaskCustomField(String taskId, String operatorId, Map body) {
         TBR tbr = (TBR) UtilHttp.doPost(tbConf.getApiHost() + "/v3/task/" + taskId + "/customfield/update", initHeaderToken(operatorId), body, TBR.class);
@@ -205,21 +230,21 @@ public class TBClientImpl implements TBClient {
 
     @Override
     public List<Map> queryProjectCustomField(String projectId, Map body) {
-        UtilMap.putNotNull(body, "pageSize", 500);
+        body = UtilMap.put(body, "pageSize", TBConf.PAGE_SIZE_LIMIT);
         TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/v3/project/" + projectId + "/customfield/search", initHeaderToken(), body, TBR.class);
         return (List<Map>) tbr.getResult();
     }
 
     @Override
     public List<Map> queryProjectCustomFlowStatus(String projectId, Map body) {
-        UtilMap.putNotNull(body, "pageSize", 500);
+        body = UtilMap.put(body, "pageSize", TBConf.PAGE_SIZE_LIMIT);
         TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/v3/project/" + projectId + "/taskflowstatus/search", initHeaderToken(), body, TBR.class);
         return (List<Map>) tbr.getResult();
     }
 
     @Override
     public List<Map> queryProjectCustomFlow(String projectId, Map body) {
-        UtilMap.putNotNull(body, "pageSize", 500);
+        body = UtilMap.put(body, "pageSize", TBConf.PAGE_SIZE_LIMIT);
         TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/v3/project/" + projectId + "/taskflow/search", initHeaderToken(), body, TBR.class);
         return (List<Map>) tbr.getResult();
     }

+ 1 - 0
mjava/src/main/java/com/malk/utils/UtilMap.java

@@ -161,6 +161,7 @@ public abstract class UtilMap {
      * 赋值 [为空赋值默认值]
      */
     public static Map put(Map data, String key, Object defaultValue) {
+        data = empty(data);
         Object value = data.get(key);
         if (ObjectUtil.isNull(value)) {
             value = defaultValue;