pruple_boy 7 ay önce
ebeveyn
işleme
61927f96cd

+ 76 - 13
mjava-aiwei/src/main/java/com/malk/aiwei/controller/TBxYDController.java

@@ -5,7 +5,6 @@ package com.malk.aiwei.controller;
  */
 
 import com.alibaba.fastjson.JSON;
-import com.malk.aiwei.server.AWServer;
 import com.malk.aiwei.service.AWClint;
 import com.malk.aiwei.service.AWYDClient;
 import com.malk.delegate.McDelegate;
@@ -380,6 +379,7 @@ public class TBxYDController {
     /**
      * 覆盖项目需求档案需求列表 tmp
      */
+    @Deprecated
     @PostMapping("pro-req/archive")
     McR archiveProjectRequirements(HttpServletRequest request) {
 
@@ -394,7 +394,7 @@ public class TBxYDController {
      * 项目规格子表导出
      */
     @PostMapping("pro-spec/export")
-    void exportSpecification(@RequestBody Map data, HttpServletResponse response) {
+    void exportProjectSpecification(@RequestBody Map data, HttpServletResponse response) {
 
         log.info("项目规格子表导出, {}", JSON.toJSONString(data));
         McException.assertParamException_Null(data, "dataList", "fileName");
@@ -425,7 +425,7 @@ public class TBxYDController {
     }
 
     /**
-     * 项规格审批通过
+     * 项规格审批通过
      */
     @PostMapping("pro-spec/approve")
     McR approveProjectSpecification(HttpServletRequest request) {
@@ -437,15 +437,75 @@ public class TBxYDController {
         return McR.success();
     }
 
+    /**
+     * 测试规格子表导出
+     */
+    @PostMapping("test-spec/export")
+    void exportTestSpecification(@RequestBody Map data, HttpServletResponse response) {
+
+        log.info("测试规格子表导出, {}", JSON.toJSONString(data));
+        McException.assertParamException_Null(data, "dataList", "fileName");
+
+        List<Map> dataList = UtilMap.getList(data, "dataList");
+        this._multiSelectCompatibility(dataList);
+        String fileName = UtilMap.getString(data, "fileName"); // 测试规格维护
+        UtilExcel.exportMapAndListByTemplate(response, dataList, Map.class, fileName, "templates_test_specification.xlsx");
+    }
+
+    /**
+     * 测试规格子表导入
+     */
+    @PostMapping("test-spec/import")
+    McR importTESTSpecification(@RequestBody Map data, HttpServletResponse response) {
+
+        log.info("测试规格子表导入, {}", JSON.toJSONString(data));
+        McException.assertParamException_Null(data, "charter", "dataList", "fileName", "prList");
+        Map result = awydClient.checkImportData_testSpecification(UtilMap.getString(data, "charter"), UtilMap.getList(data, "dataList"), UtilMap.getList(data, "prList"), UtilMap.getBoolean(data, "isSubmit"));
+        List<Map> dataList = UtilMap.getList(result, "dataList");
+        if (UtilMap.getBoolean(result, "isSuccess")) {
+            return McR.success(dataList);
+        }
+        this._multiSelectCompatibility(dataList);
+        String fileName = UtilMap.getString(data, "fileName"); // 需求维护
+        UtilExcel.exportMapAndListByTemplate(response, dataList, Map.class, fileName, "templates_test_specification_exception.xlsx");
+        return McR.errorAccess("数据校验失败");
+    }
+
+    /**
+     * 测试规格审批通过
+     */
+    @PostMapping("test-spec/approve")
+    McR approveTestSpecification(HttpServletRequest request) {
+
+        Map<String, ?> data = UtilServlet.getParamMap(request);
+        log.info("测试格审批通过, {}", JSON.toJSONString(data));
+        McException.assertParamException_Null(data, "instanceId");
+        awydClient.dealApprovedData_testSpecification(UtilMap.getString(data, "instanceId"), data);
+        return McR.success();
+    }
+
     /**
      * 提供verifier数据读取服务
      */
     @GetMapping("verifier/sync")
-    McR syncVerifier(@RequestParam String projectCode) {
+    McR syncVerifier(@RequestParam String projectCode, String productCode, String productVersion) {
+
+        Map result = awydClient.syncVerifier(projectCode, productCode, productVersion);
+        log.info("syncVerifier, {}, {}, {}", projectCode, productCode, productVersion);
+        return McR.success(result);
+    }
+
+    /**
+     * 提供verifier数据读取服务
+     */
+    @PostMapping("verifier/back")
+    McR backVerifier(@RequestBody Map data) {
 
-        List<Map> dataList = awydClient.syncVerifier(projectCode);
-        log.info("syncVerifier, {}, {}", projectCode, dataList.size());
-        return McR.success(dataList);
+        log.info("backVerifier, {}", data);
+        McException.assertParamException_Null(data, "code", "data");
+
+        awydClient.backVerifier(data);
+        return McR.success();
     }
 
     /**
@@ -519,12 +579,15 @@ public class TBxYDController {
         Map<String, ?> data = UtilServlet.getParamMap(request);
         log.info("test, {}", data);
 
-        try {
-            Map body = TBConf.assembleCustomFieldName(AWServer.TASK_TRANSMIT, "已下达");
-            tbClient.updateTaskCustomField("659a681d44ade3345fdc0d39", tbConf.getOperatorId(), body);
-        } catch (McException e) {
-            log.error(e.getMessage(), e);
-        }
+
+        awClint.taskLevelValidate(UtilMap.map("projectId, taskId", "659376ba950780b816c33d3b, 66116073ea68df0235b30bef"));
+
+//        try {
+//            Map body = TBConf.assembleCustomFieldName(AWServer.TASK_TRANSMIT, "已下达");
+//            tbClient.updateTaskCustomField("659a681d44ade3345fdc0d39", tbConf.getOperatorId(), body);
+//        } catch (McException e) {
+//            log.error(e.getMessage(), e);
+//        }
 
 //        awClint.test();
 //        awydClient.test();

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

@@ -33,10 +33,11 @@ public class TBDelegate implements TBEvent {
 
         if ("v3.task.taskflowstatus.update".equals(eventName)) {
             JSONObject data = eventJson.getJSONObject("data");
+            awClint.taskLevelValidate(data);
             awClint.doApprove(data, false);
-        }else  if ("v3.task.customfield.update".equals(eventName)) {
+        } else if ("v3.task.customfield.update".equals(eventName)) {
             JSONObject data = eventJson.getJSONObject("data");
-            System.out.println("1"+JSONObject.toJSONString(data));
+            System.out.println("1" + JSONObject.toJSONString(data));
             awClint.custFieldUpdate(data);
         }
     }

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

@@ -119,6 +119,10 @@ public interface AWClint {
      */
     List<Map> exportCheckList(String pCode, String proType);
 
+    /**
+     * 任务NA校验, 一二级不允许NA
+     */
+    void taskLevelValidate(Map data);
 
     void test();
 

+ 16 - 1
mjava-aiwei/src/main/java/com/malk/aiwei/service/AWYDClient.java

@@ -9,7 +9,12 @@ public interface AWYDClient {
     /**
      * 提供verifier数据读取服务
      */
-    List<Map> syncVerifier(String projectCode);
+    Map syncVerifier(String projectCode, String productCode, String productVersion);
+
+    /**
+     * 提供verifier数据回传服务
+     */
+    void backVerifier(Map data);
 
     /**
      * 项目需求导入校验
@@ -36,6 +41,16 @@ public interface AWYDClient {
      */
     void dealApprovedData_projectSpecification(String instanceId, Map data);
 
+    /**
+     * 测试规格导入校验
+     */
+    Map checkImportData_testSpecification(String charter, List<Map> dataList, List<String> prList, boolean isSubmit);
+
+    /**
+     * 测试规格审批通过后, 回写规格编号与关联表单
+     */
+    void dealApprovedData_testSpecification(String instanceId, Map data);
+
     
     void test();
 

+ 44 - 1
mjava-aiwei/src/main/java/com/malk/aiwei/service/impl/AWImplClient.java

@@ -372,7 +372,8 @@ public class AWImplClient implements AWClint {
         return String.valueOf(customFlowStatus.get(0).get("id"));
     }
 
-    // 前置过滤 fixme 提取方法
+    // 前置过滤 fixme 提取方法, 优化为单个查询, 处理保密模糊匹配
+    @Deprecated
     List<String> _getWorkFlowStatusList(String projectId, String... workFlowStatusNames) {
         List<String> workFlowStatusList = new ArrayList<>();
         for (String name : workFlowStatusNames) {
@@ -1950,6 +1951,48 @@ public class AWImplClient implements AWClint {
         return checkedList;
     }
 
+    /**
+     * 任务NA校验, 一二级不允许NA
+     */
+    @Override
+    public void taskLevelValidate(Map data) {
+
+        String taskId = UtilMap.getString(data, "taskId");
+        String projectId = UtilMap.getString(data, "projectId");
+
+        List<String> workIds = _getWorkFlowStatusList(projectId, Arrays.asList("N/A", "NA"));
+        if (workIds.contains(UtilMap.getString(data, "tfsId"))) {
+
+            Map taskData = tbClient.queryTaskDetail(taskId, "", "").get(0);
+            String parentTaskId = UtilMap.getString(taskData, "parentTaskId");
+            if (StringUtils.isNotBlank(parentTaskId)) {
+                Map parentTaskData = tbClient.queryTaskDetail(parentTaskId, "", "").get(0);
+                parentTaskId = UtilMap.getString(parentTaskData, "parentTaskId");
+                if (StringUtils.isBlank(parentTaskId)) {
+                    resetWorkFlow(projectId, taskId);
+                }
+            } else {
+                resetWorkFlow(projectId, taskId);
+            }
+        }
+    }
+
+    /// 重置任务状态
+    void resetWorkFlow(String projectId, String taskId) {
+        List<String> workIds = _getWorkFlowStatusList(projectId, AWServer.WORKFLOW_INITIAL);
+        for (String workFlowId : workIds) {
+            boolean isSuccess = false;
+            try {
+                tbClient.updateTaskFlowStatus(taskId, tbConf.getOperatorId(), workFlowId, "一二级任务不允许N/A");
+                isSuccess = true;
+            } catch (McException e) {
+
+            }
+            if (isSuccess) {
+                break;
+            }
+        }
+    }
 
     /************************************* ------------------- *************************************/
 

+ 213 - 14
mjava-aiwei/src/main/java/com/malk/aiwei/service/impl/AWYDImplClient.java

@@ -301,6 +301,7 @@ public class AWYDImplClient implements AWYDClient {
      * 覆盖项目需求档案需求列表 tmp
      */
     @Override
+    @Deprecated
     public void archiveProjectRequirements(String pCode, String instanceId) {
 
         // 项目需求数据
@@ -373,10 +374,17 @@ public class AWYDImplClient implements AWYDClient {
                 isSuccess = false;
                 continue;
             }
+            // prd 10.17 [目前仅同步功能、性能需求] 测试规格调整,只校验功能、性能需求,也只是同步只写入规格库
+            if (!Arrays.asList("功能需求(FUNC)", "性能需求(PFM)").contains(row.get("selectField_lronu2g3"))) {
+                row.put("selectField_m10k093c", "成功");
+                row.put("textareaField_m10k093a", "规格导入仅校验功能需求、性能需求强关联");
+                continue;
+            }
+
             String code = UtilMap.getString(row, "textField_m18x4yqo");
             if (StringUtils.isNotBlank(code)) {
                 // 重复需求+规格编号校验
-                String uCode = UtilMap.getString(row, "textField_m0vvv72j") + "-" + UtilMap.getString(row, "textField_m18x4yqo");
+                String uCode = UtilMap.getString(row, "textField_m0vvv72j") + "_" + UtilMap.getString(row, "textField_m18x4yqo");
                 if (psList.contains(uCode)) {
                     row.put("selectField_m10k093c", "失败");
                     row.put("textareaField_m10k093a", uCode + "需求+规格在当前列表已存在, 请检查数据后重新导入");
@@ -390,19 +398,17 @@ public class AWYDImplClient implements AWYDClient {
                     List<Map> tList = (List<Map>) ydClient.queryData(YDParam.builder()
                             .appType(appType_ps)
                             .systemToken(systemToken_ps)
-//                            .formUuid("FORM-C1BB004F74C1492DB3F568EA8A7A951C7NBY")
                             .formUuid("FORM-F5FD52E311514AC890D4B308CFA8E8A8BH8J")
                             .searchCondition(JSON.toJSONString(searchCondition))
                             .build(), YDConf.FORM_QUERY.retrieve_list).getData();
                     if (tList.size() > 0) {
                         Map formData = UtilMap.getMap(tList.get(0), "formData");
-                        row.put("serialNumberField_lti2e639", formData.get("serialNumberField_lti2e639")); // 赋值规格编号
+                        row.put("textField_m18x4yqo", formData.get("serialNumberField_lti2e639")); // 赋值规格编号
                         // 匹配规格编号, 条件内数据以原始数据覆盖导入数据
                         for (Object key : condition.keySet()) {
                             row.put(key, UtilMap.getString(formData, String.valueOf(key)));
                         }
-//                        row.put("associationFormField_m18x4yqq", YDConf.associationForm(appType_pr, "FORM-C1BB004F74C1492DB3F568EA8A7A951C7NBY", UtilMap.getString(tList.get(0), "formInstanceId"), code, "", false));
-                        row.put("associationFormField_m18x4yqq", YDConf.associationForm(appType_pr, "FORM-F5FD52E311514AC890D4B308CFA8E8A8BH8J", UtilMap.getString(tList.get(0), "formInstanceId"), code, "", false));
+                        row.put("associationFormField_m18x4yqq", YDConf.associationForm(appType_ps, "FORM-F5FD52E311514AC890D4B308CFA8E8A8BH8J", UtilMap.getString(tList.get(0), "formInstanceId"), code, "", false));
                         row.put("selectField_m10k093c", "引用");
                         row.put("textareaField_m10k093a", "已匹配规格编号, 条件内数据以原始数据覆盖导入数据");
                         continue;
@@ -430,7 +436,6 @@ public class AWYDImplClient implements AWYDClient {
                 List<Map> tList = (List<Map>) ydClient.queryData(YDParam.builder()
                         .appType(appType_ps)
                         .systemToken(systemToken_ps)
-//                        .formUuid("FORM-C1BB004F74C1492DB3F568EA8A7A951C7NBY")
                         .formUuid("FORM-F5FD52E311514AC890D4B308CFA8E8A8BH8J")
                         .searchCondition(JSON.toJSONString(searchCondition))
                         .build(), YDConf.FORM_QUERY.retrieve_list).getData();
@@ -439,8 +444,7 @@ public class AWYDImplClient implements AWYDClient {
                     String rCode = UtilMap.getString(formData, "serialNumberField_lti2e639");
                     row.put("selectField_m10k093c", "替换");
                     row.put("textareaField_m10k093a", code + "规格已替换, " + UtilMap.getString(formData, "serialNumberField_lti2e639") + "规格库中相同条件规格");
-//                    row.put("associationFormField_m18x4yqq", YDConf.associationForm(appType_pr, "FORM-C1BB004F74C1492DB3F568EA8A7A951C7NBY", UtilMap.getString(tList.get(0), "formInstanceId"), rCode, "", false));
-                    row.put("associationFormField_m18x4yqq", YDConf.associationForm(appType_pr, "FORM-F5FD52E311514AC890D4B308CFA8E8A8BH8J", UtilMap.getString(tList.get(0), "formInstanceId"), rCode, "", false));
+                    row.put("associationFormField_m18x4yqq", YDConf.associationForm(appType_ps, "FORM-F5FD52E311514AC890D4B308CFA8E8A8BH8J", UtilMap.getString(tList.get(0), "formInstanceId"), rCode, "", false));
                     row.put("textField_m18x4yqo", rCode);
                 } else {
                     row.put("selectField_m10k093c", "新增");
@@ -468,7 +472,6 @@ public class AWYDImplClient implements AWYDClient {
             details = ydService.queryDetails(YDParam.builder()
                     .appType(appType_ps)
                     .systemToken(systemToken_ps)
-//                    .formUuid("FORM-2C5D914DC3974745AA6F55CB916E82FEH5UA")
                     .formUuid("FORM-A1809FBB82E54CB387B372E3B15457E7IOVV")
                     .formInstanceId(ddr_new.getFormInstId())
                     .tableFieldId("tableField_lt2d2x78")
@@ -483,7 +486,8 @@ public class AWYDImplClient implements AWYDClient {
             detail.put("associationFormField_lvy7yjq8", YDConf.associationForm(UtilMap.getString(detail, "associationFormField_lvy7yjq8_id")));
 
             boolean isCreate = "新增".equals(detail.get("selectField_m10k093c"));
-            if (!isCreate) {
+            // prd 10.17 规格仅校验功能、性能需求, 规格可能为空
+            if (!isCreate && UtilMap.isNotBlankString(detail, "associationFormField_m18x4yqq_id")) {
                 detail.put("associationFormField_m18x4yqq", YDConf.associationForm(UtilMap.getString(detail, "associationFormField_m18x4yqq_id")));
                 continue;
             }
@@ -491,7 +495,6 @@ public class AWYDImplClient implements AWYDClient {
             detail.putAll(UtilMap.map("selectField_lwq7bv4z, selectField_lwq7bv51", "charter, pdt", data));
             detail.put("radioField_lti2e636", "启用");
             String formInstId = (String) ydClient.operateData(YDParam.builder()
-//                    .formUuid("FORM-C1BB004F74C1492DB3F568EA8A7A951C7NBY")
                     .formUuid("FORM-F5FD52E311514AC890D4B308CFA8E8A8BH8J")
                     .appType(appType_ps)
                     .systemToken(systemToken_ps)
@@ -504,7 +507,6 @@ public class AWYDImplClient implements AWYDClient {
                     .formInstanceId(formInstId)
                     .build(), YDConf.FORM_QUERY.retrieve_id).getFormData();
             String rCode = UtilMap.getString(crData, "serialNumberField_lti2e639");
-//            detail.put("associationFormField_lvy7yjq8", YDConf.associationForm(appType_ps, "FORM-C1BB004F74C1492DB3F568EA8A7A951C7NBY", formInstId, rCode, "", false));
             detail.put("associationFormField_m18x4yqq", YDConf.associationForm(appType_ps, "FORM-F5FD52E311514AC890D4B308CFA8E8A8BH8J", formInstId, rCode, "", false));
             detail.put("textField_m18x4yqo", rCode);
             detail.put("selectField_m10k093c", "已新增");  // 兼容重复调用
@@ -520,11 +522,187 @@ public class AWYDImplClient implements AWYDClient {
         }
     }
 
+
+    /// 产品规格
+    String appType_ts = "APP_LBOBROO4GOXH63MIROTP";
+    String systemToken_ts = "GNC66E911CRLB46HBG0AWCC3ZUWZ1QDYP2AXLPF";
+
+    /**
+     * 测试规格导入校验 [10.15 更新版本] fixme 复制\简化无层级项目需求实现逻辑
+     */
+    @Override
+    public Map checkImportData_testSpecification(String charter, List<Map> dataList, List<String> prList, boolean isSubmit) {
+
+        boolean isSuccess = true;
+        List<String> psList = new ArrayList<>();
+
+        // ‒ 匹配条件,仅规格编号、分类、类型、子类、描述、条件不可修改,作为唯一条件,其他信息作为bom属性可维护
+        Map condition = UtilMap.map("textField_m2b2mkvb, textField_m2b2mkvc, textField_m2b2mkvd", "测试类型", "验收项", "测试条件");
+        for (Map row : dataList) {
+            // 清理导入状态标志
+            row.put("selectField_m10k093c", "");
+            row.put("textareaField_m10k093a", "");
+
+            // ppExt 测试规格库校验逻辑: 1. 需求+规格编号不能为空且必须在需求列表中, 2.需求\规格相关数据不修改, 只引用测试规格内容 [解决前端关联表单导出再导入后丢失问题]
+            String prsCode = UtilMap.getString(row, "textField_m0vvv72j") + "_" + UtilMap.getString(row, "textField_m18x4yqo");
+            if (!prList.contains(prsCode)) {
+                row.put("selectField_m10k093c", "失败");
+                row.put("textareaField_m10k093a", prsCode + "需求+规格在列表中不存在, 请检查数据后重新导入");
+                isSuccess = false;
+                continue;
+            }
+            // prd 10.17 [目前仅同步功能、性能需求] 测试规格调整,只校验功能、性能需求,也只是同步只写入规格库
+            if (!Arrays.asList("功能需求(FUNC)", "性能需求(PFM)").contains(row.get("selectField_lronu2g3"))) {
+                row.put("selectField_m10k093c", "成功");
+                row.put("textareaField_m10k093a", "规格导入仅校验功能需求、性能需求强关联");
+                continue;
+            }
+            String code = UtilMap.getString(row, "textField_m2b2vx5x");
+            if (StringUtils.isNotBlank(code)) {
+                // 重复需求+规格编号+c测试规格校验
+                String uCode = UtilMap.getString(row, "textField_m0vvv72j") + "_" + UtilMap.getString(row, "textField_m18x4yqo") + "_" + UtilMap.getString(row, "textField_m2b2vx5x");
+                if (psList.contains(uCode)) {
+                    row.put("selectField_m10k093c", "失败");
+                    row.put("textareaField_m10k093a", uCode + "需求+规格+测试规格在当前列表已存在, 请检查数据后重新导入");
+                    isSuccess = false;
+                    continue;
+                }
+                psList.add(uCode);
+
+                if (!isSubmit) {
+                    List<Map> searchCondition = Arrays.asList(YDConf.searchCondition_TextFiled("serialNumberField_m2b2mkva", code, "eq"));
+                    List<Map> tList = (List<Map>) ydClient.queryData(YDParam.builder()
+                            .appType(appType_ts)
+                            .systemToken(systemToken_ts)
+                            .formUuid("FORM-767ECC0F8CCE4FB0BC48ECC63164F297BS77")
+                            .searchCondition(JSON.toJSONString(searchCondition))
+                            .build(), YDConf.FORM_QUERY.retrieve_list).getData();
+                    if (tList.size() > 0) {
+                        Map formData = UtilMap.getMap(tList.get(0), "formData");
+                        row.put("textField_m2b2vx5x", formData.get("serialNumberField_m2b2mkva")); // 赋值规格编号
+                        // 匹配规格编号, 条件内数据以原始数据覆盖导入数据
+                        for (Object key : condition.keySet()) {
+                            row.put(key, UtilMap.getString(formData, String.valueOf(key)));
+                        }
+                        row.put("associationFormField_lt6zl2fh", YDConf.associationForm(appType_ts, "FORM-767ECC0F8CCE4FB0BC48ECC63164F297BS77", UtilMap.getString(tList.get(0), "formInstanceId"), code, "", false));
+                        row.put("selectField_m10k093c", "引用");
+                        row.put("textareaField_m10k093a", "已匹配规格编号, 条件内数据以原始数据覆盖导入数据");
+                        continue;
+                    }
+                }
+            }
+            // 控制匹配条件 [ prd: 同时兼容, 有编号未匹配到数据, 通过填写内容识别 ]
+            for (Object prop : condition.keySet()) {
+                if (UtilMap.isBlankString(row, String.valueOf(prop))) {
+                    row.put("selectField_m10k093c", "失败");
+                    row.put("textareaField_m10k093a", condition.get(prop) + ", 请检查数据后重新导入");
+                    break;
+                }
+            }
+            if (UtilMap.isNotBlankString(row, "textareaField_m10k093a")) {
+                isSuccess = false;
+                continue;
+            }
+            if (!isSubmit) {
+                // 若有,执行替换逻辑, 备注体现原始被替换编号
+                List<Map> searchCondition = new ArrayList<>();
+                for (Object key : condition.keySet()) {
+                    searchCondition.add(YDConf.searchCondition_TextFiled(String.valueOf(key), row.get(key), "eq"));
+                }
+                List<Map> tList = (List<Map>) ydClient.queryData(YDParam.builder()
+                        .appType(appType_ts)
+                        .systemToken(systemToken_ts)
+                        .formUuid("FORM-767ECC0F8CCE4FB0BC48ECC63164F297BS77")
+                        .searchCondition(JSON.toJSONString(searchCondition))
+                        .build(), YDConf.FORM_QUERY.retrieve_list).getData();
+                if (tList.size() > 0) {
+                    Map formData = UtilMap.getMap(tList.get(0), "formData");
+                    String rCode = UtilMap.getString(formData, "serialNumberField_m2b2mkva");
+                    row.put("selectField_m10k093c", "替换");
+                    row.put("textareaField_m10k093a", code + "规格已替换, " + UtilMap.getString(formData, "serialNumberField_m2b2mkva") + "规格库中相同条件规格");
+                    row.put("associationFormField_lt6zl2fh", YDConf.associationForm(appType_ts, "FORM-767ECC0F8CCE4FB0BC48ECC63164F297BS77", UtilMap.getString(tList.get(0), "formInstanceId"), rCode, "", false));
+                    row.put("textField_m18x4yqo", rCode);
+                } else {
+                    row.put("selectField_m10k093c", "新增");
+                    row.put("textareaField_m10k093a", "测试规格编号不存在, 评审通过后测试规格库新增");
+                }
+            }
+        }
+        return UtilMap.map("isSuccess, dataList", isSuccess, dataList);
+    }
+
+    /**
+     * 测试规格审批通过后, 回写规格编号与关联表单
+     */
+    @Override
+    public void dealApprovedData_testSpecification(String instanceId, Map data) {
+        // 项目需求数据
+        DDR_New ddr_new = ydClient.queryData(YDParam.builder()
+                .appType(appType_ts)
+                .systemToken(systemToken_ts)
+                .formInstanceId(instanceId)
+                .build(), YDConf.FORM_QUERY.retrieve_id);
+        Map formData = ddr_new.getFormData();
+        List<Map> details = (List<Map>) formData.get("tableField_lt2d2x78");
+        if (details.size() == YDConf.PAGE_SIZE_DETAILS) {
+            details = ydService.queryDetails(YDParam.builder()
+                    .appType(appType_ts)
+                    .systemToken(systemToken_ts)
+                    .formUuid("FORM-A0AC3FDEF14B43D8A117A4077128AFB6EX2S")
+                    .formInstanceId(ddr_new.getFormInstId())
+                    .tableFieldId("tableField_lt2d2x78")
+                    .build());
+        }
+
+        boolean isUpdate = false;
+        for (Map detail : details) {
+            // 需求库\规格库
+            detail.put("associationFormField_lvy7yjq8", YDConf.associationForm(UtilMap.getString(detail, "associationFormField_lvy7yjq8_id")));
+            // prd 10.17 规格仅校验功能、性能需求, 规格可能为空
+            if (UtilMap.isNotBlankString(detail, "associationFormField_m18x4yqq_id")) {
+                detail.put("associationFormField_m18x4yqq", YDConf.associationForm(UtilMap.getString(detail, "associationFormField_m18x4yqq_id")));
+            }
+            boolean isCreate = "新增".equals(detail.get("selectField_m10k093c"));
+            if (!isCreate && UtilMap.isNotBlankString(detail, "associationFormField_lt6zl2fh_id")) {
+                detail.put("associationFormField_lt6zl2fh", YDConf.associationForm(UtilMap.getString(detail, "associationFormField_lt6zl2fh_id")));
+                continue;
+            }
+            // 主表信息需完善
+            detail.putAll(UtilMap.map("selectField_lwq7bv4z, selectField_lwq7bv51", "charter, pdt", data));
+            detail.put("radioField_lti2e636", "启用");
+            String formInstId = (String) ydClient.operateData(YDParam.builder()
+                    .formUuid("FORM-767ECC0F8CCE4FB0BC48ECC63164F297BS77")
+                    .appType(appType_ts)
+                    .systemToken(systemToken_ts)
+                    .formDataJson(JSONObject.toJSONString(detail)) // 子表组件ID与档案一致
+                    .build(), YDConf.FORM_OPERATION.create);
+            // 查询新增数据后, 规格编号
+            Map crData = ydClient.queryData(YDParam.builder()
+                    .appType(appType_ts)
+                    .systemToken(systemToken_ts)
+                    .formInstanceId(formInstId)
+                    .build(), YDConf.FORM_QUERY.retrieve_id).getFormData();
+            String rCode = UtilMap.getString(crData, "serialNumberField_m2b2mkva");
+            detail.put("associationFormField_lt6zl2fh", YDConf.associationForm(appType_ts, "FORM-767ECC0F8CCE4FB0BC48ECC63164F297BS77", formInstId, rCode, "", false));
+            detail.put("textField_m2b2vx5x", rCode);
+            detail.put("selectField_m10k093c", "已新增");  // 兼容重复调用
+            isUpdate = true;
+        }
+        if (isUpdate) {
+            ydClient.operateData(YDParam.builder()
+                    .appType(appType_ts)
+                    .systemToken(systemToken_ts)
+                    .formInstanceId(instanceId)
+                    .updateFormDataJson(JSONObject.toJSONString(UtilMap.map("tableField_lt2d2x78", details)))
+                    .build(), YDConf.FORM_OPERATION.update);
+        }
+    }
+
     /**
      * 提供verifier数据读取服务
      */
     @Override
-    public List<Map> syncVerifier(String projectCode) {
+    public Map syncVerifier(String projectCode, String productCode, String productVersion) {
 
         // 查询最新审批通过项目,规格列表
         List<Map> dataList = (List<Map>) ydClient.queryData(YDParam.builder()
@@ -541,7 +719,10 @@ public class AWYDImplClient implements AWYDClient {
 
         Map formData = UtilMap.getMap((Map) UtilList.getLast(dataList), "data");
         List<Map> details = (List<Map>) formData.get("tableField_lt2d2x78");
-        return details.stream().map(row -> {
+        details = details.stream().filter(item -> {
+            // prd 10.17 规格仅校验功能、性能需求, 规格可能为空. 因此只传递数字/模拟
+            return Arrays.asList("数字, 模拟").equals(UtilMap.getString(item, "selectField_lti2e638"));
+        }).map(row -> {
             // Type, MinSpec, MaxSpec, Unit, Description 规格描述
             Map r = UtilMap.map("Type, MinSpec, MaxSpec, Unit, Description", "textField_lubo7pf2, textField_lubo7pf3, textField_lubo7pf4, textField_lubo7pf5, textField_lt2vdhtf", row);
             // ID 规格编号 + 需求编号
@@ -559,8 +740,26 @@ public class AWYDImplClient implements AWYDClient {
             r.put("Condition", UtilMap.getString(row, "textField_lt2vdhtg")); // 规格条件
             return r;
         }).collect(Collectors.toList());
+        return UtilMap.map("code, list", UtilMap.getString(formData, "serialNumberField_lt6xc8ef"), details);
     }
 
+    /**
+     * 提供verifier数据回传服务
+     */
+    @Override
+    public void backVerifier(Map data) {
+
+        List<Map> list = UtilMap.getList(data, "data");
+        list.forEach(item -> {
+            Map formData = UtilMap.map("textField_u3qgblp, textField_cpp0ref", "ID, Title", item);
+            ydClient.operateData(YDParam.builder()
+                    .appType("APP_R5EBUF2FPN3Y8DRF93M4")
+                    .systemToken("ON566NC1VNIHPANP9TNVHB3TBIWS3E0TUZ5RLF3")
+                    .formUuid("FORM-F390DA7B88C4470EA49503753DA6C8FBQCDN")
+                    .formDataJson(JSON.toJSONString(formData))
+                    .build(), YDConf.FORM_OPERATION.create);
+        });
+    }
 
     @Override
     public void test() {

BIN
mjava-aiwei/src/main/resources/templates/templates_project_specification_exception.xlsx


BIN
mjava-aiwei/src/main/resources/templates/templates_test_specification.xlsx


BIN
mjava-aiwei/src/main/resources/templates/templates_test_specification_exception.xlsx


+ 3 - 0
mjava-hake/src/main/java/com/malk/hake/controller/HKController.java

@@ -101,6 +101,9 @@ public class HKController {
         return McR.success();
     }
 
+    /**
+     * 手动同步付款数据
+     */
     @PostMapping("monitor/sync")
     McR pay(String code) {
 

+ 115 - 0
mjava-hake/src/main/java/com/malk/hake/controller/HKController_ML.java

@@ -0,0 +1,115 @@
+package com.malk.hake.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.hake.service.HKClient_ML;
+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.common.McREnum;
+import com.malk.service.aliwork.YDClient;
+import com.malk.utils.UtilMap;
+import com.malk.utils.UtilServlet;
+import lombok.SneakyThrows;
+import lombok.Synchronized;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+
+/**
+ * 错误抛出与拦截详见 CatchException
+ */
+@Slf4j
+@RestController
+@RequestMapping("ml")
+public class HKController_ML {
+
+    @Autowired
+    private HKClient_ML hkClient;
+
+    @Autowired
+    private YDClient ydClient;
+
+    /**
+     * 通用流程发起
+     */
+    @SneakyThrows
+    @PostMapping("process/start")
+    McR startProcess(@RequestBody Map data, HttpServletRequest request, @RequestParam String code) {
+
+        Map header = UtilServlet.getHeaders(request);
+        log.info("流程发起, {}, {}, {}", code, data, header);
+        McException.assertException(!"dinge61fe69900ea236b35c2f4657eb6378f".equals(header.get("authorization")), McREnum.NOT_AUTHORIZED);
+        Map result = hkClient.startProcess(code, data);
+        return McR.success(result);
+    }
+
+    /**
+     * 通用审批校验 [区分类型, 宜搭提示不同报错信息]
+     */
+    @PostMapping("process/validate")
+    McR validateProcess(HttpServletRequest request) {
+
+        Map data = UtilServlet.getParamMap(request);
+        log.info("审批校验, {}", JSON.toJSONString(data));
+        // 拒绝审批意见必填校验 prd 4.17 取消拒绝校验, 统一返回 [审核人xxx拒绝], monitor通过活动跳转
+        //McException.assertParamException_Null(data, "Remark");
+        return McR.success();
+    }
+
+    /**
+     * 通用审批回调 [区分类型, 宜搭提示不同报错信息]
+     */
+    @PostMapping("process/callback")
+    McR callbackProcess(HttpServletRequest request) {
+
+        Map data = UtilServlet.getParamMap(request);
+        log.info("审批回调, {}", JSON.toJSONString(data));
+        if (!"Y".equals(data.get("Status"))) { // 询价\报价
+            hkClient.callbackProcess(data);
+        }
+        return McR.success();
+    }
+
+    /**
+     * 报价、询价审批, 添加标志位判定
+     */
+    @PostMapping("process/update")
+    McR updateProcess(@RequestBody Map data) {
+
+        McException.assertParamException_Null(data, "ddProcessId", "ddStatus");
+        log.info("同步标志, {}", JSON.toJSONString(data));
+
+        ydClient.operateData(YDParam.builder()
+                .formInstanceId(UtilMap.getString(data, "ddProcessId"))
+                .updateFormDataJson(JSON.toJSONString(UtilMap.map("radioField_lzkqlkgh", data.get("ddStatus"))))
+                .build(), YDConf.FORM_OPERATION.update);
+        return McR.success();
+    }
+
+    /**
+     * 通讯录同步
+     */
+    @Synchronized
+    @PostMapping("contact/sync")
+    McR syncContact() {
+
+        log.info("手动触发, 通讯录同步");
+        hkClient.syncContact();
+        return McR.success();
+    }
+
+    /**
+     * 手动同步付款数据
+     */
+    @PostMapping("monitor/sync")
+    McR pay(String code) {
+
+        log.info("手动触发, 拉取Monitor同步");
+        hkClient.syncMonitor(code);
+        return McR.success();
+    }
+}

+ 26 - 0
mjava-hake/src/main/java/com/malk/hake/service/HKClient_ML.java

@@ -0,0 +1,26 @@
+package com.malk.hake.service;
+
+import java.util.Map;
+
+public interface HKClient_ML {
+
+    /**
+     * 通用流程发起
+     */
+    Map<String, Object> startProcess(String code, Map data);
+
+    /**
+     * 通用审批回调
+     */
+    void callbackProcess(Map data);
+
+    /**
+     * 通讯录同步
+     */
+    void syncContact();
+
+    /**
+     * 付款数据同步
+     */
+    void syncMonitor(String code);
+}

+ 299 - 0
mjava-hake/src/main/java/com/malk/hake/service/impl/HKImplClient_ML.java

@@ -0,0 +1,299 @@
+package com.malk.hake.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.malk.hake.service.HKClient_ML;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.common.McException;
+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.service.dingtalk.DDService;
+import com.malk.utils.*;
+import lombok.SneakyThrows;
+import lombok.Synchronized;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.*;
+
+@Service
+@Slf4j
+public class HKImplClient_ML implements HKClient_ML {
+
+    @Autowired
+    private YDClient ydClient;
+
+    // monitor接口ip
+    String _getEnvApi(String path) {
+        if (UtilEnv.getActiveProfile().equals(UtilEnv.ENV_PROD)) {
+//            return "http://172.16.20.13:10002/api/public" + path;
+        }
+        return "http://hawkfiltration.fortiddns.com:10001/api/public" + path;
+    }
+
+    private Map MATE;
+
+    private Map _getMeta() {
+        if (ObjectUtil.isNull(MATE)) {
+            MATE = (Map) UtilFile.readJsonObjectFromResource("static/json/form_ml.json"); // 本地匹配宜搭组件ID
+        }
+        return MATE;
+    }
+
+    /**
+     * 创建/更新数据
+     */
+    private String upsertData(String code, Map data) {
+        // 组织单据数据
+        Map meta = _getMeta();
+        Map<String, ?> component = (Map) ((Map) meta.get(code)).get("compIds");
+        Map formData = UtilMap.empty();
+        for (String key : component.keySet()) {
+            if (key.startsWith("employeeField_")) {
+                // getString, 避免 [null] 数据类型
+                formData.put(key, Arrays.asList(UtilMap.getString(data, (String) component.get(key))));
+            } else if (key.startsWith("tableField_")) {
+                List<Map> rows = (List<Map>) data.get("rows");
+                List<Map> details = new ArrayList<>();
+                Map<String, String> compIds = (Map) component.get(key);
+                for (Map row : rows) {
+                    Map detail = UtilMap.empty();
+                    for (String prop : compIds.keySet()) {
+                        detail.put(prop, row.get(compIds.get(prop)));
+                    }
+                    details.add(detail);
+                }
+                formData.put(key, details);
+            } else if (key.startsWith("dateField_")) {
+                String date = String.valueOf(data.get(component.get(key)));
+                if (StringUtils.isNotBlank(date)) {
+                    formData.put(key, UtilDateTime.parse(date, "yyyy/MM/dd").getTime());
+                }
+            } else {
+                Object value = data.get(component.get(key));
+                formData.put(key, value);
+            }
+        }
+        // 发起人匹配规则
+        String userId = _getUserId(code, meta, data);
+        YDConf.FORM_OPERATION operation = YDConf.FORM_OPERATION.start;
+        String processCode = String.valueOf(((Map) meta.get(code)).get("processCode"));
+        if (UtilString.isBlankCompatNull(processCode)) {
+            operation = YDConf.FORM_OPERATION.create;
+        }
+        String formInstanceId = UtilMap.getString(data, "instanceId");
+        YDParam ydParam = YDParam.builder()
+                .formUuid(String.valueOf(((Map) meta.get(code)).get("formUuid")))
+                .processCode(processCode)
+                .userId(userId)
+                .formDataJson(JSON.toJSONString(formData))
+                .updateFormDataJson(JSON.toJSONString(formData))
+                .useLatestVersion(true)
+                .build();
+        // 匹配更新机制: Upsert操作
+        if (StringUtils.isNotBlank(formInstanceId)) {
+            ydParam.setFormInstanceId(formInstanceId);
+            operation = YDConf.FORM_OPERATION.update;
+        }
+        Object result = ydClient.operateData(ydParam, operation);
+        return StringUtils.isNotBlank(formInstanceId) ? formInstanceId : (String) result;
+    }
+
+    /// 发起人处理
+    String _getUserId(String code, Map meta, Map data) {
+        String userId = String.valueOf(data.get(String.valueOf(((Map) meta.get(code)).get("creator"))));
+        // 报价单
+        if ("BJ".equals(code)) {
+            if (Arrays.asList("样品", "大货").contains(data.get("quoteType"))) {
+                userId = String.valueOf(data.get("dtOurReferencePersonId"));
+            } else {
+                userId = String.valueOf(data.get("dtSellerPersonId"));
+            }
+        }
+        if (UtilString.isBlankCompatNull(userId)) {
+            userId = YDConf.PUB_ACCOUNT;
+        }
+        return userId;
+    }
+
+    @Autowired
+    private YDConf ydConf;
+
+    /**
+     * 通用流程发起
+     */
+    @SneakyThrows
+    @Override
+    public Map<String, Object> startProcess(String code, Map data) {
+        String instanceId = upsertData(code, data);
+        Map meta = _getMeta();
+        // 回传钉钉单据号
+        Thread.sleep(800);
+        Map form = ydClient.queryData(YDParam.builder()
+                .formInstanceId(String.valueOf(instanceId))
+                .build(), YDConf.FORM_QUERY.retrieve_id).getFormData();
+        String url = "https://pxi03f.aliwork.com/" + ydConf.getAppType() + "/processDetail?procInsId=" + instanceId;
+        return UtilMap.map("ddProcessId, dtNumber, dtUrl", instanceId, form.get(meta.get(code + "_RC")), url);
+    }
+
+    @Autowired
+    private DDClient ddClient;
+
+    @Autowired
+    private DDClient_Contacts ddClient_contacts;
+
+    @Autowired
+    private DDService ddService;
+
+    private static final String AUTH = "bbb76928-a9db-42fa-ac83-cc9a2ed75162";
+
+    /**
+     * 通用审批回调 [区分类型, 宜搭提示不同报错信息]
+     */
+    @Override
+    public void callbackProcess(Map data) {
+
+        // 拒绝审批意见必填校验
+        McException.assertParamException_Null(data, "Result");
+        // prd 4.17 取消拒绝校验, 统一返回 [审核人xxx拒绝], monitor通过活动跳转
+        //if ("0".equals(data.get("Result"))) {
+        // McException.assertParamException_Null(data, "Remark");
+        //}
+        // 回调monitor审批信息
+        data.put("Url", "https://pxi03f.aliwork.com/" + ydConf.getAppType() + "/processDetail?procInsId=" + data.get("DdProcessId"));
+        data.put("ApprovalDate", UtilDateTime.formatLocalDateTime(LocalDateTime.now()));
+        Map UserInfo = ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), String.valueOf(data.get("Approver")));
+        // prd 4.17 取消拒绝校验, 统一返回 [审核人xxx拒绝], monitor通过活动跳转
+        if ("0".equals(data.get("Result")) && !data.containsKey("Remark")) {
+            data.put("Remark", "审批人 【" + UserInfo.get("name") + "】拒绝");
+        }
+        data.put("Approver", String.valueOf(UserInfo.get("name")));
+        log.info("审批回调, {}", JSON.toJSONString(data));
+        // prd: 通过 http status 判定, 200 即为成功
+        String rsp = UtilHttp.doPost(_getEnvApi("/Approval"), UtilMap.map("Authorization", AUTH), UtilMap.map("Code", data.get("Code")), data);
+        Map result = (Map) JSON.parse(rsp);
+        McException.assertException(result.containsKey("errorMsg"), String.valueOf(result.get("status")), String.valueOf(result.get("errorMsg")), "Monitor");
+    }
+
+    /**
+     * 通讯录同步
+     */
+    @Override
+    @Synchronized
+    public void syncContact() {
+
+        // prd: 非全量同步: 马来部门
+        List<Long> deptList = UtilList.asList(698673653L); // 马来
+        // prd: 10.21 马来没有三级部门, 只处理二级部门,一级就是大公司
+        ddClient_contacts.getDepartmentId_all(ddClient.getAccessToken(), false, deptList.get(0)).forEach(deptList::add);
+
+        for (long deptId : deptList) {
+            List<String> userIds = ddClient_contacts.listDepartmentUserId(ddClient.getAccessToken(), deptId);
+            if (userIds.size() == 0) {
+                continue;
+            }
+            String dpetCascade = ddService.getUserDepartmentHierarchyJoin(ddClient.getAccessToken(), userIds.get(0), "-");
+            String deptName = UtilMap.getString(ddClient_contacts.getDepartmentInfo(ddClient.getAccessToken(), deptId), "name");
+
+            deptName = "Top Management";
+
+            for (String userId : userIds) {
+                Map userInfo = ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), userId);
+                Map data = UtilMap.map("employeeNumber, employeeName, dtPersonId, lastName", userInfo.get("job_number"), userInfo.get("name"), userInfo.get("userid"), userInfo.get("title"));
+                // prd: 手机号花名册未展示, 因此添加判空, 避免覆盖Monitor
+                if (StringUtils.isNotBlank(String.valueOf(userInfo.get("mobile")))) {
+                    data.put("mobilePhone", userInfo.get("mobile"));
+                }
+                if (userInfo.containsKey("org_email")) {
+                    data.put("email", userInfo.get("org_email"));
+                }
+                long hiredDate = new Date().getTime();
+                if (userInfo.containsKey("hired_date")) {
+                    hiredDate = UtilMap.getLong(userInfo, "hired_date");
+                }
+                /// prd 10.21 马来入职必填的, 默认为入职日期, 若为空则取值当前日期
+                data.put("employeeStartDate", UtilDateTime.formatDate(new Date(hiredDate)));
+                data.put("employeeDept", deptName);
+                data.put("dtDepts", dpetCascade);
+                data.put("dtPersonGroup", "");
+                log.info("同步人员, {}", JSON.toJSONString(UtilMap.map("employees", Arrays.asList(data))));
+                _syncContact(data, userId);
+            }
+        }
+
+        // 离职人员同步
+        Date sDate = UtilDateTime.convertToDateFromLocalDateTime(LocalDateTime.now().minusDays(1));
+        List<Map<String, String>> maps = ddClient_contacts.getLeaveEmployeeRecords(ddClient.getAccessToken(), sDate, null);
+        for (Map<String, String> map : maps) {
+            // prd 钉钉离职数据获取不到工号, 通过99999 monitor判断, 若存在则更新部门, 负责忽略
+            Map data = UtilMap.map("employeeNumber, employeeName, dtPersonId, mobilePhone, employeeDept", "99999", map.get("name"), map.get("userId"), map.get("mobile"), "离职");
+            data.put("employeeFinishDate", map.get("leaveTime").split("T")[0]);
+            _syncContact(data, map.get("userId"));
+            log.info("离职人员, {}", JSON.toJSONString(UtilMap.map("employees", Arrays.asList(data))));
+        }
+    }
+
+    /// 员工信息写入monitor
+    void _syncContact(Map data, String userId) {
+
+        // prd: 通过 http status 判定, 200 即为成功
+        String rsp = UtilHttp.doPost(_getEnvApi("/EmployeeImport"), UtilMap.map("Authorization", AUTH), null, UtilMap.map("employees", Arrays.asList(data)));
+        Map result = (Map) JSON.parse(rsp);
+        if (UtilList.isNotEmpty(UtilMap.getList(result, "fail"))) {
+            result = (Map) UtilMap.getList(result, "fail").get(0); // 单条同步, 记录错误人员
+        }
+        //McException.assertException(result.containsKey("msg"), String.valueOf(result.get("status")), String.valueOf(result.get("msg")), "Monitor");
+        if (result.containsKey("msg")) {
+            ydClient.operateData(YDParam.builder()
+                    .formUuid("FORM-49C2AC35492D4D69BF98036B1B756105MT59")
+                    .formDataJson(JSON.toJSONString(UtilMap.map("dateField_lo47byyd, employeeField_lo47byyj, textareaField_lo47byyo, textareaField_lo47sanj", new Date().getTime(), userId, result, data)))
+                    .build(), YDConf.FORM_OPERATION.create);
+        }
+    }
+
+    @Autowired
+    private YDService ydService;
+
+    /**
+     * 付款数据同步
+     */
+    @Override
+    public void syncMonitor(String code) {
+
+        McException.assertAccessException(!Arrays.asList("FKSQ", "YFKSQ").contains(code), "单据编码未匹配");
+        boolean isYF = "YFKSQ".equals(code);
+
+        String rsp = UtilHttp.doPost(_getEnvApi(isYF ? "/PrePayment" : "/Payment"), UtilMap.map("Authorization", AUTH), null, UtilMap.empty());
+        Map result = (Map) JSON.parse(rsp);
+        List<Map> dataList = (List<Map>) UtilMap.getList(result, isYF ? "orders" : "rows");
+        log.info("付款数据同步, {} {}", code, dataList.size());
+        // prd: 通过 http status 判定, 200 即为成功
+        for (Map row : dataList) {
+            Map search = null;
+            if (isYF) {
+                search = YDConf.searchCondition_TextFiled("textField_lnmyiqan", UtilMap.getString(row, "orderNumber"), "eq");
+            } else {
+                search = YDConf.searchCondition_TextFiled("textField_lnmzbyzu", UtilMap.getString(row, "invoiceNumber"), "eq");
+            }
+            // 匹配是否新增
+            List<Map> formList = ydService.queryFormData(isYF ? "FORM-7DA9C030A3E84689AADA23BA6BAC435DVWDM" : "FORM-98BF1EE731CD48078FEA0701C5F08E73PKQU", JSON.toJSONString(Arrays.asList(search)));
+            if (formList.size() == 0) {
+                // 宜搭付款控制字段
+                row.put("status", "否");       // 反馈中
+                row.put("payed", 0);           // 已付款
+            } else {
+                Map record = formList.get(0);
+                row.put("status", record.get("radioField_ls3ddkie"));       // 反馈中
+                row.put("payed", record.get("numberField_ls3ddkif"));       // 已付款
+                row.put("instanceId", record.get("instanceId"));
+            }
+            upsertData(code, row);
+        }
+    }
+}

+ 9 - 4
mjava-hake/src/main/resources/application-dev.yml

@@ -44,8 +44,13 @@ dingtalk:
   token:
   operator: ""   # OA管理员账号
 
-# aliwork
-aliwork:
-  appType: "APP_QWUVLI1R6XYUXWAOPF6O"
-  systemToken: "SE766NA1XW0F9PG19UX926VYF9RS31DVAYMNLJ27"
+# aliwork-上海
+#aliwork:
+#  appType: "APP_QWUVLI1R6XYUXWAOPF6O"
+#  systemToken: "SE766NA1XW0F9PG19UX926VYF9RS31DVAYMNLJ27"
+
 
+# aliwork-马来
+aliwork:
+  appType: "APP_QJOL2IQW1DTH37FXPGSY"
+  systemToken: "E3A66E810VPOPE2LCXHUJ6O6CWA12VWLSO81MOG"

+ 38 - 0
mjava-hake/src/main/resources/application-prod-ml.yml

@@ -0,0 +1,38 @@
+# 环境配置
+server:
+  port: 9011
+  servlet:
+    context-path: /api/hake
+
+# condition
+spel:
+  scheduling: true         # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    hikari:
+      connection-init-sql: SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci           # SqlServer, Oracle 无需设置类型
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: root
+    password: mu123
+    url: jdbc:mysql://127.0.0.1:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  jpa:
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+
+# dingtalk
+dingtalk:
+  agentId: 2757249888
+  appKey: ding3zk0vhfifznseopg
+  appSecret: Eso_p9HrVrJClEqcwbYuuOeDbS5Lb0e8Qq_HWtJm4_GYR38E-O5UEvakWpxXAvqq
+  corpId: dinge61fe69900ea236b35c2f4657eb6378f
+  aesKey:
+  token:
+  operator: ""   # OA管理员账号
+
+# aliwork
+aliwork:
+  appType: "APP_QJOL2IQW1DTH37FXPGSY"
+  systemToken: "E3A66E810VPOPE2LCXHUJ6O6CWA12VWLSO81MOG"

+ 341 - 0
mjava-hake/src/main/resources/static/json/form_ml.json

@@ -0,0 +1,341 @@
+{
+  "BJ": {
+    "formUuid": "FORM-44DC2EEBEDFC4F95BBF609039D2A16ED7CR4",
+    "processCode": "TPROC--U1B66W915IJO5NMTD7GIAD9112ZD2B5NSO81M4P",
+    "creator": "dtPersonId",
+    "compIds": {
+      "textField_lnmyiqad": "orderNumber",
+      "textField_lnmyiqae": "customerNumber",
+      "textField_lnmyiqaf": "customerStatus",
+      "textField_lnmyiqag": "customerName",
+      "textField_lnmyiqai": "deliveryAddress",
+      "textField_lnmyiqak": "dtPersonId",
+      "textField_lnmyiqan": "dtPersonGroup",
+      "textField_lnwqhgoy": "dtOurReferencePersonId",
+      "textField_lnwqhgoz": "dtSellerPersonId",
+      "textField_lnwqhgp2": "quoteType",
+      "employeeField_lnwqhgox": "dtPersonId",
+      "employeeField_lnwqhgp0": "dtOurReferencePersonId",
+      "employeeField_lnwqhgp1": "dtSellerPersonId",
+      "tableField_lnmyiqau": {
+        "textField_lnmyiqav": "partNumber",
+        "textField_lnmyiqaw": "partName",
+        "numberField_lnmyiqax": "quantity",
+        "textField_lnmyiqaz": "currency",
+        "textField_lsts7vew": "unit",
+        "numberField_lnmyiqb1": "exchangeRate",
+        "numberField_lnmyiqb2": "priceInCompanyCurrencyExTax",
+        "numberField_lnmyiqb3": "priceInTax",
+        "numberField_lt60a87j": "standardPrice",
+        "numberField_lnmyiqb4": "",
+        "numberField_lnmyiqb5": "",
+        "textField_lnmyiqb6": "deliveryDate",
+        "textField_lu12hl9n": "circleRate"
+      }
+    }
+  },
+  "BJ_RC": "serialNumberField_lntocd8t",
+  "XSDD": {
+    "formUuid": "FORM-4E41C174C9A848048BD203C794CA6B51OK25",
+    "processCode": "TPROC--U1B66W915IJO5NMTD7GIAD9112ZD2A5NSO81MVO",
+    "creator": "dtPersonId",
+    "compIds": {
+      "textField_lnmyiqad": "orderNumber",
+      "textField_lnmyiqae": "customerNumber",
+      "textField_lnmyiqag": "customerName",
+      "textField_lnmyiqai": "deliveryAddress",
+      "textField_lnmyiqaf": "seller",
+      "textField_lnmyiqak": "dtPersonId",
+      "employeeField_lo5g6p79": "dtPersonId",
+      "textField_lnmyiqan": "dtPersonGroup",
+      "numberField_lnmyrzwx": "reason",
+      "tableField_lnmyiqau": {
+        "textField_lnmyiqav": "partNumber",
+        "textField_lnmyiqaw": "partName",
+        "numberField_lnmyiqax": "quantity",
+        "textField_lnmyiqaz": "unit",
+        "numberField_lnmyiqb2": "priceExTax",
+        "textField_lnmyrzx0": "deliveryDate",
+        "textField_lnmyrzwy": "currency",
+        "numberField_lnmyiqb1": "exchangeRate",
+        "numberField_lnmyrzwz": "priceInCompanyCurrencyExTax",
+        "textField_lnmyiqb6": "rowType"
+      },
+      "textareaField_lte4l7om": "changes"
+    }
+  },
+  "XSDD_RC": "serialNumberField_lntocd8t",
+  "XSDDTZ": {
+    "formUuid": "FORM-1C10DB469D934CA9894BB1C8E7B38CCBJTL8",
+    "processCode": "TPROC--U1B66W915IJO5NMTD7GIAD9112ZD2B5NSO81M7P",
+    "creator": "dtPersonId",
+    "compIds": {
+      "textField_lnmyiqad": "orderNumber",
+      "textField_lnmyiqae": "customerNumber",
+      "textField_lnmyiqag": "customerName",
+      "textField_lnmyiqai": "deliveryAddress",
+      "textField_lnmyiqaf": "seller",
+      "textField_lnmyiqak": "dtPersonId",
+      "employeeField_lo5g6p79": "dtPersonId",
+      "textField_lnmyiqan": "dtPersonGroup",
+      "numberField_lnmyrzwx": "reason",
+      "textField_lptxghkq": "dtOurReferencePersonId",
+      "employeeField_lptxghks": "dtOurReferencePersonId",
+      "textField_lptxghkr": "dtSellerPersonId",
+      "employeeField_lptxghkt": "dtSellerPersonId",
+      "tableField_lnmyiqau": {
+        "textField_lnmyiqav": "partNumber",
+        "textField_lnmyiqaw": "partName",
+        "numberField_lnmyiqax": "quantity",
+        "textField_lnmyiqaz": "unit",
+        "textField_lnmyrzx0": "deliveryDate",
+        "textField_lnmyiqb6": "rowType"
+      }
+    }
+  },
+  "GYSZQ": {
+    "formUuid": "FORM-79DE1098EF9B4C5A8383E703A299AEA1F161",
+    "processCode": "TPROC--U1B66W915IJO5NMTD7GIAD9112ZD2B5NSO81M3P",
+    "creator": "dtPersonId",
+    "compIds": {
+      "textField_lnmyiqad": "orderNumber",
+      "textField_lnmyiqae": "supplierName",
+      "textField_lnmyiqag": "paymentTerm",
+      "textField_lnmyiqak": "dtPersonId",
+      "employeeField_lo5ggrvr": "dtPersonId",
+      "textField_lnmyiqan": "dtPersonGroup"
+    }
+  },
+  "GYSZQ_RC": "serialNumberField_lntocd8t",
+  "CGDD": {
+    "formUuid": "FORM-6713C1C854E34018BFCAD414604E790EF7X5",
+    "processCode": "TPROC--U1B66W915IJO5NMTD7GIAD9112ZD2B5NSO81M1P",
+    "creator": "dtPersonId",
+    "compIds": {
+      "textField_lo5h6ojh": "orderNumber",
+      "textField_lnmyiqad": "supplierNumber",
+      "textField_lnmyiqae": "supplierName",
+      "textField_lnmyiqag": "seller",
+      "textField_lnmyiqak": "dtPersonId",
+      "employeeField_lo5fmt0u": "dtPersonId",
+      "textField_lnmyiqan": "dtPersonGroup",
+      "tableField_lnmyiqau": {
+        "textField_lnmyiqaw": "partNumber",
+        "textField_lnmyiqav": "partName",
+        "textField_lnmyiqaz": "unit",
+        "numberField_lnmyiqax": "quantity",
+        "numberField_lnmyiqb2": "priceExTax",
+        "textField_lnmyrzx0": "deliveryDate",
+        "textField_lnmyrzwy": "currency",
+        "numberField_lnmyiqb1": "exchangeRate",
+        "numberField_lnmyrzwz": "priceInCompanyCurrencyExTax"
+      }
+    }
+  },
+  "CGDD_RC": "serialNumberField_lntocd8t",
+  "CGDDTZ": {
+    "formUuid": "FORM-49E9538AC5B445FCB4F4AB549E29334AG5LG",
+    "processCode": "TPROC--U1B66W915IJO5NMTD7GIAD9112ZD2B5NSO81M6P",
+    "creator": "dtPersonId",
+    "compIds": {
+      "textField_lo5h6ojh": "orderNumber",
+      "textField_lnmyiqad": "supplierNumber",
+      "textField_lnmyiqae": "supplierName",
+      "textField_lnmyiqag": "seller",
+      "textField_lnmyiqak": "dtPersonId",
+      "employeeField_lo5fmt0u": "dtPersonId",
+      "textField_lnmyiqan": "dtPersonGroup",
+      "tableField_lnmyiqau": {
+        "textField_lnmyiqaw": "partNumber",
+        "textField_lnmyiqav": "partName",
+        "textField_lnmyiqaz": "unit",
+        "numberField_lnmyiqax": "quantity",
+        "textField_lnmyrzx0": "deliveryDate"
+      }
+    }
+  },
+  "CGDDJQ": {
+    "formUuid": "FORM-74F6DC3C1AF44F49AD56EF0C2A79EDDBSVBM",
+    "processCode": "TPROC--U1B66W915IJO5NMTD7GIAD9112ZD2B5NSO81M2P",
+    "creator": "dtPersonId",
+    "compIds": {
+      "textField_lo5i25ar": "orderNumber",
+      "textField_lnmyiqad": "supplierNumber",
+      "textField_lnmyiqae": "supplierName",
+      "textField_lnmyiqag": "seller",
+      "textField_lnmyiqak": "dtPersonId",
+      "employeeField_lo5gpzw4": "dtPersonId",
+      "textField_lnmyiqan": "dtPersonGroup",
+      "tableField_lnmyiqau": {
+        "textField_lnmyiqaw": "partNumber",
+        "textField_lnmyiqav": "partName",
+        "textField_lnmyiqaz": "unit",
+        "numberField_lnmyiqax": "quantity",
+        "numberField_lnmyiqb2": "priceExTax",
+        "textField_lnmyrzx0": "deliveryDate",
+        "textField_lnmyrzwy": "currency",
+        "numberField_lnmyiqb1": "exchangeRate",
+        "numberField_lnmyrzwz": "priceInCompanyCurrencyExTax"
+      }
+    }
+  },
+  "CGDDJQ_RC": "serialNumberField_lntocd8t",
+  "FKSQ": {
+    "formUuid": "FORM-7DA9C030A3E84689AADA23BA6BAC435DVWDM",
+    "creator": "",
+    "compIds": {
+      "textField_lnmyiqad": "supplierNumber",
+      "textField_lnmyiqae": "supplierName",
+      "dateField_lxy21w4g": "paymentDate",
+      "textField_lnmyiqak": "paymentMethod",
+      "dateField_lxy204st": "dueDate",
+      "textField_ls3ddkia": "supplierInvoiceNumber",
+      "textField_lnmzbyzu": "invoiceNumber",
+      "numberField_lnmzbyzz": "paymentAmount",
+      "numberField_lnmzbz00": "invoiceAmount",
+      "numberField_lnmzbz01": "restAmount",
+      "textField_ls3ddkib": "supplierAgentDtId",
+      "employeeField_ls3ddkid": "supplierAgentDtId",
+      "radioField_ls3ddkie": "status",
+      "numberField_ls3ddkif": "payed"
+    }
+  },
+  "FKSQ_RC": "serialNumberField_lntocd8t",
+  "YFKSQ": {
+    "formUuid": "FORM-98BF1EE731CD48078FEA0701C5F08E73PKQU",
+    "creator": "",
+    "compIds": {
+      "textField_lnmyiqad": "supplierNumber",
+      "textField_lnmyiqae": "supplierName",
+      "dateField_lxy25lq9": "paymentDate",
+      "textField_lnmyiqak": "paymentMethod",
+      "textField_lnmyiqan": "orderNumber",
+      "numberField_lnmzbyzz": "orderAmount",
+      "numberField_lnmzbz00": "paymentAmount",
+      "tableField_lnmzeuoe": {
+        "textField_lnmzeuof": "position",
+        "numberField_lnmzeuoh": "rowAmount",
+        "numberField_lnmzeuoi": "rowPaymentAmount",
+        "textField_lnmzeuok": "partNumber",
+        "textField_lnmzeuom": "partName",
+        "numberField_lnmzeuon": "quantity",
+        "numberField_lnmzeuoo": "price",
+        "numberField_lnmzeuop": "vat",
+        "numberField_lnmzeuoq": "discount"
+      },
+      "textField_ls3ddkib": "supplierAgentDtId",
+      "employeeField_ls3egxx6": "supplierAgentDtId",
+      "radioField_ls3ddkie": "status",
+      "numberField_ls3ddkif": "payed"
+    }
+  },
+  "YFKSQ_RC": "serialNumberField_lntocd8t",
+  "SJJY": {
+    "formUuid": "FORM-4DB6276FEF414E99AE12C8AB8B3ED2A3FWZ7",
+    "processCode": "TPROC--U1B66W915IJO5NMTD7GIAD9112ZD2B5NSO81MZO",
+    "creator": "dtPersonId",
+    "compIds": {
+      "numberField_lnmzi8vv": "reportNumber",
+      "textField_lnmyiqae ": "orderNumber",
+      "numberField_lnmzi8vw": "reportQuantity",
+      "textField_lnmyiqak": "reporter",
+      "textField_lnmyiqan": "operationNumber",
+      "textField_lnmzi8vx": "operationName",
+      "textField_lnmzi8vy": "reportDate",
+      "textField_lnmzi8vz": "dtPersonId",
+      "textField_lnmzi8w0": "dtPersonGroup"
+    }
+  },
+  "SJJY_RC": "serialNumberField_lntocd8t",
+  "SCJS": {
+    "formUuid": "FORM-9E4D1BB86E134175B0066050FF742D5522MK",
+    "processCode": "TPROC--U1B66W915IJO5NMTD7GIAD9112ZD2B5NSO81M5P",
+    "creator": "dtPersonId",
+    "compIds": {
+      "textField_lnn033p5": "finishedGoodsOpNo",
+      "textField_lnmyiqae ": "finishedInspectionOpNo",
+      "textField_lnn033p6": "orderNumber",
+      "textField_lnmyiqak": "partNumber",
+      "textField_lnmyiqan": "partName",
+      "numberField_lnn033pe": "reportQuantity",
+      "textField_lnn033p7": "reporter",
+      "textField_lnn033p9": "operationNumber",
+      "textField_lnn033pb": "operationName",
+      "textField_lnmzi8vy": "reportDate",
+      "textField_lnmzi8vz": "dtPersonId",
+      "textField_lnmzi8w0": "dtPersonGroup"
+    }
+  },
+  "SCJS_RC": "serialNumberField_lntocd8t",
+  "CGDH": {
+    "formUuid": "FORM-A9A68409F56E46BB9D3FBED5623396F604IW",
+    "processCode": "TPROC--U1B66W915IJO5NMTD7GIAD9112ZD2A5NSO81MXO",
+    "creator": "dtPersonId",
+    "compIds": {
+      "textField_lnn033p5": "supplierNumber",
+      "textField_lnmyiqae": "supplierName",
+      "textField_lqo0h0st": "orderNumber",
+      "textField_lnmyiqak": "orderType",
+      "textField_lnmyiqan": "partNumber",
+      "textField_lnn07qeq": "partName",
+      "textField_lnn033p7": "unit",
+      "numberField_lnn0a6sw": "quantity",
+      "textField_lnn033pb": "deliveryDate",
+      "textField_lnmzi8vy": "dtPersonId"
+    }
+  },
+  "CGDH_RC": "serialNumberField_lntocd8t",
+  "XJSP": {
+    "formUuid": "FORM-88DEA07EAFB848578A153C19921306DAK3TP",
+    "processCode": "TPROC--U1B66W915IJO5NMTD7GIAD9112ZD2A5NSO81MWO",
+    "creator": "dtPersonId",
+    "compIds": {
+      "textField_lnmyiqad": "orderNumber",
+      "textField_lnmyiqae": "supplierNumber",
+      "textField_lnmyiqaf": "supplierStatus",
+      "textField_lnmyiqag": "supplierName",
+      "textareaField_lq3r0wyk": "deliveryAddress",
+      "textField_lnmyiqak": "dtPersonId",
+      "employeeField_lo5h89wz": "dtPersonId",
+      "textField_lnmyiqan": "dtPersonGroup",
+      "tableField_lnmyiqau": {
+        "textField_lnmyiqav": "partNumber",
+        "textField_lnmyiqaw": "partName",
+        "numberField_lnmyiqax": "quantity",
+        "textField_lnmyiqaz": "currency",
+        "textField_lsts7vew": "unit",
+        "numberField_lnmyiqb1": "exchangeRate",
+        "numberField_lnmyiqb2": "priceInCompanyCurrencyExTax",
+        "numberField_lnmyiqb3": "priceInTax",
+        "textField_lnmyiqb6": "deliveryDate"
+      }
+    }
+  },
+  "XJSP_RC": "serialNumberField_lntocd8t",
+  "DJFP": {
+    "formUuid": "FORM-5B29BD093BC34BDDB869ABB45849E669PFAT",
+    "processCode": "TPROC--U1B66W915IJO5NMTD7GIAD9112ZD2B5NSO81M0P",
+    "creator": "dtPersonId",
+    "compIds": {
+      "textField_lnmyiqad": "orderNumber",
+      "textField_lnmyiqae": "customerNumber",
+      "textField_lnmyiqaf": "customerName",
+      "textField_lnmyiqag": "seller",
+      "textField_lnmyiqai": "dtPersonId",
+      "employeeField_lo5hdfsd": "dtPersonId",
+      "textField_lnmyiqak": "dtPersonGroup",
+      "numberField_lnn0i86l": "reason",
+      "tableField_lnmyiqau": {
+        "textField_lnmyiqav": "partNumber",
+        "textField_lnmyiqaw": "partName",
+        "numberField_lnmyiqax": "quantity",
+        "textField_lnmyiqaz": "unit",
+        "numberField_lnmyiqb1": "priceExTax",
+        "textField_lnn0i86n": "currency",
+        "numberField_lnn0i86m": "exchangeRate",
+        "numberField_lnmyiqb2": "priceInCompanyCurrencyExTax"
+      }
+    }
+  },
+  "DJFP_RC": "serialNumberField_lntocd8t"
+}

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

@@ -84,8 +84,9 @@ public class YDConf {
         update,
 
         // fixme: 不支持新增字段更新, 若有可先更新版本后执行 [无版本和更新json参数];
-        // ppExt: 不会触发消息通知, 需要 start / creat. todo 通过架包形式会触发通知
+        // ppExt: start / create, noExecuteExpression 默认值是 true, 若需要触发配置消息通知, 设置为 false
         upsert,                     // insertOrUpdate
+        upsert_v2,                  // 新版本 searchCondition 条件精准匹配
         multi_create,               // 批量操作
         delete_batch,               // 批量删除
         multi_update,               // 批量更新

+ 8 - 0
mjava/src/main/java/com/malk/service/aliwork/impl/YDClientImpl.java

@@ -43,6 +43,11 @@ public class YDClientImpl implements YDClient {
         return "https://api.dingtalk.com/v1.0/yida" + uri;
     }
 
+    // 请求地址 [v2]
+    private String getRequestUrl_v2(String uri) {
+        return "https://api.dingtalk.com/v2.0/yida" + uri;
+    }
+
     // 请求地址 [实例]
     private String getRequestUrl(String uri, String instanceId) {
         return getRequestUrl(uri) + "/" + instanceId;
@@ -65,6 +70,9 @@ public class YDClientImpl implements YDClient {
             case upsert:
                 ddr_new = (DDR_New) UtilHttp.doPost(this.getRequestUrl("/forms/instances/insertOrUpdate"), this.ddClient.initTokenHeader(), bodys, DDR_New.class);
                 break;
+            case upsert_v2:
+                ddr_new = (DDR_New) UtilHttp.doPost(this.getRequestUrl_v2("/forms/instances/insertOrUpdate"), this.ddClient.initTokenHeader(), bodys, DDR_New.class);
+                break;
             case delete:
                 ddr_new = (DDR_New) UtilHttp.doDelete(getRequestUrl("/forms/instances"), ddClient.initTokenHeader(), bodys, DDR_New.class);
                 break;