Browse Source

艾为多模板适配

pruple_boy 1 year ago
parent
commit
17fc4dd130

+ 57 - 45
mjava-aiwei/src/main/java/com/malk/aiwei/controller/TBxYDController.java

@@ -6,6 +6,9 @@ package com.malk.aiwei.controller;
 
 import com.alibaba.fastjson.JSON;
 import com.malk.aiwei.service.AWClint;
+import com.malk.delegate.McDelegate;
+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.server.dingtalk.DDConf;
@@ -25,7 +28,6 @@ import org.springframework.web.bind.annotation.RestController;
 import javax.servlet.http.HttpServletRequest;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 
 @Slf4j
 @RestController
@@ -103,23 +105,6 @@ public class TBxYDController {
         return McR.success();
     }
 
-    /**
-     * 项目主数据增量更新
-     */
-    @PostMapping("project/update")
-    McR updateProject(HttpServletRequest request) {
-
-        Map<String, ?> data = UtilServlet.getParamMap(request);
-        log.info("项目主数据增量更新, {}", data);
-        McException.assertParamException_Null(data, "projectCode");
-
-        List<String> pIdList = (List<String>) JSON.parse(UtilMap.getString(data, "projectCode"));
-        for (String projectCode : pIdList) {
-            awClint.updateProject(projectCode);
-        }
-        return McR.success();
-    }
-
     /**
      * 分配项目角色
      */
@@ -148,6 +133,15 @@ public class TBxYDController {
         return McR.success();
     }
 
+    /**
+     * 手动触发, 同步项目主数据
+     */
+    @PostMapping("project/sync")
+    McR syncProject() {
+        awClint.syncProject(null);
+        return McR.success();
+    }
+
     /**
      * 手动触发, 同步预检项
      */
@@ -158,11 +152,45 @@ public class TBxYDController {
     }
 
     /**
-     * 手动触发, 同步项目主数据
+     * 项目主数据增量更新
      */
-    @PostMapping("project/sync")
-    McR syncProject() {
-        awClint.syncProject(null);
+    @PostMapping("project/update")
+    McR updateProject(HttpServletRequest request) {
+        Map<String, ?> data = UtilServlet.getParamMap(request);
+        log.info("项目主数据增量更新, {}", data);
+
+        // ppExt: 宜搭数据创建后, 以及客户系统同步到主数据有时间差, 做延迟代理 10s
+        mcDelegate.setTimeout(() -> {
+            // 多项目变更(项目成员变更) ppExt: 自动化通过公式获取子表字段,接口取不到。目前先通过传递实例ID,进行查询
+            if (data.containsKey("formInstId")) {
+                Map formData = ydClient.queryData(YDParam.builder()
+                        .formInstanceId(UtilMap.getString(data, "formInstId"))
+                        .build(), YDConf.FORM_QUERY.retrieve_id).getFormData();
+                for (Map row : (List<Map>) UtilMap.getList(formData, "tableField_lqovv8at")) {
+                    awClint.updateProject(UtilMap.getString(row, "textField_lqovv8ax"));
+                }
+            }
+            // 单项目变更(项目号变更)
+            if (data.containsKey("projectCode")) {
+                awClint.updateProject(UtilMap.getString(data, "projectCode"));
+            }
+        }, 10000);
+        return McR.success();
+    }
+
+    /**
+     * 手动触发, 同步CRM基线数据
+     */
+    @PostMapping("crm/sync")
+    McR syncCRM(HttpServletRequest request) {
+        Map<String, ?> data = UtilServlet.getParamMap(request);
+        log.info("同步CRM基线数据, {}", data);
+
+        if (data.containsKey("projectId")) {
+            awClint.syncBaseLineForCRM(UtilMap.getString(data, "projectId"));
+        } else {
+            awClint.syncBaseLineForCRM();
+        }
         return McR.success();
     }
 
@@ -199,38 +227,22 @@ public class TBxYDController {
     @Autowired
     private YDService ydService;
 
+    @Autowired
+    private McDelegate mcDelegate;
+
     @PostMapping("test")
     McR test(HttpServletRequest request) {
 
-        log.info("test, {}", UtilServlet.getParamMap(request));
-        log.info("header, {}", UtilServlet.getHeaders(request));
-        List<Map> rsp = tbClient.idMapQuery(tbConf.getOperatorId(), "dingTalk-user", ddConf.getCorpId());
-        return McR.success(rsp);
-    }
+        Map<String, ?> data = UtilServlet.getParamMap(request);
+        log.info("test, {}", data);
 
-    // 获取项目自定义字段ID, 格式 { 名称: id } todo 提取
-    Map<String, String> getProjectCFID(String projectId, List<String> names) {
-        List<Map> customField = tbClient.queryProjectCustomField(projectId, null);
-        List tList = customField.stream().filter(item -> names.contains(item.get("name"))).collect(Collectors.toList());
-        return (Map<String, String>) tList.stream().reduce(UtilMap.empty(), (acc, cur) -> {
-            ((Map) acc).put(((Map) cur).get("name"), ((Map) cur).get("id"));
-            return acc;
-        });
+        return McR.success();
     }
 
     @GetMapping("tmp")
     McR tmp() {
 
-        String projectId = "65bb77bb530c668c1a82754b";
-
-//        tbClient.queryGanttBaseline(projectId, null);
-//        String cfid = getProjectCFID(projectId, Arrays.asList(AWServer.TASK_CODE)).get(AWServer.TASK_CODE);
-//        String tql = "cf:" + cfid + " = " + "TR1-01";
-//        tbClient.queryProjectTaskList(projectId, UtilMap.map("q", tql), null);
-//        tbClient.queryProjectRoles(projectId)
-//        tbClient.queryProjectMember(projectId, null);
-
-//        awClint.test();
+        awClint.test();
         return McR.success();
     }
 }

+ 13 - 0
mjava-aiwei/src/main/java/com/malk/aiwei/schedule/AWScheduleTask.java

@@ -71,4 +71,17 @@ public class AWScheduleTask {
             e.printStackTrace();
         }
     }
