Kaynağa Gözat

知识库版本管理

pruple_boy 1 yıl önce
ebeveyn
işleme
428ca01a7a

+ 29 - 0
mjava-aiwei/src/main/java/com/malk/aiwei/controller/TBxYDController.java

@@ -89,6 +89,34 @@ public class TBxYDController {
         return McR.success(awClint.doCheck(taskId, false));
     }
 
+    /**
+     * 交付物审批
+     */
+    @PostMapping("do-approve")
+    McR doApprove(HttpServletRequest request) {
+
+        Map<String, ?> data = UtilServlet.getParamMap(request);
+        log.info("交付物审批, {}", data);
+        Map formData = awClint.doApprove(data, UtilMap.getBoolean(data, "isChange"));
+        return McR.success(formData);
+    }
+
+    /**
+     * 交付物变更发起
+     */
+    @PostMapping("change-approve")
+    McR changeApprove(HttpServletRequest request) {
+
+        Map<String, ?> data = UtilServlet.getParamMap(request);
+        log.info("交付物变更发起, {}", data);
+        
+        // prd 618 通过任务扩展按钮变更情况, 提交后再触发审批回写
+        if (UtilMap.getBoolean(data, "isChange")) {
+            awClint.changeApprove(UtilMap.getString(data, "taskId"), UtilMap.getString(data, "formInstId"));
+        }
+        return McR.success();
+    }
+
     /**
      * 通过模板创建项目
      */
@@ -296,6 +324,7 @@ public class TBxYDController {
     @GetMapping("tmp")
     McR tmp(String programIds) {
 
+//        awClint.tmp();
         return McR.success();
     }
 }

+ 1 - 1
mjava-aiwei/src/main/java/com/malk/aiwei/delegate/TBDelegate.java

@@ -33,7 +33,7 @@ public class TBDelegate implements TBEvent {
 
         if ("v3.task.taskflowstatus.update".equals(eventName)) {
             JSONObject data = eventJson.getJSONObject("data");
-            awClint.doApprove(data);
+            awClint.doApprove(data, false);
         }
     }
 

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