+
+    /**
+     * 每天20点同步, 全量同步CRM
+     */
+    @Scheduled(cron = "0 0 20 * * ? ")
+    public void sync_5() {
+        try {
+            awClint.syncBaseLineForCRM();
+        } catch (Exception e) {
+            // 记录错误信息
+            e.printStackTrace();
+        }
+    }
 }

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

@@ -18,6 +18,7 @@ public class AWServer {
     public static final String TASK_CODE = "任务编号";
     public static final String TASK_STAGE = "TR评审节点";
     public static final String TASK_STAGE_BLANK = "无TR评审节点";
+    public static final String TASK_PRODUCT = "产品型号";
 
     // prd 23.02.29 字段从 预检项 变更为 技术检查项
     public static final String TASK_CHECK_LINK = "技术检查项";

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

@@ -52,6 +52,16 @@ public interface AWClint {
      */
     void removeDependencies(String projectId, List<String> trNode);
 
+    /**
+     * 增量同步crm基线
+     */
+    void syncBaseLineForCRM(String projectId);
+
+    /**
+     * 全量同步crm基线
+     */
+    void syncBaseLineForCRM();
+
     /**
      * 同步预检项 [实现]
      *

+ 198 - 22
mjava-aiwei/src/main/java/com/malk/aiwei/service/impl/AWImplClient.java

@@ -12,9 +12,7 @@ import com.malk.server.teambition.TBConf;
 import com.malk.service.aliwork.YDClient;
 import com.malk.service.aliwork.YDService;
 import com.malk.service.teambition.TBClient;
-import com.malk.utils.UtilEnv;
-import com.malk.utils.UtilMap;
-import com.malk.utils.UtilString;
+import com.malk.utils.*;
 import lombok.Synchronized;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
@@ -40,7 +38,7 @@ public class AWImplClient implements AWClint {
     // 项目主数据表
     String _matchFormUuid(String code) {
         Map<String, String> formUuid = UtilMap.empty();
-        if (UtilEnv.getActiveProfile().equals(UtilEnv.ENV_PROD)) {
+        if (true || UtilEnv.getActiveProfile().equals(UtilEnv.ENV_PROD)) {
             formUuid.put("REVIEW", "FORM-812FD46AF391449A8F206EDB3221B38840UQ"); // 交付物审批记录
             formUuid.put("REVIEW_PROCESS", "TPROC--RJC66SC1NEFHXJ0H770K0CF4WN1K21HQ706RL5"); // 交付物审批记录
             formUuid.put("PROJECT", "FORM-141E21DF183846028E21727CE43CD1C75CLZ"); // 项目主数据
@@ -48,6 +46,7 @@ public class AWImplClient implements AWClint {
             formUuid.put("CHECK", "FORM-1A5D4D7FBF88409B956EBE51F9342A6BKOLP"); // 预检项
             formUuid.put("RECORD", "FORM-6E2C0D1197264B8AA23EB3FECAE7344B00BN"); // 预检项记录
             formUuid.put("ROLE", "FORM-3C7396A12ADB48A8833EBD90089C93833R21"); // 项目角色
+            formUuid.put("CRM_LOG", "FORM-16DD578308E64763AE1539D8176CCCD0GX6F"); // crm推送日志
             formUuid.put("DOMAIN", "https://yida.awinic.com/"); // 宜搭域名
         } else {
             // [ fixme: 测试环境添加主数据, 匹配不同架构下企业成员 ]
@@ -58,6 +57,7 @@ public class AWImplClient implements AWClint {
             formUuid.put("CHECK", "FORM-E6CB042D7929448888AE5E2B27631E57IVPM");
             formUuid.put("RECORD", "FORM-7B63BB056145452F8BC0A2C52492DE00QVBH");
             formUuid.put("ROLE", "FORM-5BE21392886E46DF955D1EBC100ADA429NON");
+            formUuid.put("CRM_LOG", "FORM-7F40E85D4FA8487C8E00C9156B76A953CBMG");
             formUuid.put("DOMAIN", "https://kabom7.aliwork.com/");
         }
         return formUuid.get(code);
@@ -111,12 +111,13 @@ 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, radioField_lrnddfq6", tCode, pCode, "启用");
+                Map formData = UtilMap.map("selectField_lqxuswzd, textField_lrncs2fu", tCode, pCode);
                 formData.put("attachmentField_lqxtebtq", _getDocs(taskData));
-                formData.put("selectField_lqxuswze", UtilMap.getString(rProject, "textField_lrj7vnxb")); // 项目编号
-                formData.put("textField_lqxuc9m4", UtilMap.getString(rProject, "textField_lqxtykcd")); // 项目名称
+                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
+                formData.put("textField_ltwcq7s6", UtilMap.getString(rProject, "textField_ltwcq7s6"));              // 项目类型
                 // 匹配任务编码与项目角色
                 List<Map<String, String>> compIds = Arrays.asList(  // 任务表角色, 交付物评审表: 角色, 审批人
                         UtilMap.map("tsRole, prRole, prEmp", "multiSelectField_lrokzlo7, multiSelectField_lrokzlo7, employeeField_lqxtebtw"),
@@ -149,6 +150,7 @@ public class AWImplClient implements AWClint {
                 // 组装数据
                 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"))
@@ -182,27 +184,34 @@ public class AWImplClient implements AWClint {
     private DDConf ddConf;
 
     // 获取任务详情 fixme 提取到 tbservice
-    private Map _getTaskFieldMap(String taskId, String... fieldNames) {
-
-        Map rTask = tbClient.queryTaskDetail(taskId, null, null).get(0);
-        List<Map> customfields = UtilMap.getList(rTask, "customfields");
-
-        // 任务编码字段ID
-        List<Map> customField = tbClient.queryProjectCustomField(String.valueOf(rTask.get("projectId")), null);
+    private Map<String, String> _getTaskFieldMap(Map task, List<Map> projectCustomField, String... fieldNames) {
+        return __getTaskMap(task, projectCustomField, fieldNames);
+    }
 
-        // 组装任务数据
-        Map result = UtilMap.map("task", rTask);
+    // 组装任务数据
+    private Map __getTaskMap(Map task, List<Map> projectCustomField, String... fieldNames) {
+        Map result = UtilMap.map("task", task);
+        List<Map> taskCustomField = (List<Map>) task.get("customfields");
         for (String filed : fieldNames) {
-            Optional optional = customField.stream().filter(item -> filed.equals(item.get("name"))).findAny();
+            Optional optional = projectCustomField.stream().filter(item -> filed.equals(item.get("name"))).findAny();
             if (optional.isPresent()) {
                 Map map = (Map) optional.get();
-                result.put(filed, TBConf.getTaskFieldValue_First(customfields, String.valueOf(map.get("id"))));
+                result.put(filed, TBConf.getTaskFieldValue_First(taskCustomField, String.valueOf(map.get("id"))));
                 result.put(filed + "_id", map.get("id")); // ppExt: 返回字段ID
             }
         }
         return result;
     }
 
+    // 获取任务详情 fixme 提取到 tbservice
+    private Map _getTaskFieldMap(String taskId, String... fieldNames) {
+        // 查询任务详情
+        Map rTask = tbClient.queryTaskDetail(taskId, null, null).get(0);
+        // 查询项目任务ID
+        List<Map> customField = tbClient.queryProjectCustomField(String.valueOf(rTask.get("projectId")), null);
+        return __getTaskMap(rTask, customField, fieldNames);
+    }
+
     // 匹配工作流名称, 获取ID
     @Deprecated
     String _getWorkFlowStatus(String projectId, String workFlowStatusName) {
@@ -239,7 +248,6 @@ public class AWImplClient implements AWClint {
     List<Map> _getDocs(Map taskData) {
         Map rTask = UtilMap.getMap(taskData, "task");
         List<Map> customfields = UtilMap.getList(rTask, "customfields");
-
         String deliverable = String.valueOf(taskData.get(AWServer.TASK_APPROVE_ATTACHMENT + "_id")); // 交付物任务字段ID
         List<Map> attachments = TBConf.getTaskFieldValue(customfields, deliverable).stream().map(item -> {
             Map link = (Map) JSON.parse(String.valueOf(item.get("metaString")));
@@ -397,7 +405,8 @@ public class AWImplClient implements AWClint {
                 Object rootItems = baseFormData.get(rootFormData.get("textField_lr7bgi76"));
                 Object projectRoot = rootFormData.get("textField_lqxu439g");
                 if (rootItems != null && projectRoot != null) {
-                    details.add(UtilMap.map("employeeField_lqxtykch, selectField_lqxu6bgf", rootItems, projectRoot));
+                    // prd 提交代表标识, 用于同步项目角色
+                    details.add(UtilMap.map("employeeField_lqxtykch, selectField_lqxu6bgf, radioField_lu8li5ie", rootItems, projectRoot, rootFormData.get("radioField_lu8li5ie")));
                     // prd 添加项目经理, 用于数据权限控制
                     if (AWServer.PROJECT_PM_NAME.equals(projectRoot)) {
                         formData.put("employeeField_ltzn872j", rootItems);
@@ -504,7 +513,7 @@ public class AWImplClient implements AWClint {
                 pmUserId.addAll(userIds);
             }
             // prd 项目各代表, 可独立N/A任务, 添加角色控制权限
-            if (roleName.endsWith("代表")) {
+            if ("是".equals(row.get("radioField_lu8li5ie"))) {
                 representativeIds.addAll(userIds);
             }
             roleIds.addAll(userIds);
@@ -529,6 +538,8 @@ public class AWImplClient implements AWClint {
         if (!pmUserId.isEmpty()) {
             tbClient.updateProjectMember(_convertUserId(pmUserId, false), Arrays.asList(pmRoleId), projectId);
         }
+        // prd 人员变更, 自动同步更新执行人: 仅触发存在执行人 & 未完成场景
+        updateProjectRole(projectId, Arrays.asList("全部", "自动更新"));
         // 同步项目分组 [prd 主数据建立产品与分组对照表,绑定末级分类接口,若未匹配则不添加]
         String groupName = UtilMap.getString(formData, "textField_ltsdsti6");
         if (StringUtils.isNotBlank(groupName)) {
@@ -598,6 +609,12 @@ public class AWImplClient implements AWClint {
                     }
                     log.info("tr节点控制, {}, {}", trName, trNode);
                 }
+                // prd 人员变更, 自动同步更新执行人: 仅触发存在执行人 & 未完成场景
+                if (trNode.contains("自动更新")) {
+                    if (StringUtils.isBlank(UtilMap.getString(task, "executorId"))) {
+                        continue;
+                    }
+                }
                 String roleName = TBConf.getTaskFieldValue_First(customfields, UtilMap.getString((Map) optional.get(), "id"));
                 // 获取角色在项目主数据对应成员
                 optional = rList.stream().filter(item -> roleName.equals(item.get("selectField_lqxu6bgf"))).findAny();
@@ -673,6 +690,152 @@ public class AWImplClient implements AWClint {
                 .collect(Collectors.toList());
     }
 
+    /**
+     * 同步crm基线
+     */
+    @Override
+    public void syncBaseLineForCRM(String projectId) {
+        String C_TR5_03_13 = "TR5-03-13";
+        String C_TR6_01_11 = "TR6-01-11";
+
+        // 7. 产品型号 (Name) : 以上计划时间对应的产品型号 / 取产品型号字段
+        List<Map> customField = tbClient.queryProjectCustomField(projectId, null);
+
+        // 基线同步需求
+        List<Map> baseLines = tbClient.queryGanttBaselineList(projectId, null).stream()
+                .filter(item -> UtilMap.getString(item, "title").toUpperCase().contains("PDCP")).collect(Collectors.toList());
+        log.info("PDCP基线, {}, {}", projectId, baseLines.size());
+        List<Map> baseLineTaskList = new ArrayList();
+        if (baseLines.size() > 0) {
+            // prd 若存在多个同名, 取最新基线 [返回值创建时间升序]
+            tbClient.queryGanttBaselineTask(UtilMap.getString(baseLines.get(baseLines.size() - 1), "id"), null, taskList -> {
+                baseLineTaskList.addAll(taskList);
+                return true;
+            });
+        }
+        log.info("基线任务, {}", baseLineTaskList.size());
+
+        // 最新同步需求
+        Map<String, String> proData = getProjectCFID(projectId, Arrays.asList(AWServer.TASK_CODE, AWServer.TASK_PRODUCT));
+        String tql = "cf:" + proData.get(AWServer.TASK_CODE) + " = " + C_TR5_03_13 + " AND (cf:" + proData.get(AWServer.TASK_PRODUCT) + " != null)" + " ORDER BY startDate DESC";
+        List<Map> taskList = tbClient.queryProjectTaskList(projectId, UtilMap.map("q", tql), null);
+        log.info("项目计划: {}, {}", C_TR5_03_13, taskList.size());
+
+        List<Map> dataList = new ArrayList<>();
+        for (Map task : taskList) {
+            String name = _getTaskFieldMap(task, customField, AWServer.TASK_PRODUCT).get(AWServer.TASK_PRODUCT);
+            // 多料号兼容
+            if (dataList.stream().filter(item -> name.equals(item.get("Name"))).findAny().isPresent()) {
+                continue;
+            }
+            Map tData = UtilMap.map("Name", name);
+            // 2. 预计α推广 (AlphaDate): 最新项目计划(非基线)里的ADCPα任务对应的计划完成时间 / TR5-03-13任务号 取最新版本
+            String date = UtilMap.getString(task, "dueDate");
+            if (UtilString.isNotBlankCompatNull(date)) {
+                tData.put("AlphaDate", UtilDateTime.formatDate(UtilDateTime.parse(date, UtilDateTime.DATE_MSEL_ISO)));
+            }
+            // 3. α推广 (AlphaDate2): 【ADCPα】对应的实际完成时间 / TR5-03-13任务号 取最新版本
+            date = UtilMap.getString(task, "accomplishTime");
+            if (UtilString.isNotBlankCompatNull(date)) {
+                tData.put("AlphaDate2", UtilDateTime.formatDate(UtilDateTime.parse(date, UtilDateTime.DATE_MSEL_ISO)));
+            }
+            // 1. α推广基线 (AlphaBaseline): PDCP basseline 基线下的ADCPα任务的计划完成时间 / PDCP basseline 基线 TR5-03-13任务号
+            Optional optional = baseLineTaskList.stream().filter(item -> task.get("id").equals(item.get("id"))).findAny();
+            if (optional.isPresent()) {
+                date = UtilMap.getString((Map) optional.get(), "planDueDate");
+                if (StringUtils.isNotBlank(date)) {
+                    tData.put("AlphaBaseline", UtilDateTime.formatDate(UtilDateTime.parse(date, UtilDateTime.DATE_MSEL_ISO)));
+                }
+            }
+            if (tData.keySet().size() >= 2) {
+                dataList.add(tData);
+                _pushCRM(projectId, tData);
+            }
+        }
+
+        tql = "cf:" + proData.get(AWServer.TASK_CODE) + " = " + C_TR6_01_11 + " AND (cf:" + proData.get(AWServer.TASK_PRODUCT) + " != null)" + " ORDER BY startDate ASC";
+        taskList = tbClient.queryProjectTaskList(projectId, UtilMap.map("q", tql), null);
+        log.info("项目计划: {}, {}", C_TR6_01_11, taskList.size());
+
+        dataList = new ArrayList<>();
+        for (Map task : taskList) {
+            String name = _getTaskFieldMap(task, customField, AWServer.TASK_PRODUCT).get(AWServer.TASK_PRODUCT);
+            // 多料号兼容
+            if (dataList.stream().filter(item -> name.equals(item.get("Name"))).findAny().isPresent()) {
+                continue;
+            }
+            Map tData = UtilMap.map("Name", name);
+            // 5. 预计正式发布 (AgentSampleDate): 最新项目计划(非基线),取最早那个产品版本的计划完成时间 / TR6-01-11任务号 最早版本
+            String date = UtilMap.getString(task, "dueDate");
+            if (UtilString.isNotBlankCompatNull(date)) {
+                tData.put("AgentSampleDate", UtilDateTime.formatDate(UtilDateTime.parse(date, UtilDateTime.DATE_MSEL_ISO)));
+            }
+            // 6. 正式发布 (ProductSendDate): 【ADCP】对应的取最早那个产品版本的实际完成时间 / TR6-01-11任务号 最早版本
+            date = UtilMap.getString(task, "accomplishTime");
+            if (UtilString.isNotBlankCompatNull(date)) {
+                tData.put("ProductSendDate", UtilDateTime.formatDate(UtilDateTime.parse(date, UtilDateTime.DATE_MSEL_ISO)));
+            }
+            // 4. 正式发布基线 (ReleaseBaseline): 取PDCPbasseline基线里的ADCP的计划完成时间,取最早那个产品版本的计划完成时间 / PDCP basseline 基线 TR6-01-11任务号
+            Optional optional = baseLineTaskList.stream().filter(item -> task.get("id").equals(item.get("id"))).findAny();
+            if (optional.isPresent()) {
+                date = UtilMap.getString((Map) optional.get(), "planDueDate");
+                if (StringUtils.isNotBlank(date)) {
+                    tData.put("ReleaseBaseline", UtilDateTime.formatDate(UtilDateTime.parse(date, UtilDateTime.DATE_MSEL_ISO)));
+                }
+            }
+            if (tData.keySet().size() >= 2) {
+                dataList.add(tData);
+                _pushCRM(projectId, tData);
+            }
+        }
+
+    }
+
+    // 推送crm: prd 单次推送必须为相同料号
+    void _pushCRM(String projectId, Map record) {
+        log.info("推送crm, {}", record);
+        Map param = UtilMap.map("username, password, client_id, client_secret, grant_type", "interface@awinic.com.cn.uat", "welcome12", "3MVG959Nd8JMmavT2IGqAtf_hIbxepsElGbOpno6AO8KdQJSpSNqY9bnLRU2exuAEh3qIXb1oTn98S9h0WWZk", "3C9FC7427866D69586964F65A16D288EBBB1544335DD9FF3A03DE21DF14A7C6A", "password");
+        String rsp = UtilHttp.doPost("https://test.salesforce.com/services/oauth2/token", null, param, UtilMap.empty());
+        Map result = (Map) JSON.parse(rsp);
+        Map header = UtilMap.map("Authorization", "OAuth " + result.get("access_token"));
+        rsp = UtilHttp.doPost(UtilMap.getString(result, "instance_url") + "/services/apexrest/TBProductStageTimeRest", header, null, Arrays.asList(record), null);
+        ydClient.operateData(YDParam.builder()
+                .formUuid(_matchFormUuid("CRM_LOG"))
+                .formDataJson(JSON.toJSONString(UtilMap.map("textareaField_lu8kinep, textareaField_lu8kineu, textField_lu8kinew", Arrays.asList(record), rsp, projectId)))
+                .build(), YDConf.FORM_OPERATION.create);
+    }
+
+    /**
+     * 全量同步crm基线
+     */
+    @Override
+    public void syncBaseLineForCRM() {
+
+        // prd 同步CRM: 项目状态为执行 & TB项目号不为空
+        YDParam ydParam = YDParam.builder()
+                .formUuid(_matchFormUuid("PROJECT"))
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_llouhiyf", "执行")))
+                .build();
+        List<Map> proList = ydService.queryFormData_all(ydParam);
+        for (Map pro : proList) {
+            String projectId = UtilMap.getString(pro, "textField_lqxtykce");
+            if (StringUtils.isBlank(projectId)) {
+                continue;
+            }
+            syncBaseLineForCRM(projectId);
+        }
+    }
+
+    // 获取项目自定义字段ID, 格式 { 名称: id } fixme 提取
+    Map<String, String> getProjectCFID(String projectId, List<String> names) {
+        List<Map> customField = tbClient.queryProjectCustomField(projectId, null);
+        List tList = customField.stream().filter(item -> names.contains(item.get("name"))).collect(Collectors.toList());
+        return (Map<String, String>) tList.stream().reduce(UtilMap.empty(), (acc, cur) -> {
+            ((Map) acc).put(((Map) cur).get("name"), ((Map) cur).get("id"));
+            return acc;
+        });
+    }
+
     /**
      * 同步预检项 [全量同步, 忽略任务号为空记录]
      */
@@ -712,9 +875,20 @@ public class AWImplClient implements AWClint {
             if (optional.isPresent()) {
                 ydParam.setFormInstanceId(UtilMap.getString((Map) optional.get(), "formInstanceId"));
                 operate = YDConf.FORM_OPERATION.update;
+                // prd 0402 兼容删除场景, 标记状态为停用, 不做实际删除
+                ((Map) optional.get()).put("status", true);
             }
             ydClient.operateData(ydParam, operate);
         }
+
+        List<Map> dataList = curList.stream().filter(item -> "启用".equals(item.get("radioField_lrnddfq6")) && !item.containsKey("status")).collect(Collectors.toList());
+        log.info(" 兼容删除场景, 标记状态为停用, {}", dataList.size());
+        dataList.forEach(item -> {
+            ydClient.operateData(YDParam.builder()
+                    .formInstanceId(UtilMap.getString(item, "formInstanceId"))
+                    .updateFormDataJson(JSON.toJSONString(UtilMap.map("radioField_lrnddfq6", "停用")))
+                    .build(), YDConf.FORM_OPERATION.update);
+        });
     }
 
     /**
@@ -763,9 +937,11 @@ public class AWImplClient implements AWClint {
         log.info("同步预检项 ##, {}", type);
     }
 
+
     @Override
     public void test() {
 
+
     }
 }
 

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

@@ -57,7 +57,7 @@ dingtalk:
   token:
   operator: ""   # OA管理员账号
 
-## aliwork
+# aliwork
 aliwork:
   appType: "APP_H7WUJTKB448F9IBDC6C4"
   systemToken: "DHA66081DN6GRFNC6GTRW5NIJS082ZF0UN9PLLF"
@@ -69,7 +69,7 @@ teambition:
   TenantId: 6034c885e71842e1e5bb5218        # 管理后台 - 企业xx - 企业ID
   OperatorId: 5e698cca21f5ad70dfba7d2b      # 公共账号, 需要有操作权限 [牧语]
 
-## dingtalk
+# dingtalk
 #dingtalk:
 #  agentId: 2848797049
 #  appKey: dingbqy1qugrihao43dl

+ 8 - 4
mjava-fengkaili/src/main/java/com/malk/fengkaili/service/impl/FKLImplService.java

@@ -238,10 +238,14 @@ public class FKLImplService implements FKLService {
                             type = "公假"; // 包含休息, 休息加班打卡, 忽略跨休息日连续请假情况, prd 钉钉后台配置: 产假, 婚假按自然日
                             day_1 = type;
                             day_2 = type;
-                        } else if (StringUtils.isBlank(result)) {
-                            type = "/"; // 新入职
-                            day_1 = "/";
-                            day_2 = "/";
+                        } else if (StringUtils.isBlank(result) || result.equals("不在考勤组并打卡")) {
+                            if (StringUtils.isBlank(result)) {
+                                type = "/"; // 新入职
+                            } else {
+                                type = "zc"; // 新入职
+                            }
+                            day_1 = type;
+                            day_2 = type;
                         } else if (result.equals("正常") || (result.split(",").length == 2 && result.contains("外勤") && result.contains("补卡")) || result.equals("下班外勤") || result.equals("上班外勤") || result.equals("上班外勤,下班外勤")) {
                             // 包含补卡, 一次外勤补卡, 外勤考勤情况 [调休会被标识为考勤正常]
                             type = "zc";

+ 11 - 6
mjava-luyi/src/main/java/com/malk/luyi/controller/LYController.java

@@ -44,7 +44,7 @@ public class LYController {
 
         log.info("报告查询, {}", data);
         McException.assertParamException_Null(data, "cardNo, page, size");
-        
+
         // prd 3.5 无手机号查询
         if (data.containsKey("phoneNo")) {
             // 更新客户档案
@@ -128,7 +128,7 @@ public class LYController {
     McR relation(HttpServletRequest request) {
 
         Map data = UtilServlet.getParamMap(request);
-        log.info("细胞查询, {}", data);
+        log.info("客户关联渠道, {}", data);
 
         if ("是".equals(data.get("type"))) {
             mcDelegate.setTimeout(() -> {
@@ -154,15 +154,20 @@ public class LYController {
     @PostMapping("test")
     McR test() {
 
-        ydClient.queryData(YDParam.builder()
-                .formUuid("FORM-MFA66S91KKJDA2708OHTW6RBOJRK3SVWO7KLL1")
-                .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_llk8hw32", "张少文")))
-                .build(), YDConf.FORM_QUERY.retrieve_search_form);
+//        ydClient.queryData(YDParam.builder()
+//                .formUuid("FORM-MFA66S91KKJDA2708OHTW6RBOJRK3SVWO7KLL1")
+//                .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_llk8hw32", "张少文")))
+//                .build(), YDConf.FORM_QUERY.retrieve_search_form);
 
 //        ydClient.queryData(YDParam.builder()
 //                .formUuid("FORM-6YA66WB1BATD2ZPWA2Z2S813SFFC3EGRBHYLLH")
 //                .searchFieldJson(JSON.toJSONString(UtilMap.map("serialNumberField_llyhc8ck", "QD-20240130-0009")))
 //                .build(), YDConf.FORM_QUERY.retrieve_search_form);
+
+
+        ydClient.operateData(YDParam.builder()
+                .formInstanceId("FINST-I1A66071U47JBCQBAS6P087BV43127WAFE9ULH0A9")
+                .build(), YDConf.FORM_OPERATION.delete);
         return McR.success();
     }
 }

+ 157 - 0
mjava-luyi/src/main/java/com/malk/luyi/controller/PJJSController.java

@@ -0,0 +1,157 @@
+package com.malk.luyi.controller;
+
+/**
+ * 错误抛出与拦截详见 CatchException
+ */
+
+import com.alibaba.fastjson.JSON;
+import com.malk.delegate.McDelegate;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.common.McR;
+import com.malk.service.aliwork.YDClient;
+import com.malk.utils.UtilMap;
+import com.malk.utils.UtilServlet;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+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 javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@RestController
+@RequestMapping()
+public class PJJSController {
+
+    @Autowired
+    private YDClient ydClient;
+    @Autowired
+    private McDelegate mcDelegate;
+
+    @SneakyThrows
+    @PostMapping("/insetXMC")
+    McR insetXMC(HttpServletRequest request) {
+        Map data = UtilServlet.getParamMap(request);
+        String UUID="";
+        String FORMID="";
+        String ZD="";
+
+
+        String XMLX=data.get("xmLx").toString();
+        if (XMLX.equals("投前项目")){
+            FORMID="textField_ltjj7pre";
+            UUID="FORM-6EC75348FD8241D989DACA4B272CD8EBXFQK";
+              ZD="associationFormField_lt1jaec6, textField_lt1jaecd, textField_lt1jaecc, selectField_lt1iahs9";
+
+        }else if (XMLX.equals("投后项目")){
+            FORMID="textField_ltjj7prg";
+            UUID="FORM-2C96908BBA4C4C8D85D2F7C2D6E8C1B9GIYC";
+            ZD="associationFormField_lt1ouopy, textField_lt1ouopz, textField_lt1ouoq2, selectField_lt1oy6qj";
+        }
+
+        log.info("项目池, {}", data);
+        if (XMLX.equals("投前项目") || XMLX.equals("投后项目")) {
+            List<Map> associations = YDConf.associationForm("APP_G951QZ32AUJNJUE4G127", "FORM-225404C233714BD0B77A2EF675AEF29A1VVA", data.get("xmcId").toString(), data.get("xmcLsh").toString(),null, false);
+            log.info("associations: {}", associations);
+            log.info("UUID, {}", UUID);
+            log.info("ZD, {}", ZD);
+             String tquid=   ydClient.operateData(YDParam.builder()
+                     .appType("APP_G951QZ32AUJNJUE4G127")
+                     .systemToken("HOA66I8176RID79L6QF3554SPPYH22VTBH1TLX6")
+                        .formUuid(UUID)
+                        .formDataJson(JSON.toJSONString(UtilMap.map(ZD, associations,data.get("xmName"),data.get("xmcLsh") , XMLX )))
+                        .build(), YDConf.FORM_OPERATION.create).toString();////
+                if (tquid!=""){
+                    ydClient.operateData(YDParam.builder()
+                            .appType("APP_G951QZ32AUJNJUE4G127")
+                            .systemToken("HOA66I8176RID79L6QF3554SPPYH22VTBH1TLX6")
+                            .formInstanceId(String.valueOf(data.get("xmcId")))
+                            .updateFormDataJson(JSON.toJSONString(UtilMap.map(FORMID,tquid )))
+                            .build(), YDConf.FORM_OPERATION.update);
+                }
+
+
+
+
+
+        }
+        return McR.success();
+    }
+    @SneakyThrows
+    @PostMapping("/insetTHXM")
+    McR insetTHXM(HttpServletRequest request) {
+
+        Map data = UtilServlet.getParamMap(request);
+        log.info("data:"+data);
+        if (data.get("SFZJ").toString().equals("否")){
+
+        List<Map> dataList = (List<Map>) ydClient.queryData(YDParam.builder()
+                .appType("APP_G951QZ32AUJNJUE4G127")
+                .systemToken("HOA66I8176RID79L6QF3554SPPYH22VTBH1TLX6")
+                .formUuid("FORM-225404C233714BD0B77A2EF675AEF29A1VVA")
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("serialNumberField_lt1i52jt",data.get("XMBH").toString())))
+                .build(), YDConf.FORM_QUERY.retrieve_search_form).getData();
+        if (dataList!=null && dataList.size()>0){
+            log.info("dataList:"+dataList);
+            String XMCSLID=dataList.get(0).get("formInstanceId").toString();
+            String  FORMID="textField_ltjj7prg";
+            String  UUID="FORM-2C96908BBA4C4C8D85D2F7C2D6E8C1B9GIYC";
+            String  ZD="associationFormField_lt1ouopy, textField_lt1ouopz, textField_lt1ouoq2, selectField_lt1oy6qj";
+            List<Map> associations = YDConf.associationForm("APP_G951QZ32AUJNJUE4G127", "FORM-225404C233714BD0B77A2EF675AEF29A1VVA", XMCSLID, data.get("XMBH").toString(),null, false);
+            String tquid=   ydClient.operateData(YDParam.builder()
+                    .appType("APP_G951QZ32AUJNJUE4G127")
+                    .systemToken("HOA66I8176RID79L6QF3554SPPYH22VTBH1TLX6")
+                    .formUuid(UUID)
+                    .formDataJson(JSON.toJSONString(UtilMap.map(ZD, associations,data.get("XMMC"),data.get("XMBH") , "投后项目" )))
+                    .build(), YDConf.FORM_OPERATION.create).toString();////
+            if (tquid!=""){
+                ydClient.operateData(YDParam.builder()
+                        .appType("APP_G951QZ32AUJNJUE4G127")
+                        .systemToken("HOA66I8176RID79L6QF3554SPPYH22VTBH1TLX6")
+                        .formInstanceId(String.valueOf(XMCSLID))
+                        .updateFormDataJson(JSON.toJSONString(UtilMap.map(FORMID,tquid )))
+                        .build(), YDConf.FORM_OPERATION.update);
+            }
+        }
+        log.info("request, {}", dataList);
+        }
+
+        ///
+//        String tquid=   ydClient.operateData(YDParam.builder()
+//                .appType("APP_G951QZ32AUJNJUE4G127")
+//                .systemToken("HOA66I8176RID79L6QF3554SPPYH22VTBH1TLX6")
+//                .formUuid(UUID)
+//                .formDataJson(JSON.toJSONString(UtilMap.map(ZD, associations,data.get("xmName"),data.get("xmcLsh") , XMLX )))
+//                .build(), YDConf.FORM_OPERATION.create).toString();////
+//        ydClient.operateData(YDParam.builder()
+//                .appType("APP_G951QZ32AUJNJUE4G127")
+//                .systemToken("HOA66I8176RID79L6QF3554SPPYH22VTBH1TLX6")
+//                .formInstanceId(String.valueOf(data.get("xmcId")))
+//                .updateFormDataJson(JSON.toJSONString(UtilMap.map("textField_ltjj7prg",tquid )))
+//                .build(), YDConf.FORM_OPERATION.update);
+
+//        ydClient.operateData(YDParam.builder()
+//                .appType("APP_G951QZ32AUJNJUE4G127")
+//                .systemToken("HOA66I8176RID79L6QF3554SPPYH22VTBH1TLX6")
+//                .formInstanceId(String.valueOf(data.get("xmcId")))
+//                .updateFormDataJson(JSON.toJSONString(UtilMap.map(FORMID,tquid )))
+//                .build(), YDConf.FORM_OPERATION.update);
+
+
+        //mapList
+
+        return McR.success();
+    }
+
+
+
+
+
+
+
+}

+ 1 - 1
mjava/src/main/java/com/malk/server/aliwork/YDConf.java

@@ -52,7 +52,7 @@ public class YDConf {
         retrieve_list_all,          // 全局查询, 包含子表单
         multi_retrieve_id,
 
-        retrieve_id,                // 单个ID查询
+        retrieve_id,                // 单个ID查询 todo 若秘钥不匹配, 返回空, 添加报错说明
 
         retrieve_search_process,            // 流程列表
         retrieve_search_form,               // 表单列表

+ 1 - 0
mjava/src/main/java/com/malk/service/aliwork/YDService.java

@@ -11,6 +11,7 @@ public interface YDService {
 
     // todo: 批量创建接口异常; 查询子表超过50自动查询全量; 查询表单全部数据, 避免第一次重复查询优化, 添加直接返回formData, 兼容formInstId;  upsert方法
 
+    // todo 0402 批量更新 / 删除
 
     /**
      * 操作数据 [异步]

+ 9 - 2
mjava/src/main/java/com/malk/service/teambition/TBClient.java

@@ -133,7 +133,7 @@ public interface TBClient {
     /**
      * 查询项目任务
      *
-     * @param param:q ppExt TQL 格式 cf:查询项目字段ID = TR1-01
+     * @param param:q ppExt TQL 格式 "cf:项目字段ID" + ORDER BY + AND + OR [不支持小写]
      * @apiNote API: https://open.teambition.com/docs/apis/6321c6d1912d20d3b5a49ec1
      * @apiNote TQL: https://open.teambition.com/docs/documents/639982966b99d5002b510f0b
      */
@@ -213,7 +213,14 @@ public interface TBClient {
      *
      * @apiNote https://open.teambition.com/docs/apis/6424f885912d20d3b50b759b
      */
-    List<Map> queryGanttBaseline(String projectId, Map body);
+    List<Map> queryGanttBaselineList(String projectId, Map body);
+
+    /**
+     * 获取甘特图基线下的任务 ppExt: 创建基线时候会记录当时「项目下有开始时间或截止时间的任务」,若没有这样的任务,基线内不会保存 [基线下任务Id, 是相同的, 仅保留了任务标题和时间]
+     *
+     * @apiNote https://open.teambition.com/docs/apis/6424f857912d20d3b50b048a
+     */
+    List<Map> queryGanttBaselineTask(String baselineId, Map param, QueryAll lambda);
 
     /**
      * 查询项目分组 [ppExt: TB分组有继承关系,传递当前最后一层即可]

+ 20 - 1
mjava/src/main/java/com/malk/service/teambition/impl/TBClientImpl.java

@@ -303,13 +303,32 @@ public class TBClientImpl implements TBClient {
     }
 
     @Override
-    public List<Map> queryGanttBaseline(String projectId, Map body) {
+    public List<Map> queryGanttBaselineList(String projectId, Map body) {
         body = UtilMap.put(body, "pageSize", TBConf.PAGE_SIZE_LIMIT);
         body.put("projectId", projectId);
         TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/v3/gantt/baseline", initHeaderToken(), body, TBR.class);
         return (List<Map>) tbr.getResult();
     }
 
+    @Override
+    public List<Map> queryGanttBaselineTask(String baselineId, Map param, QueryAll lambda) {
+        param = UtilMap.put(param, "pageSize", 10);
+        param.put("baselineId", baselineId);
+        TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/v3/gantt/baseline-task", initHeaderToken(), param, TBR.class);
+        List<Map> dataList = (List<Map>) tbr.getResult();
+        if (ObjectUtil.isNotNull(lambda)) {
+            boolean isAll = lambda.dataList(dataList);
+            if (!isAll) {
+                return dataList;
+            }
+            if (StringUtils.isNotBlank(tbr.getNextPageToken())) {
+                param.put("pageToken", tbr.getNextPageToken());
+                queryGanttBaselineTask(baselineId, param, lambda);
+            }
+        }
+        return dataList;
+    }
+
     @Override
     public List<Map> queryTagList(Map param) {
         param = UtilMap.put(param, "pageSize", TBConf.PAGE_SIZE_LIMIT);

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

@@ -24,7 +24,7 @@ public abstract class UtilDateTime {
     public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
     /// ISO-8601的date-time格式: T表示分隔符,Z表示的是UTC
     public final static String DATE_TIME_ISO = "yyyy-MM-dd'T'HH:mm:ssZ";
-    public final static String DATE_MSEL_ISO = "yyyy-MM-dd'T'HH:mm:SSSZ";
+    public final static String DATE_MSEL_ISO = "yyyy-MM-dd'T'HH:mm:ss.SSS"; // TB
 
 
     //// convert ////

+ 4 - 0
mjava/src/main/java/com/malk/utils/UtilHttp.java

@@ -93,6 +93,10 @@ public abstract class UtilHttp {
 
     /*** ------------ 创建POST请求 ------------ ***/
 
+    public static String doPost(String url, Map header, Map<String, Object> param, Object body, Map form) {
+        return doRequest(METHOD.POST, url, header, param, body, form, null, null); // 兼容非 map
+    }
+
     public static String doPost(String url, Map header, Map<String, Object> param, Map body, Map form) {
         return doRequest(METHOD.POST, url, header, param, body, form);
     }