@@ -28,6 +28,7 @@ public class AWServer {
     public static final String TASK_APPROVE_ATTACHMENT = "交付件";
     public static final String TASK_APPROVE_VERSION = "交付件版本";
     public static final String TASK_APPROVE_LINK = "交付件审批流程";
+    public static final String TASK_APPROVE_STATE = "变更状态";
     public static final String TASK_APPROVE_DESC = "交付件描述";
 
     // ppExt: 注意不同任务类型名称唯一性, 如通用已完成不要使用

+ 8 - 1
mjava-aiwei/src/main/java/com/malk/aiwei/service/AWClint.java

@@ -10,7 +10,12 @@ public interface AWClint {
     /**
      * 交付物审批
      */
-    void doApprove(Map data);
+    Map doApprove(Map data, boolean isChange);
+
+    /**
+     * 交付物审批变更发起
+     */
+    void changeApprove(String taskId, String instanceId);
 
     /**
      * 交付物审批回调
@@ -99,4 +104,6 @@ public interface AWClint {
     void approveVersion(String taskId, String pCode);
 
     void test();
+
+    void tmp();
 }

+ 132 - 38
mjava-aiwei/src/main/java/com/malk/aiwei/service/impl/AWImplClient.java

@@ -77,20 +77,44 @@ public class AWImplClient implements AWClint {
 
     /**
      * 交付物审批 [ppExt: 宜搭附件传递 downloadUrl 和 name 即可实现在线预览]
+     * -
+     * 6.18 变更说明
+     * 1. 提交发布与提交变更均通过任务按钮操作, 本质上链到宜搭的页面
+     * 2. 提交发布自动触发评审, 执行结束关闭tab页面. 若有报错则弹出错误信息
+     * 3. 提交变更链接到交付物审批页面, 去掉所有的拒绝, 开启退回操作. 并添加 PCR 填写
      */
     @Override
-    public void doApprove(Map data) {
-
-        String projectId = UtilMap.getString(data, "projectId");
-        String workFlowApprove = _getWorkFlowStatus(projectId, AWServer.WORKFLOW_APPROVE);
-        if (!data.get("tfsId").equals(workFlowApprove)) {
-            return;
+    public Map doApprove(Map data, boolean isChange) {
+
+        // 通过工作流触发: prd 618 通过任务扩展按钮触发, fixme tb任务完成态工作流工作会触发完成时间更新, 导致逾期问题
+        String type = isChange ? "变更" : "发布"; // 回调 发布 变更
+        boolean isCallback = data.containsKey("tfsId");
+        if (isCallback) {
+            String projectId = UtilMap.getString(data, "projectId");
+            String workFlowApprove = _getWorkFlowStatus(projectId, AWServer.WORKFLOW_APPROVE);
+            if (!data.get("tfsId").equals(workFlowApprove)) {
+                return null;
+            }
+            type = "回调";
         }
+        // todo 已变更工作流, 不允许再提交发布; 没有发布状态也不允许做变更
+
         log.info("交付物审批, {}", data);
-        String pCode = UtilMap.getString(data, "projectId");
-        String creatorId = UtilMap.getString(data, "creatorId");
+        McException.assertParamException_Null(data, "taskId");
         String taskId = UtilMap.getString(data, "taskId");
-        Map taskData = _getTaskFieldMap(UtilMap.getString(data, "taskId"), AWServer.TASK_CODE, AWServer.TASK_APPROVE_ATTACHMENT, AWServer.TASK_APPROVE_DESC, AWServer.TASK_PRODUCT, AWServer.TASK_PRODUCT_VERSION);
+        Map taskData = _getTaskFieldMap(taskId, AWServer.TASK_CODE, AWServer.TASK_APPROVE_ATTACHMENT, AWServer.TASK_APPROVE_DESC, AWServer.TASK_PRODUCT, AWServer.TASK_PRODUCT_VERSION);
+        List<Map> docs = _getDocs(taskData);
+        // ppExt: TB有卡控, 校验交付物不能为空, 同时兼容非交付件任务类型
+        McException.assertAccessException(!isCallback && docs.size() == 0, "任务交付件不能为空, 请填写后再操作!");
+        // prd 618 通过任务扩展按钮触发, 接口仅需传递 taskId 即可, 相关信息直接从任务详情取值
+        Map rTask = UtilMap.getMap(taskData, "task");
+        String pCode = UtilMap.getString(rTask, "projectId");
+        String executorId = UtilMap.getString(rTask, "executorId");
+        if (!isCallback) {
+            McException.assertAccessException(StringUtils.isBlank(executorId), "任务执行人未分配!");
+        }
+        // 返回userId, 若非执行人权限, 页面控制弹出框
+        String userId = "";
         String tCode = UtilMap.getString(taskData, AWServer.TASK_CODE);
         // 工作流tb虽然配置了流转逻辑, 冗余稳定性
         List<Map> formList = (List<Map>) ydClient.queryData(YDParam.builder()
@@ -99,9 +123,8 @@ public class AWImplClient implements AWClint {
                 .instanceStatus("RUNNING")
                 .build(), YDConf.FORM_QUERY.retrieve_search_process).getData();
         if (formList.size() > 0 && formList.stream().filter(item -> tCode.equals(((Map) item.get("data")).get("selectField_lqxuswzd"))).findAny().isPresent()) {
-            return;
+            McException.assertAccessException(true, "已存在审批中流程, 请勿重复提交!");
         }
-
         String result = "";
         List<Map> tList = null;
         Map rProject = null;
@@ -126,7 +149,8 @@ public class AWImplClient implements AWClint {
             if (StringUtils.isBlank(result)) {
                 List<Map> roles = (List<Map>) rProject.get("tableField_lqxtykcf");
                 Map formData = UtilMap.map("selectField_lqxuswzd, textField_lrncs2fu", tCode, pCode);
-                formData.put("attachmentField_lqxtebtq", _getDocs(taskData));
+                formData.put("attachmentField_lqxtebtq", docs);
+                formData.put("textField_lxl98hsu", UtilMap.getString(rTask, "content"));                            // 任务标题
                 formData.put("selectField_lqxuswze", UtilMap.getString(rProject, "textField_lrj7vnxb"));            // 项目编号
                 formData.put("textField_lqxuc9m4", UtilMap.getString(rProject, "textareaField_lrj7vnxl"));          // 项目描述
                 formData.put("employeeField_ltzn872j", UtilMap.getString(rProject, "employeeField_ltzn872j_id"));   // 项目经理 0402 控制矩阵角色为空, 流转到PM
@@ -164,21 +188,49 @@ public class AWImplClient implements AWClint {
                     formData.put(compId.get("prEmp"), namesApprover);
                 }
                 // 组装数据
-                Map<String, String> extra = (Map) tbClient.idMapQuery(creatorId, "dingTalk-user", ddConf.getCorpId()).get(0).get("extra");
-                UtilMap.putAll(formData, UtilMap.map("textField_lr3dlwsa, textField_lr3er4qb", taskId, creatorId));
-                formData.put("employeeField_lui5fu7z", Arrays.asList(extra.get("userId"))); // 0402 指定发起人, 用于结构化数据子流程发起人
-                String instanceId = (String) ydClient.operateData(YDParam.builder()
-                        .formUuid(_matchFormUuid("REVIEW"))
-                        .processCode(_matchFormUuid("REVIEW_PROCESS"))
-                        .formDataJson(JSON.toJSONString(formData))
-                        .userId(extra.get("userId"))
-                        .build(), YDConf.FORM_OPERATION.start);
-                result = _matchFormUuid("DOMAIN") + ydConf.getAppType() + "/processDetail?procInsId=" + instanceId;
+                Map<String, String> extra = (Map) tbClient.idMapQuery(executorId, "dingTalk-user", ddConf.getCorpId()).get(0).get("extra");
+                UtilMap.putAll(formData, UtilMap.map("textField_lr3dlwsa, textField_lr3er4qb", taskId, executorId));
+                formData.put("employeeField_lui5fu7z", Arrays.asList(extra.get("userId")));  // 0402 指定发起人, 用于结构化数据子流程发起人
+                formData.put("radioField_lxkaihe5", type);
+                formData.put("selectField_lxl7xtwe", isChange ? "提交变更" : "提交发布");       // 评审类型标题
+                userId = extra.get("userId");
+                // prd 618 通过任务扩展按钮触发, 变更填写关联pcr后再提交, 返回数据. 并需要回调
+                if (!isChange) {
+                    String instanceId = (String) ydClient.operateData(YDParam.builder()
+                            .formUuid(_matchFormUuid("REVIEW"))
+                            .processCode(_matchFormUuid("REVIEW_PROCESS"))
+                            .formDataJson(JSON.toJSONString(formData))
+                            .userId(extra.get("userId"))
+                            .build(), YDConf.FORM_OPERATION.start);
+                    result = _matchFormUuid("DOMAIN") + ydConf.getAppType() + "/processDetail?procInsId=" + instanceId;
+                } else {
+                    formData.put("result", result);
+                    formData.put("userId", userId);
+                    List<String> pmUserIds = (List<String>) JSON.parse(UtilMap.getString(rProject, "employeeField_ltzn872j_id"));
+                    formData.put("PMName", ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), pmUserIds.get(0)).get("name"));
+                    return formData;
+                }
             }
         }
         log.info("交付物结果, {}", result);
+        // prd 618 通过任务扩展按钮, 提交后再触发审批回写, 直接提交场景下直接更新审批链接
+        if (!isChange) {
+            Map body = TBConf.assembleCustomFieldName(AWServer.TASK_APPROVE_LINK, result);
+            tbClient.updateTaskCustomField(taskId, tbConf.getOperatorId(), body);
+        }
+        return UtilMap.map("result, userId", result, userId);
+    }
+
+    /**
+     * 交付物审批变更发起
+     */
+    @Override
+    public void changeApprove(String taskId, String instanceId) {
+        String result = _matchFormUuid("DOMAIN") + ydConf.getAppType() + "/processDetail?procInsId=" + instanceId;
         Map body = TBConf.assembleCustomFieldName(AWServer.TASK_APPROVE_LINK, result);
         tbClient.updateTaskCustomField(taskId, tbConf.getOperatorId(), body);
+        Map body2 = TBConf.assembleCustomFieldName(AWServer.TASK_APPROVE_STATE, "变更中");
+        tbClient.updateTaskCustomField(taskId, tbConf.getOperatorId(), body2);
     }
 
     /**
@@ -188,9 +240,17 @@ public class AWImplClient implements AWClint {
     public void approved(Map data) {
 
         log.info("交付物审批回调, {}", data);
-        String result = String.valueOf(data.get("approve"));
+        String result = UtilMap.getString(data, "approve");
+        String type = UtilMap.getString(data, "type");
+        if ("变更".equals(type)) {
+            Map body = TBConf.assembleCustomFieldName(AWServer.TASK_APPROVE_STATE, "已变更");
+            tbClient.updateTaskCustomField(UtilMap.getString(data, "taskId"), tbConf.getOperatorId(), body);
+        }
+        // todo 任务工作流更新, tb需要调整
+//        if ("回调".equals(UtilMap.getString(data, "type"))) {
         String workFlowId = _getWorkFlowStatus(String.valueOf(data.get("projectId")), result);
         tbClient.updateTaskFlowStatus(String.valueOf(data.get("taskId")), tbConf.getOperatorId(), workFlowId, String.valueOf(data.get("approve")));
+//        }
     }
 
     @Autowired
@@ -613,14 +673,14 @@ public class AWImplClient implements AWClint {
             tbClient.updateProjectStatusField(projectId, tbConf.getOperatorId(), TBConf.assembleCustomFieldName("项目描述", UtilMap.getString(formData, "textareaField_lrj7vnxl")));
             tbClient.updateProjectStatusField(projectId, tbConf.getOperatorId(), TBConf.assembleCustomFieldName("项目重要等级", UtilMap.getString(formData, "textField_lwj1r7n6")));
         }
-        // 项目添加到项目集
-//        List<Map> groupList = tbClient.queryProgramList_all();
-//        Optional optional = groupList.stream().filter(item -> groupName.equals(item.get("name"))).findAny();
-//        if (optional.isPresent()) {
-//            tbClient.upsertProgramProject(UtilMap.getString((Map) optional.get(), "id"), Arrays.asList(projectId), tbConf.getOperatorId());
-//        }
-
         log.info("TB项目信息, {}, {}", projectId, roleIds.size());
+
+        // 项目添加到项目集
+        List<Map> groupList = tbClient.queryProgramList_all();
+        Optional optional = groupList.stream().filter(item -> groupName.equals(item.get("name"))).findAny();
+        if (optional.isPresent()) {
+            tbClient.upsertProgramProject(UtilMap.getString((Map) optional.get(), "id"), Arrays.asList(projectId), tbConf.getOperatorId());
+        }
     }
 
     // fixme 提取方法, 参考 getProjectCFID 实现
@@ -700,6 +760,20 @@ public class AWImplClient implements AWClint {
                         tbClient.updateTaskExecutor(UtilMap.getString(task, "id"), tbConf.getOperatorId(), tbUserId, false, false);
                     }
                 }
+                // prd 6.17 任务分配:资源匹配多人时,取值第一人作为执行人,其他人员作为参与者
+                if (roleIds.size() > 1) {
+                    String executorId = UtilMap.getString(task, "executorId");
+                    List<String> tbUserIds = roleIds.stream().map(item -> _convertUserId(item, false)).collect(Collectors.toList());
+                    // prd 多人情况避免, 手动指定后被覆盖. 判定当前任务执行人在角色范围内则不支持更新
+                    if (StringUtils.isNotBlank(executorId) && tbUserIds.contains(executorId)) {
+                        continue;
+                    }
+                    if (tbUserIds.size() > 0) {
+                        tbClient.updateTaskExecutor(UtilMap.getString(task, "id"), tbConf.getOperatorId(), tbUserIds.get(0), false, false);
+                    }
+                    roleIds.remove(0);
+                    tbClient.updateTaskInvolveMembers(UtilMap.getString(task, "id"), tbConf.getOperatorId(), tbUserIds, null, false, false);
+                }
             }
             return true;
         });
@@ -1173,6 +1247,8 @@ public class AWImplClient implements AWClint {
      * 4. 需要通过模板自动创建知识库, TB 134版本已支持
      * 1. 调整: fixme 知识库列表接口, 查询受到 operatorId 权限控制, 仅会返回操作者有权限知识库
      * 2. 方案: 依赖同3, 公共账号 TBManager 默认是项目拥有者, 会默认同步到知识库成员, 查询权限后通过知识库创建者添加编辑, 用于复制文件 [ppExt: 直接添加知识库根节点 rootNodeId 编辑权限]
+     * -
+     * -5. 高密知识库访问限制: 同一个知识库内可以复制, 产品答复通过接口形态也与实际操作一致的表现, 均有控制不能往外进行复制. 写入是被允许的
      */
     @Override
     public void approveVersion(String taskId, String pCode) {
@@ -1197,7 +1273,7 @@ public class AWImplClient implements AWClint {
             rootNodeId = UtilMap.getString(pList.get(0), "textField_lx342bvd");
             creatorId = UtilMap.getString(pList.get(0), "textField_lx8ra1bs");
             if (StringUtils.isBlank(workspaceId)) {
-                // 获取知识库空间Id [ ppExt: 知识库列表接口, 查询受到 operatorId 权限控制, 仅会返回操作者有权限知识库 ]
+                // 获取知识库空间Id [ ppExt: 知识库列表接口, 查询受到 operatorId 权限控制, 仅会返回操作者有权限知识库 ] todo tb提供接口返回知识库相关信息
                 List<Map> workspaces = ddClient_storage.searchWorkspaces(ddClient.getAccessToken(), unionId, pCode, null);
                 if (workspaces.isEmpty()) {
                     result = "项目知识库未找到";
@@ -1228,7 +1304,9 @@ public class AWImplClient implements AWClint {
             // 知识库文件夹 & 拷贝处理 [prd 文件目录结构 TR评审节点/资源名称]
             Map taskData = _getTaskFieldMap(taskId, AWServer.TASK_APPROVE_ATTACHMENT, AWServer.TASK_STAGE, AWServer.TASK_ROLE);
             Map rTask = UtilMap.getMap(taskData, "task");
-
+            // fixme: 知识库所有操作和权限都与依赖于权限, 通过 spaceId + dentryId 查询创建人需要传递操作者, 操作者需要有查看以上权限. todo 目前临时读取执行人, tb文档相关信息需要返回创建人信息
+            Map<String, String> extra = (Map) tbClient.idMapQuery(UtilMap.getString(rTask, "executorId"), "dingTalk-user", ddConf.getCorpId()).get(0).get("extra");
+            String executorIdUnionId = String.valueOf(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), extra.get("userId")).get("unionid"));
             // 文件/文件夹版本控制: fileType, fileName, pCode, pId, workspaceId, rootNodeId, unionId
             String fileName = "项目交付件";
             Map record = _upsertFileAbsolutePath("文件夹", fileName, pCode, UtilMap.getString(rTask, "projectId"), workspaceId, rootNodeId, unionId);
@@ -1259,9 +1337,8 @@ public class AWImplClient implements AWClint {
                 String title = UtilMap.getString(doc, "title");
                 fileName = fileName + "/" + title;
                 Map version = _upsertFileAbsolutePath("文件", fileName, pCode, UtilMap.getString(rTask, "projectId"), workspaceId, rootNodeId, unionId);
-
-                // 通过提供通过 spaceId + dentryId 查询创建人, 再由创建人为公共账号添加编辑权限, 再通过公共账号复制 [ppExt: 知识库可为其他成员添加不高于自身的权限]
-                DDR_New ddr_dentry = ddClient_storage.getSpaceDentryDetail(ddClient.getAccessToken(), unionId, UtilMap.getString(doc, "instanceId"), UtilMap.getString(doc, "instanceId"), false);
+                // 通过提供 spaceId + dentryId 查询创建人, 再由创建人为公共账号添加编辑权限, 再通过公共账号复制 [ppExt: 知识库可为其他成员添加不高于自身的权限]
+                DDR_New ddr_dentry = ddClient_storage.getSpaceDentryDetail(ddClient.getAccessToken(), executorIdUnionId, UtilMap.getString(doc, "instanceId"), UtilMap.getString(doc, "instanceId"), false);
                 try {
                     String creatorId_t = UtilMap.getString(ddr_dentry.getUpdater(), "unionId");
                     List<Map> members_t = Arrays.asList(UtilMap.map("type, id", "USER", ddConf.getOperator()));
@@ -1283,7 +1360,6 @@ public class AWImplClient implements AWClint {
                     log.error(e.getMessage(), e);
                     continue;
                 }
-
                 // 更新最新版本记录, 叠加版本号
                 Map formData = UtilMap.map("textField_lx36gdpp, numberField_lx36gdpz", fileName, order);
                 formData.put("textField_lx342bvd", ddr_new.getDentryId());
@@ -1304,11 +1380,11 @@ public class AWImplClient implements AWClint {
                 tbClient.updateTaskCustomField(taskId, tbConf.getOperatorId(), UtilMap.map("customfieldName, value", AWServer.TASK_APPROVE_VERSION, attas));
             }
         }
+        // prd: 变更有单独BPM流程, TBx宜搭只是作为归档管理, 目前交付物审批只有同意和退回操作 [6.18]
         if (StringUtils.isNotBlank(result)) {
             Map body = TBConf.assembleCustomFieldName(AWServer.TASK_APPROVE_VERSION, result);
             tbClient.updateTaskCustomField(taskId, tbConf.getOperatorId(), body);
         }
-        // todo: 业务待定, 若变更有单独BPM流程, TBx宜搭只是作为归档管理, 不存在变更版本审批拒绝场景
         log.info("知识库版本管理, {}", result);
     }
 
@@ -1317,8 +1393,26 @@ public class AWImplClient implements AWClint {
 
     }
 
+    @Override
+    public void tmp() {
+//        this.tmp_617();
+    }
+
+    /// tmp: 6.17 处理审批通过数据 [新项目工作量配置异常, 审批通过配置为审核通过]
+    private void tmp_617() {
+
+        List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
+                .formUuid("FORM-812FD46AF391449A8F206EDB3221B38840UQ")
+                .createFromTimeGMT("2024-06-03")
+                .build());
+        for (Map item : dataList) {
+            approved(UtilMap.map("approve, taskId, creatorId, projectId", "审批通过", item.get("textField_lr3dlwsa"), item.get("textField_lr3er4qb"), item.get("textField_lrncs2fu")));
+        }
+        log.info("处理审批通过数据, {}", dataList.size());
+    }
+
     /// tmp: 5.22 处理检查项重复数据
-    private void _test() {
+    private void tmp_522() {
         List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
                 .formUuid("FORM-6E2C0D1197264B8AA23EB3FECAE7344B00BN")
                 .searchFieldJson(JSON.toJSONString(UtilMap.map("selectField_lqxuswze", "A2328")))

+ 94 - 0
mjava-cloudpure/src/main/java/com/malk/cloudpure/controller/TSController.java

@@ -0,0 +1,94 @@
+package com.malk.cloudpure.controller;
+
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.common.McException;
+import com.malk.server.common.McR;
+import com.malk.service.aliwork.YDClient;
+import com.malk.service.aliwork.YDService;
+import com.malk.service.dingtalk.DDClient;
+import com.malk.service.dingtalk.DDClient_Contacts;
+import com.malk.utils.UtilMap;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 错误抛出与拦截详见 CatchException
+ */
+@Slf4j
+@RestController
+@RequestMapping("test")
+public class TSController {
+
+    @Autowired
+    private DDClient ddClient;
+
+    @Autowired
+    private DDClient_Contacts ddClient_contacts;
+
+    @Autowired
+    private YDService ydService;
+
+    @Autowired
+    private YDClient ydClient;
+
+    @SneakyThrows
+    @PostMapping("test")
+    McR test() {
+
+        //updatePosition("2445432306884832");
+        tianhua618();
+
+        return McR.success();
+    }
+
+
+    /// 6.18 苏州天华更新部门职位
+    private void tianhua618() {
+        List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
+                .appType("APP_GW0W0VPQ0FR53R258J6G")
+                .systemToken("WO966N71H84IUHE3AYRISBIWQTIH39K5R1ZRL32")
+                .formUuid("FORM-120A6337A2034F4FB0E44949DB05C3BE5FTC")
+                .build());
+        dataList.forEach(item -> {
+            updatePosition(UtilMap.getString(item, "textField_lxjl5kwm"));
+            ydClient.operateData(YDParam.builder()
+                    .appType("APP_GW0W0VPQ0FR53R258J6G")
+                    .systemToken("WO966N71H84IUHE3AYRISBIWQTIH39K5R1ZRL32")
+                    .formInstanceId(UtilMap.getString(item, "formInstanceId"))
+                    .build(), YDConf.FORM_OPERATION.delete);
+        });
+
+    }
+
+    private void updatePosition(String userId) {
+        String token = ddClient.getAccessToken("ding4h80bxhmsujcmkad", "gO9i1LvoExN3ozhKbJ6ZzZhHgvAXEfUfwJMJvYyaP0H5zt5jk2zhiWQLXQMCz9yd");
+        Map userInfo = null;
+        try {
+            userInfo = ddClient_contacts.getUserInfoById(token, userId);
+        } catch (McException e) {
+            log.error(e.getMessage(), e);
+            return;
+        }
+        String title = UtilMap.getString(userInfo, "title");
+        if (StringUtils.isBlank(title)) {
+            log.info("更新异常, {}, {}", userInfo);
+            return;
+        }
+        List<Long> deptIds = (List<Long>) UtilMap.getList(userInfo, "dept_id_list").stream().map(item -> Long.parseLong(String.valueOf(item))).collect(Collectors.toList());
+        List<Map> titles = deptIds.stream().map(dept_id -> UtilMap.map("dept_id, title", dept_id, title)).collect(Collectors.toList());
+        ddClient_contacts.updateUser_dingTalk(token, userId, deptIds, UtilMap.map("dept_title_list", titles));
+    }
+
+}
+
+

+ 5 - 0
mjava/src/main/java/com/malk/server/aliwork/YDParam.java

@@ -94,6 +94,11 @@ public class YDParam extends BaseDto {
         }
     }
 
+    /// 查询创建时间, 钉钉版本接口 [旧版本接口不支持]
+    private String createFromTimeGMT;
+    private String createToTimeGMT;
+
+
     /**
      * 自定义参数
      */

+ 3 - 1
mjava/src/main/java/com/malk/service/dingtalk/DDClient_Storage.java

@@ -72,6 +72,7 @@ public interface DDClient_Storage {
      * fixme: 文件复制后, 变更记录不会保存
      * 1.知识库文件/文件夹复制接口需要传递来源 workplaceId, 但测试下来传递目标 workplaceId 也可以, 包括从我的文档内拷贝均验证通过
      * 2.知识库复制需要同时有文件与\复制目标文件夹下编辑权限. 目前外部使用不能提供公共操作账号, 只能通过 dentryUuid 给来源文件添加编辑权限, 再进行复制操作
+     * 3.高密知识库访问限制: 同一个知识库内可以复制, 产品答复通过接口形态也与实际操作一致的表现, 均有控制不能往外进行复制. 写入是被允许的
      */
     DDR_New copyDentries(String accessToken, String operatorId, String workspaceId, String fileDentryId, String targetSpaceId, String toParentDentryId, String fileName);
 
@@ -88,7 +89,8 @@ public interface DDClient_Storage {
      * 查询知识库节点信息
      * ppExt: 历史接口, 可兼容当前TB返回
      * 1. TB 任务上未返回真实 workspaceId, 返回的 WorkspaceId 其实是 spaceId, instanceId 其实是 dentryId.
-     * 2. 通过提供通过 spaceId + dentryId 查询创建人, 再由创建人为公共账号添加编辑权限, 再通过公共账号复制 [ppExt: 知识库可为其他成员添加不高于自身的权限]
+     * 2. 通过提供 spaceId + dentryId 查询创建人, 再由创建人为公共账号添加编辑权限, 再通过公共账号复制 [ppExt: 知识库可为其他成员添加不高于自身的权限]
+     * 3. fixme: 知识库所有操作和权限都与依赖于权限, 通过 spaceId + dentryId 查询创建人需要传递操作者, 操作者需要有查看以上权限
      *
      * @apiNote https://open.dingtalk.com/document/isvapp/query-knowledge-base-node-information?spm=a2q3p.21071111.0.0.93ffaiEWaiEWOI
      */

+ 7 - 0
mjava/src/main/java/com/malk/service/teambition/TBClient.java

@@ -153,6 +153,13 @@ public interface TBClient {
      */
     Map updateTaskExecutor(String taskId, String operatorId, String executorId, boolean disableActivity, boolean disableNotification);
 
+    /**
+     * 更新任务参与者 [ppExt: addInvolvers 参数忽略, involveMembers 已兼容新增与更新, 同时传递会有异常]
+     *
+     * @apiNote https://open.teambition.com/docs/apis/6321c6d2912d20d3b5a4a66a
+     */
+    Map updateTaskInvolveMembers(String taskId, String operatorId, List<String> involveMembers, List<String> delInvolvers, boolean disableActivity, boolean disableNotification);
+
     /**
      * 更新任务自定义字段值
      * - ppExt:

+ 14 - 0
mjava/src/main/java/com/malk/service/teambition/impl/TBClientImpl.java

@@ -8,6 +8,7 @@ import com.malk.server.teambition.TBConf;
 import com.malk.server.teambition.TBR;
 import com.malk.service.teambition.TBClient;
 import com.malk.utils.UtilHttp;
+import com.malk.utils.UtilList;
 import com.malk.utils.UtilMap;
 import com.malk.utils.UtilToken;
 import lombok.Synchronized;
@@ -254,6 +255,19 @@ public class TBClientImpl implements TBClient {
         return (Map) tbr.getResult();
     }
 
+    @Override
+    public Map updateTaskInvolveMembers(String taskId, String operatorId, List<String> involveMembers, List<String> delInvolvers, boolean disableActivity, boolean disableNotification) {
+        Map body = UtilMap.map("disableActivity, disableNotification", disableActivity, disableNotification);
+        if (UtilList.isNotEmpty(involveMembers)) {
+            body.put("involveMembers", involveMembers);
+        }
+        if (UtilList.isNotEmpty(delInvolvers)) {
+            body.put("delInvolvers", delInvolvers);
+        }
+        TBR tbr = (TBR) UtilHttp.doPut(tbConf.getApiHost() + "/v3/task/" + taskId + "/involveMembers", 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);