pruple_boy 2 months ago
parent
commit
6d2b181239
46 changed files with 1123 additions and 114 deletions
  1. 8 0
      mjava-aipocloud/src/main/java/com/malk/aipocloud/delegate/DDDelegate.java
  2. 72 0
      mjava-aiwei/src/main/java/com/malk/aiwei/controller/PLController.java
  3. 22 5
      mjava-aiwei/src/main/java/com/malk/aiwei/controller/TBxYDController.java
  4. 43 0
      mjava-aiwei/src/main/java/com/malk/aiwei/schedule/Timer.java
  5. 13 0
      mjava-aiwei/src/main/java/com/malk/aiwei/service/GZT_PN.java
  6. 4 2
      mjava-aiwei/src/main/java/com/malk/aiwei/service/impl/AWImplClient.java
  7. 7 16
      mjava-aiwei/src/main/java/com/malk/aiwei/service/impl/AWYDImplClient.java
  8. 120 0
      mjava-aiwei/src/main/java/com/malk/aiwei/service/impl/GZT_PNImpl.java
  9. BIN
      mjava-aiwei/src/main/resources/templates/templates_project_requirements.xlsx
  10. BIN
      mjava-aiwei/src/main/resources/templates/templates_project_requirements_exception.xlsx
  11. BIN
      mjava-aiwei/src/main/resources/templates/templates_project_requirements_test.xlsx
  12. BIN
      mjava-aiwei/src/main/resources/templates/templates_project_specification.xlsx
  13. BIN
      mjava-aiwei/src/main/resources/templates/templates_project_specification_exception.xlsx
  14. BIN
      mjava-aiwei/src/main/resources/templates/templates_test_specification.xlsx
  15. BIN
      mjava-aiwei/src/main/resources/templates/templates_test_specification_exception.xlsx
  16. 303 4
      mjava-cloudpure/src/main/java/com/malk/cloudpure/controller/TSController.java
  17. 8 0
      mjava-cloudpure/src/main/java/com/malk/cloudpure/delegate/DDDelegate.java
  18. 17 6
      mjava-fengkaili/src/main/java/com/malk/fengkaili/service/impl/FKLImplService.java
  19. 6 0
      mjava-hake/pom.xml
  20. 22 0
      mjava-hake/src/main/java/com/malk/hake/controller/DDController.java
  21. 26 4
      mjava-hake/src/main/java/com/malk/hake/controller/HKController.java
  22. 4 3
      mjava-hake/src/main/java/com/malk/hake/controller/HKController_ML.java
  23. 71 0
      mjava-hake/src/main/java/com/malk/hake/delegate/DDDelegate.java
  24. 10 2
      mjava-hake/src/main/java/com/malk/hake/schedule/HKScheduleTask.java
  25. 11 1
      mjava-hake/src/main/java/com/malk/hake/service/HKClient.java
  26. 1 1
      mjava-hake/src/main/java/com/malk/hake/service/HKClient_ML.java
  27. 154 2
      mjava-hake/src/main/java/com/malk/hake/service/impl/HKImplClient.java
  28. 15 10
      mjava-hake/src/main/java/com/malk/hake/service/impl/HKImplClient_ML.java
  29. 57 0
      mjava-hake/src/main/java/com/malk/hake/service/impl/OkHttpUtil.java
  30. 1 1
      mjava-hake/src/main/resources/application-dev.yml
  31. 0 38
      mjava-hake/src/main/resources/application-prod-ml.yml
  32. 2 2
      mjava-hake/src/main/resources/application-prod.yml
  33. 4 4
      mjava-hake/src/main/resources/application-test.yml
  34. 8 11
      mjava-hangshi/src/main/java/com/malk/hangshi/service/impl/HSImplService.java
  35. 8 0
      mjava-laidi/src/main/java/com/malk/laidi/delegate/DDDelegate.java
  36. 7 0
      mjava/src/main/java/com/malk/controller/DDCallbackController.java
  37. 6 0
      mjava/src/main/java/com/malk/delegate/DDEvent.java
  38. 8 0
      mjava/src/main/java/com/malk/delegate/impl/DDImplEvent.java
  39. 5 0
      mjava/src/main/java/com/malk/server/dingtalk/DDConf.java
  40. 1 2
      mjava/src/main/java/com/malk/service/aliwork/YDClient.java
  41. 12 0
      mjava/src/main/java/com/malk/service/dingtalk/DDClient_Contacts.java
  42. 5 0
      mjava/src/main/java/com/malk/service/dingtalk/DDClient_Event.java
  43. 5 0
      mjava/src/main/java/com/malk/service/dingtalk/DDService.java
  44. 27 0
      mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_Contacts.java
  45. 19 0
      mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_Event.java
  46. 11 0
      mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplService.java

+ 8 - 0
mjava-aipocloud/src/main/java/com/malk/aipocloud/delegate/DDDelegate.java

@@ -8,6 +8,8 @@ import org.springframework.context.annotation.Primary;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
+import java.util.Map;
+
 /**
 /**
  * OA审批事件 [主项目也有实现, 添加 @Primary 优先注入主项目实现]
  * OA审批事件 [主项目也有实现, 添加 @Primary 优先注入主项目实现]
  * -
  * -
@@ -57,4 +59,10 @@ public class DDDelegate implements DDEvent {
         log.info("审批实例回调执行业务逻辑, {}", approveResult);
         log.info("审批实例回调执行业务逻辑, {}", approveResult);
         abClient.callbackApprove(processCode, processInstanceId, approveResult, staffId);
         abClient.callbackApprove(processCode, processInstanceId, approveResult, staffId);
     }
     }
+
+    // 考勤打卡事件回调
+    @Override
+    public void executeEvent_attendance_check(Map<String, ?> record) {
+        log.info("executeEvent_attendance_check");
+    }
 }
 }

+ 72 - 0
mjava-aiwei/src/main/java/com/malk/aiwei/controller/PLController.java

@@ -0,0 +1,72 @@
+package com.malk.aiwei.controller;
+
+
+import com.malk.aiwei.service.GZT_PN;
+import com.malk.server.common.McException;
+import com.malk.server.common.McR;
+import com.malk.utils.UtilServlet;
+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.RequestBody;
+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("pl")
+public class PLController {
+
+    @Autowired
+    private GZT_PN gzt_pn;
+
+    /**
+     * jsApi 注册
+     */
+    @PostMapping("register")
+    McR register(@RequestBody Map<String, String> data) {
+
+        McException.assertParamException_Null(data, "url", "nonceStr");
+        return McR.success(gzt_pn.register(data));
+    }
+
+    /**
+     * 工作台数据 - 资讯中心
+     */
+    @PostMapping("portal/ZXZX")
+    List<Map> portal_ZXZZ(HttpServletRequest request) {
+
+        Map data = UtilServlet.getParamMap(request);
+        log.info("工作台数据, {}", data);
+        return gzt_pn.getPortalList("派能资讯");
+    }
+
+    @PostMapping("portal/NLPS")
+    List<Map> portal_NLPS(HttpServletRequest request) {
+
+        Map data = UtilServlet.getParamMap(request);
+        log.info("工作台数据, {}", data);
+        return gzt_pn.getPortalList("能量派送");
+    }
+
+    @PostMapping("portal/RZZX")
+    List<Map> portal_RZZX(HttpServletRequest request) {
+
+        Map data = UtilServlet.getParamMap(request);
+        log.info("工作台数据, {}", data);
+        return gzt_pn.getPortalList("人资资讯");
+    }
+
+    @PostMapping("portal/NBZX")
+    List<Map> portal_NBZX(HttpServletRequest request) {
+
+        Map data = UtilServlet.getParamMap(request);
+        log.info("工作台数据, {}", data);
+        return gzt_pn.getPortalList("内部资讯");
+    }
+
+}

+ 22 - 5
mjava-aiwei/src/main/java/com/malk/aiwei/controller/TBxYDController.java

@@ -484,6 +484,21 @@ public class TBxYDController {
         return McR.success();
         return McR.success();
     }
     }
 
 
+    /**
+     * 测规格需求书子表导出
+     */
+    @PostMapping("test-req/export")
+    void exportTestRequirements(@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_project_requirements_test.xlsx");
+    }
+
     /**
     /**
      * 提供verifier数据读取服务
      * 提供verifier数据读取服务
      */
      */
@@ -580,7 +595,7 @@ public class TBxYDController {
         log.info("test, {}", data);
         log.info("test, {}", data);
 
 
 
 
-        awClint.taskLevelValidate(UtilMap.map("projectId, taskId", "659376ba950780b816c33d3b, 66116073ea68df0235b30bef"));
+//        awClint.taskLevelValidate(UtilMap.map("projectId, taskId", "659376ba950780b816c33d3b, 66116073ea68df0235b30bef"));
 
 
 //        try {
 //        try {
 //            Map body = TBConf.assembleCustomFieldName(AWServer.TASK_TRANSMIT, "已下达");
 //            Map body = TBConf.assembleCustomFieldName(AWServer.TASK_TRANSMIT, "已下达");
@@ -589,7 +604,7 @@ public class TBxYDController {
 //            log.error(e.getMessage(), e);
 //            log.error(e.getMessage(), e);
 //        }
 //        }
 
 
-//        awClint.test();
+        awClint.test();
 //        awydClient.test();
 //        awydClient.test();
 
 
 //        ydClient.operateData(YDParam.builder()
 //        ydClient.operateData(YDParam.builder()
@@ -605,8 +620,10 @@ public class TBxYDController {
 
 
 //        awClint.tmp();
 //        awClint.tmp();
 
 
-        List<String> tags = tbClient.queryProjectTag("66c763a2a86f837e07c418e9", tbConf.getOperatorId());
-        tags.add("111");
-        return McR.success(tags);
+//        List<String> tags = tbClient.queryProjectTag("66c763a2a86f837e07c418e9", tbConf.getOperatorId());
+//        tags.add("111");
+
+
+        return McR.success();
     }
     }
 }
 }

+ 43 - 0
mjava-aiwei/src/main/java/com/malk/aiwei/schedule/Timer.java

@@ -0,0 +1,43 @@
+package com.malk.aiwei.schedule;
+
+import com.malk.aiwei.service.GZT_PN;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Configuration
+@EnableScheduling
+public class Timer {
+
+    @Autowired
+    private GZT_PN gzt_pn;
+
+
+    private Map portal_cache = new HashMap();
+
+    @Scheduled(cron = "0 */5 * * * ?")//工作台数据5分钟更新一次
+    public void hzmhByType() {
+        try {
+            gzt_pn.getDataList();
+//            HttpUtil.get("http://aservice.risechina.com:9020/ruisi/hzmh/byType");
+//            portal_cache = new HashMap();
+//            rsService.getPortalList("财务专区");
+//            rsService.getPortalList("公司头条");
+//            rsService.getPortalList("企业文化");
+//            rsService.getPortalList("员工自助");
+//            rsService.getPortalList("规章制度");
+
+        } catch (Exception e) {
+            // 记录错误信息
+            e.printStackTrace();
+        }
+    }
+
+}
+

+ 13 - 0
mjava-aiwei/src/main/java/com/malk/aiwei/service/GZT_PN.java

@@ -0,0 +1,13 @@
+package com.malk.aiwei.service;
+
+import java.util.List;
+import java.util.Map;
+
+public interface GZT_PN {
+
+    List<Map> getPortalList(String section);
+
+    void getDataList();
+
+    Map register(Map<String, String> data);
+}

+ 4 - 2
mjava-aiwei/src/main/java/com/malk/aiwei/service/impl/AWImplClient.java

@@ -886,7 +886,6 @@ public class AWImplClient implements AWClint {
                 if (!workFlowStatusList.contains(task.get("tfsId"))) {
                 if (!workFlowStatusList.contains(task.get("tfsId"))) {
                     continue;
                     continue;
                 }
                 }
-
                 // prd 0920 下发不阻断,没有执行人也更新,已下达 [ 存量任务兼容未添加字段 ]
                 // prd 0920 下发不阻断,没有执行人也更新,已下达 [ 存量任务兼容未添加字段 ]
                 try {
                 try {
                     Map body = TBConf.assembleCustomFieldName(AWServer.TASK_TRANSMIT, "已下达");
                     Map body = TBConf.assembleCustomFieldName(AWServer.TASK_TRANSMIT, "已下达");
@@ -2000,7 +1999,10 @@ public class AWImplClient implements AWClint {
     public void test() {
     public void test() {
 
 
 
 
-        exportCheckList("A2453", "产品开发项目");
+        _convertUserId(tbConf.getOperatorId(), true);
+        _convertUserId(ddConf.getOperator(), false);
+
+//        exportCheckList("A2453", "产品开发项目");
 
 
 //        approveVersion("659a681d44ade3345fdc0d39", "99999");
 //        approveVersion("659a681d44ade3345fdc0d39", "99999");
         //
         //

+ 7 - 16
mjava-aiwei/src/main/java/com/malk/aiwei/service/impl/AWYDImplClient.java

@@ -244,7 +244,7 @@ public class AWYDImplClient implements AWYDClient {
         for (Map detail : details) {
         for (Map detail : details) {
             // 主表信息需完善
             // 主表信息需完善
             detail.putAll(UtilMap.map("textField_lth2h04b, textField_lypq3ees, textField_lypq3eet", "charter, pdt, mpdt", data));
             detail.putAll(UtilMap.map("textField_lth2h04b, textField_lypq3ees, textField_lypq3eet", "charter, pdt, mpdt", data));
-            detail.putAll(UtilMap.map("radioField_lroozhse, dateField_ly5ewjnf, employeeField_ly5ewjnc", "启用", new Date().getTime(), Arrays.asList(data.get("userId"))));
+            detail.putAll(UtilMap.map("radioField_lroozhse, dateField_ly5ewjnf, employeeField_ly5ewjnc", "", new Date().getTime(), Arrays.asList(data.get("userId"))));
             String formInstId = (String) ydClient.operateData(YDParam.builder()
             String formInstId = (String) ydClient.operateData(YDParam.builder()
                     .formUuid("FORM-7A52930D0E834522AD65A4CFE2C0818F1KQO")
                     .formUuid("FORM-7A52930D0E834522AD65A4CFE2C0818F1KQO")
                     .appType(appType_pr)
                     .appType(appType_pr)
@@ -493,7 +493,7 @@ public class AWYDImplClient implements AWYDClient {
             }
             }
             // 主表信息需完善
             // 主表信息需完善
             detail.putAll(UtilMap.map("selectField_lwq7bv4z, selectField_lwq7bv51", "charter, pdt", data));
             detail.putAll(UtilMap.map("selectField_lwq7bv4z, selectField_lwq7bv51", "charter, pdt", data));
-            detail.put("radioField_lti2e636", "启用");
+            detail.put("radioField_lti2e636", "");
             String formInstId = (String) ydClient.operateData(YDParam.builder()
             String formInstId = (String) ydClient.operateData(YDParam.builder()
                     .formUuid("FORM-F5FD52E311514AC890D4B308CFA8E8A8BH8J")
                     .formUuid("FORM-F5FD52E311514AC890D4B308CFA8E8A8BH8J")
                     .appType(appType_ps)
                     .appType(appType_ps)
@@ -551,15 +551,9 @@ public class AWYDImplClient implements AWYDClient {
                 isSuccess = false;
                 isSuccess = false;
                 continue;
                 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");
             String code = UtilMap.getString(row, "textField_m2b2vx5x");
             if (StringUtils.isNotBlank(code)) {
             if (StringUtils.isNotBlank(code)) {
-                // 重复需求+规格编号+c测试规格校验
+                // 重复需求+规格编号+测试规格校验
                 String uCode = UtilMap.getString(row, "textField_m0vvv72j") + "_" + UtilMap.getString(row, "textField_m18x4yqo") + "_" + UtilMap.getString(row, "textField_m2b2vx5x");
                 String uCode = UtilMap.getString(row, "textField_m0vvv72j") + "_" + UtilMap.getString(row, "textField_m18x4yqo") + "_" + UtilMap.getString(row, "textField_m2b2vx5x");
                 if (psList.contains(uCode)) {
                 if (psList.contains(uCode)) {
                     row.put("selectField_m10k093c", "失败");
                     row.put("selectField_m10k093c", "失败");
@@ -594,15 +588,12 @@ public class AWYDImplClient implements AWYDClient {
             // 控制匹配条件 [ prd: 同时兼容, 有编号未匹配到数据, 通过填写内容识别 ]
             // 控制匹配条件 [ prd: 同时兼容, 有编号未匹配到数据, 通过填写内容识别 ]
             for (Object prop : condition.keySet()) {
             for (Object prop : condition.keySet()) {
                 if (UtilMap.isBlankString(row, String.valueOf(prop))) {
                 if (UtilMap.isBlankString(row, String.valueOf(prop))) {
-                    row.put("selectField_m10k093c", "失败");
-                    row.put("textareaField_m10k093a", condition.get(prop) + ", 请检查数据后重新导入");
+                    // prd 11.01 测试规格调整,全部字段不做校验, 功能、性能需求也会存在空情况
+                    row.put("selectField_m10k093c", "成功");
+                    row.put("textareaField_m10k093a", "测试规格导入全部字段不做校验, 功能、性能需求也会存在空情况");
                     break;
                     break;
                 }
                 }
             }
             }
-            if (UtilMap.isNotBlankString(row, "textareaField_m10k093a")) {
-                isSuccess = false;
-                continue;
-            }
             if (!isSubmit) {
             if (!isSubmit) {
                 // 若有,执行替换逻辑, 备注体现原始被替换编号
                 // 若有,执行替换逻辑, 备注体现原始被替换编号
                 List<Map> searchCondition = new ArrayList<>();
                 List<Map> searchCondition = new ArrayList<>();
@@ -669,7 +660,7 @@ public class AWYDImplClient implements AWYDClient {
             }
             }
             // 主表信息需完善
             // 主表信息需完善
             detail.putAll(UtilMap.map("selectField_lwq7bv4z, selectField_lwq7bv51", "charter, pdt", data));
             detail.putAll(UtilMap.map("selectField_lwq7bv4z, selectField_lwq7bv51", "charter, pdt", data));
-            detail.put("radioField_lti2e636", "启用");
+            detail.put("radioField_lti2e636", "");
             String formInstId = (String) ydClient.operateData(YDParam.builder()
             String formInstId = (String) ydClient.operateData(YDParam.builder()
                     .formUuid("FORM-767ECC0F8CCE4FB0BC48ECC63164F297BS77")
                     .formUuid("FORM-767ECC0F8CCE4FB0BC48ECC63164F297BS77")
                     .appType(appType_ts)
                     .appType(appType_ts)

+ 120 - 0
mjava-aiwei/src/main/java/com/malk/aiwei/service/impl/GZT_PNImpl.java

@@ -0,0 +1,120 @@
+package com.malk.aiwei.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.aiwei.service.GZT_PN;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.server.dingtalk.DDR_New;
+import com.malk.service.aliwork.YDClient;
+import com.malk.service.dingtalk.DDClient;
+import com.malk.service.dingtalk.DDService;
+import com.malk.utils.UtilDateTime;
+import com.malk.utils.UtilMap;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+@EnableScheduling
+@Service
+@Slf4j
+public class GZT_PNImpl implements GZT_PN {
+
+    private Map portal_cache = new HashMap();
+    @Autowired
+    private YDClient ydClient;
+
+    @Override
+    public List<Map> getPortalList(String section) {
+        return _portalList(section);
+    }
+
+    @Override
+    public void getDataList() {
+        try {
+
+            portal_cache = new HashMap();
+            getPortalList("派能资讯");
+            getPortalList("能量派送");
+            getPortalList("人资资讯");
+            getPortalList("内部资讯");
+
+        } catch (Exception e) {
+            // 记录错误信息
+            e.printStackTrace();
+        }
+    }
+
+    private List<Map> _portalList(String type) {
+        List<Map> pList = UtilMap.getList(portal_cache, type);
+        try {
+            if (pList.isEmpty()) {
+                List<Map> dataList = (List<Map>) ydClient.queryData(YDParam.builder()
+                        .appType("APP_OM7HTYCXYQYCKJ046D9V")
+                        .systemToken("BN766KC1CS9Q2LJQDAKIN82VVUHT2J07ALF3M7R2")
+                        .formUuid("FORM-918FE77C2E8248069B7CA497C6B2E4C6XD4B")
+                        .pageSize(5)
+                        .searchFieldJson(JSONObject.toJSONString(UtilMap.map("selectField_m17kv4an", type)))
+                        .build(), YDConf.FORM_QUERY.retrieve_search_form).getData();
+
+                dataList.sort(Comparator.comparingInt(item -> UtilMap.getInt(item, "numberField_m3s6swzt"))); // 排序
+                pList = dataList.stream().map(item -> {
+                    Map formData = UtilMap.getMap(item, "formData");
+
+                    Map row = UtilMap.map("title, source, link", UtilMap.getString(formData, "textField_m17kv4aq"), UtilMap.getString(formData, "textField_m17kv4as"), UtilMap.getString(formData, "textField_m17kv4at"));
+                    long date = UtilMap.getLong(formData, "dateField_m17kv4ar");
+                    if (date > 0L) {
+                        row.put("dateTime", UtilDateTime.format(new Date(date), "yyyy-MM-dd HH:mm"));
+                    }
+                    // 图片免登处理
+                    String image = UtilMap.getString(formData, "imageField_m1abjxl0");
+                    if (StringUtils.isNotBlank(image)) {
+                        List<Map> attas = (List<Map>) JSON.parse(image);
+                        row.put("image", convertTemporaryUrl_PN(UtilMap.getString(attas.get(0), "url")));
+                        Object image1 = row.get("image");
+                        //   System.out.println("image=========" + UtilMap.getString(attas.get(0), "url"));
+//                    System.out.println("image1========="+image1);
+                    }
+                    return row;
+                }).collect(Collectors.toList());
+                portal_cache.put(type, pList);
+            }
+        } catch (Exception e) {
+
+        }
+        log.info("type: {},list:{}", type, pList);
+        return pList;
+    }
+
+    @Autowired
+    private DDClient ddClient;
+
+    /**
+     * 派能
+     */
+    public String convertTemporaryUrl_PN(String url) {
+        String ddapptonken = ddClient.getAccessToken("dingmpxci8bolc3jpima", "Y_k3jpKNHbGvb9S9As2Y61ZaUFNglm7SCqquIkcowLBRoc4ZpH7DG0ZTn8LyHMwI");
+        Map param = new HashMap();
+        param.put("systemToken", "BN766KC1CS9Q2LJQDAKIN82VVUHT2J07ALF3M7R2");
+        param.put("userId", YDConf.PUB_ACCOUNT);
+        param.put("fileUrl", url);          // URL在param上时, 需要编码 [UtilHttp已经做了编码] - URLEncoder.encode(url, "UTF-8")
+        param.put("timeout", 60000);      // 默认1分钟, 最大24小时 [毫秒]
+        // System.out.println("ssss:"+(String) DDR_New.doGet("https://api.dingtalk.com/v1.0/yida/apps/temporaryUrls/APP_G951QZ32AUJNJUE4G127" , ddClient.initTokenHeader_PN(), param).getResult());
+        return (String) DDR_New.doGet("https://api.dingtalk.com/v1.0/yida/apps/temporaryUrls/APP_OM7HTYCXYQYCKJ046D9V", DDConf.initTokenHeader(ddapptonken), param).getResult();
+    }
+
+    @Autowired
+    private DDService ddService;
+
+    @Override
+    public Map register(Map<String, String> data) {
+        String ddapptonken = ddClient.getAccessToken("dingmpxci8bolc3jpima", "Y_k3jpKNHbGvb9S9As2Y61ZaUFNglm7SCqquIkcowLBRoc4ZpH7DG0ZTn8LyHMwI");
+        return ddService.registerJsApi(ddapptonken, data.get("url"), data.get("nonceStr"));
+    }
+}

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


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


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


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


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


+ 303 - 4
mjava-cloudpure/src/main/java/com/malk/cloudpure/controller/TSController.java

@@ -11,6 +11,7 @@ import com.malk.service.dingtalk.DDClient;
 import com.malk.service.dingtalk.DDClient_Contacts;
 import com.malk.service.dingtalk.DDClient_Contacts;
 import com.malk.service.dingtalk.DDClient_Workflow;
 import com.malk.service.dingtalk.DDClient_Workflow;
 import com.malk.utils.UtilDateTime;
 import com.malk.utils.UtilDateTime;
+import com.malk.utils.UtilList;
 import com.malk.utils.UtilMap;
 import com.malk.utils.UtilMap;
 import lombok.SneakyThrows;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
@@ -22,10 +23,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.bind.annotation.RestController;
 
 
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
 /**
 /**
@@ -65,6 +63,307 @@ public class TSController {
         return McR.success();
         return McR.success();
     }
     }
 
 
+    // 12.09 蓝云账龄表
+
+    /**
+     * 同步开票档案账龄表
+     */
+
+    private YDParam.YDParamBuilder _initLYParam() {
+        return YDParam.builder()
+                .appType("APP_ERBDTFS82HOVBPL3NFH0")
+                .systemToken("RRB66F91T97H1WN89QZYC47PKLZO2ZQOUMOQLP");
+    }
+
+    /**
+     * 蓝云, 同步催款函
+     */
+    @GetMapping("syncCallLetters")
+    public McR syncCallLetters() {
+        List<Map> dataList = ydService.queryFormData_all(_initLYParam()
+                .formUuid("FORM-EC785A5AB2B9432C892062823EB7C62A9NTL")
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("selectField_lvituew9, radioField_m4qrz687", "正常", "否")))
+                .build());
+        // 更新数据版本, upsert不支持版本更新
+//        if (true) {
+//            for (Map data : dataList) {
+//                ydClient.operateData(_initLYParam()
+//                        .formInstanceId(UtilMap.getString(data, "formInstanceId"))
+//                        .updateFormDataJson(JSON.toJSONString(UtilMap.empty()))
+//                        .useLatestVersion(true)
+//                        .build(), YDConf.FORM_OPERATION.delete);
+//            }
+//            return McR.success();
+//        }
+
+        // 公司主体
+        List<Map> subjectList = ydService.queryFormData_all(_initLYParam()
+                .formUuid("FORM-B1425F3ECC294B858B267A806CB5AEDEFTND")
+                .build());
+        log.info("同步账龄表数据, {}", dataList.size());
+        // 匹配逻辑: 客户 + 蓝云主体【明细按照开票周期】
+        Map<String, Map> setMap = new HashMap();
+        for (Map data : dataList) {
+            String kpgs = UtilMap.getString(data, "selectField_lvc9x4vo");  // 开票公司
+            String khmc = UtilMap.getString(data, "selectField_lvdojfui");  // 客户名称
+            String unique = kpgs + "_" + khmc;
+            Map formData = UtilMap.getMap(setMap, unique); // 累计标识
+            if (!setMap.containsKey(unique)) {
+                // 主表信息: 由 --> 至, 合计金额
+                formData = UtilMap.map("textField_m06ij3z7, textField_m06ij3z8, numberField_m06lmogz", kpgs, khmc, UtilMap.getFloat(data, "numberField_m4qrz685"));
+                List<Map> subjects = subjectList.stream().filter(item -> kpgs.equals(UtilMap.getString(item, "textField_lrintpap"))).collect(Collectors.toList()); // 精准匹配
+                if (subjects.size() == 0) {
+                    continue;
+                }
+                formData.putAll(UtilMap.map("textField_m06ij3zr, textField_m06ij3zs", kpgs, UtilMap.getString(subjects.get(0), "textField_m4qrp33v")));
+                // 付款信息, 指定主体下账号
+                List<Map> details = (List<Map>) UtilMap.getList(subjects.get(0), "tableField_luq9ph7m").stream().filter(item -> "是".equals(((Map) item).get("radioField_m4s770ep"))).collect(Collectors.toList());
+                if (details.size() > 0) {
+                    // 开户行, 银行账号
+                    formData.putAll(UtilMap.map("textField_m06ij3zt, textField_m06ij3zu", "textField_lrpq6es0, textField_lrpq6es1, ", details.get(0)));
+                }
+                // 开票子表: 业务内容-开票内容, 发票号码, 开票时间-开票周期(文本), 项目名称, 金额-未回款金额, 备注-差异说明
+                Map sub = UtilMap.map("textField_m06ij3zf, textField_m06ij3zg, textField_m06ij3zh, textField_m06ij3zi, numberField_m06ij3zk, textField_m06ij3zj", "textField_lvdnme0u, textField_lvd8pp35, textField_m25j5gxv, textField_lvdntzul, numberField_m4qrz685, textareaField_m25j5gyc", data);
+                sub.put("textField_m06ij3ze", 1); // 序号
+                formData.put("tableField_m06ij3zd", UtilList.asList(sub));
+            } else {
+                List<Map> table = UtilMap.getList(formData, "tableField_m06ij3zd");
+                // 开票子表: 业务内容-开票内容, 发票号码, 开票时间-开票周期(文本), 项目名称, 金额-未回款金额, 备注-差异说明
+                Map sub = UtilMap.map("textField_m06ij3zf, textField_m06ij3zg, textField_m06ij3zh, textField_m06ij3zi, numberField_m06ij3zk, textField_m06ij3zj", "textField_lvdnme0u, textField_lvd8pp35, textField_m25j5gxv, textField_lvdntzul, numberField_m4qrz685, textareaField_m25j5gyc", data);
+                sub.put("textField_m06ij3ze", table.size() + 1); // 序号
+                table.add(sub);
+                formData.put("tableField_m06ij3zd", table);
+                // 累计合计金额
+                formData.put("numberField_m06lmogz", UtilMap.getFloat(formData, "numberField_m06lmogz") + UtilMap.getFloat(data, "numberField_m4qrz685"));
+            }
+            setMap.put(unique, formData);
+        }
+
+        log.info("催款函, {}", setMap.size());
+        for (String unique : setMap.keySet()) {
+            Map formData = UtilMap.getMap(setMap, unique);
+            formData.put("textField_m4qxo07x", unique + "_" + UtilMap.getFloat(formData, "numberField_m06lmogz"));
+            ydClient.operateData(_initLYParam()
+                    .formUuid("FORM-738D89FEC34740EC92B08BF6D7B9470DEQY6")
+                    .processCode("TPROC--IMD665A1KDXNJ2V47QQ9UCM9I9F827DEEI60M6")
+                    .formDataJson(JSON.toJSONString(formData))
+                    .userId("396511732") // 通过宜搭平台发起的,不会有待办, 通过企业账号发送 (system)
+                    .build(), YDConf.FORM_OPERATION.start);
+        }
+        return McR.success();
+    }
+
+
+    /**
+     * 蓝云, 全量同步账龄表 [后续可调整为同步前一天更新数据]
+     */
+    @GetMapping("syncAgingSchedule")
+    public void syncAgingSchedule() {
+
+        List<Map> dataList = ydService.queryFormData_all(_initLYParam()
+                .formUuid("FORM-6603375ED27B4D059CBB919C2BEFA44BZVOL")
+//                .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_lvdosccc", "KP_2024121301677")))
+                .build());
+
+        // 合同业务类型: 日保一次性与小业主一致
+        Map compIds = UtilMap.map("大业主, 小业主, 工程订单, 日保一次性", "tableField_lvc9x4vt, tableField_lvdnme13, tableField_lvd8pp44, tableField_lvdnme13");
+
+        // 账龄表主表: 1 开票档案, 合同业务类型, 开票编号
+        Map compId_main = UtilMap.map("associationFormField_m25j5gxb, selectField_lvc9x4vn, textField_lvdosccc", "kpdh_gl, selectField_lvc9x4vn, textField_lvdosccc");
+        // 账龄表主表: 2 开票公司, 公司编码, 开票类型,
+        compId_main.putAll(UtilMap.map("selectField_lvc9x4vo, textField_m065jst5, selectField_lvc9x4vp", "selectField_lvc9x4vo, textField_m065jst5, selectField_lvc9x4vp"));
+        // 账龄表主表: 3 项目点名称, 项目点编号, 收款协议
+        compId_main.putAll(UtilMap.map("textField_lvdntzul, textField_m25j5gxk, selectField_lvc9x4vq", "textField_m4h15mt7, textField_lvdntzul, selectField_lvc9x4vq"));
+        // 账龄表主表: 4 客户名称, 客户编号, 实际开票日期
+        compId_main.putAll(UtilMap.map("selectField_lvdojfui, textField_lvdojfuj, dateField_lw5ud9bk", "selectField_lvdojfui, textField_lvdojfuj, dateField_lw5ud9bk"));
+        // 账龄表主表: 5 实例ID, 发票号码, 发票状态
+        compId_main.putAll(UtilMap.map("textField_m25j5gyi, textField_lvd8pp35, selectField_lvituew9", "formInstanceId, textField_lvd8pp35, selectField_lvituew9"));
+
+        // 账龄表子表: 1 开票周期, 开票周期-文本, 开票内容, 含税单价, 数量, 税率
+        List<String> compId_detail = UtilList.asList("dateField_m25j5gxu", "textField_m25j5gxv", "textField_lvdnme0u", "numberField_lvdnme0w", "numberField_lvdnme0x", "numberField_lvdnme0y");
+        // 账龄表子表: 2 含税小计, 税额, 无税金额, 差异说明, 已回款金额, 回款周期, 回款周期-文本
+        compId_detail.addAll(Arrays.asList("numberField_lvdnme0z", "numberField_lvdnme10", "numberField_lvdnme11", "textareaField_m25j5gyc", "numberField_lvg084l9", "dateField_m0dp1g0e", "textField_m25j5gyh"));
+        // 账龄表子表: 匹配来源数据表
+        Map compIds_tab = UtilMap.map("tableField_lvc9x4vt, tableField_lvd8pp44, tableField_lvdnme13",
+                // 大业主对应子表
+                Arrays.asList("dateField_lw5uybgq", "dateField_lw5uybgq_wb", "textField_lvd8pp2t", "numberField_lvd8pp2v", "numberField_lvd8pp2w", "numberField_lvd8pp2x", "numberField_lvd8pp2y", "numberField_lvd8pp2z", "numberField_lvd8pp30", "textField_lvdnme0g", "numberField_lvg084lb", "dateField_m0f55roc", "dateField_m0f55roc_wb"),
+                // 工程订单对应子表
+                Arrays.asList("dateField_lvd8pp45", "ateField_lvd8pp45_wb", "textField_lvd8pp3w", "numberField_lvd8pp3y", "numberField_lvd8pp3z", "numberField_lvd8pp40", "numberField_lvd8pp41", "numberField_lvd8pp42", "numberField_lvd8pp43", "textField_lvdnme0h", "numberField_lvg084l9", "dateField_m0dp1g0e", "dateField_m0dp1g0e_wb"),
+                // 小业主\日报一次性对应子表
+                Arrays.asList("dateField_lvdnme0t", "dateField_lvdnme0t_wb", "textField_lvdnme0u", "numberField_lvdnme0w", "numberField_lvdnme0x", "numberField_lvdnme0y", "numberField_lvdnme0z", "numberField_lvdnme10", "numberField_lvdnme11", "textField_lvdnme12", "numberField_lvg084la", "dateField_m0f55roe", "dateField_m0f55roe_wb"));
+
+        // 明细处理为主表记录, 以明细形式写入
+        for (Map formData : dataList) {
+
+            String compId_type = UtilMap.getString(compIds, UtilMap.getString(formData, "selectField_lvc9x4vn"));
+            List<Map> details = UtilMap.getList(formData, compId_type);
+            log.info("账龄表同步, {}, {}, {}", UtilMap.getString(formData, "selectField_lvc9x4vn"), formData.get("formInstanceId"), details.size());
+            if (details.isEmpty()) {
+                continue;
+            }
+            // 主表字段匹配 kpdh_gl
+            Map dataForm = UtilMap.empty();
+            for (Object key : compId_main.keySet()) {
+                if ("kpdh_gl".equals(compId_main.get(key))) {
+                    dataForm.put(key, YDConf.associationForm("APP_ERBDTFS82HOVBPL3NFH0", "FORM-6603375ED27B4D059CBB919C2BEFA44BZVOL", UtilMap.getString(formData, "formInstanceId"), UtilMap.getString(formData, "textField_lvdosccc"), null, false));
+                } else {
+                    dataForm.put(key, formData.get(compId_main.get(key)));
+                }
+            }
+            // 子表数据匹配 _wb
+            for (Map detail : details) {
+                // 唯一条件: 单据编号 + 业务类型 + 开票周期
+                String compId_date = (String) UtilMap.getList(compIds_tab, compId_type).get(0);
+                long kpzq = UtilMap.getLong(detail, compId_date);
+                if (kpzq == 0) {
+                    continue;
+                }
+                List<String> arrCompId = UtilMap.getList(compIds_tab, compId_type);
+                for (int i = 0; i < arrCompId.size(); i++) {
+                    String key = compId_detail.get(i);
+                    if (arrCompId.get(i).contains("dateField_")) {
+                        String cId = arrCompId.get(i).replace("_wb", "");
+                        if (!detail.containsKey(cId)) {
+                            continue;
+                        }
+                        long tm = UtilMap.getLong(detail, cId);
+                        if (tm == 0) {
+                            continue;
+                        }
+                        if (arrCompId.get(i).contains("_wb")) {
+                            dataForm.put(key, UtilDateTime.format(new Date(tm), "yyyy-MM"));
+                        } else {
+                            dataForm.put(key, tm);
+                        }
+                    } else {
+                        dataForm.put(key, detail.get(arrCompId.get(i)));
+                    }
+                }
+                String kpzq_wb = UtilDateTime.format(new Date(kpzq), "yyyy-MM");
+                List<Map> searchCondition = Arrays.asList(YDConf.searchCondition_TextFiled("textField_lvdosccc", UtilMap.getString(formData, "textField_lvdosccc"), "eq"),
+                        YDConf.searchCondition_TextFiled("selectField_lvc9x4vn", UtilMap.getString(formData, "selectField_lvc9x4vn"), "eq"),
+                        YDConf.searchCondition_TextFiled("textField_m25j5gxv", kpzq_wb, "eq"));
+                // 回款状态与未回款金额记录, 用于催款函查询
+                float figure = UtilMap.getFloat(dataForm, "numberField_lvdnme0z") - UtilMap.getFloat(dataForm, "numberField_lvg084l9");
+                dataForm.put("radioField_m4qrz687", figure == 0 ? "是" : "否");
+                dataForm.put("numberField_m4qrz685", figure);
+                ydClient.operateData(_initLYParam()
+                        .searchCondition(JSON.toJSONString(searchCondition))
+                        .formUuid("FORM-EC785A5AB2B9432C892062823EB7C62A9NTL")
+                        .formDataJson(JSON.toJSONString(dataForm))
+                        .build(), YDConf.FORM_OPERATION.upsert_v2);
+            }
+        }
+    }
+
+    // 12.17 JL华南用户加入一分
+    @GetMapping("jll/invite")
+    public McR JLL_invite() {
+
+        // 授权专属账号允许加入一分组织
+//        String token = ddClient.getAccessToken("dingy8h3bgoqsdparydl", "Zshlws58uFmezijH7TAn2LgXvh2qqXUUtpObXJQODlE5SeWAQYmvKbgIX_ona0_t");
+//        ddClient_contacts.multiOrgPermissions(token, "dingc54a4bb4def9663f35c2f4657eb6378f", null);
+
+        // 0109 授权专属账号允许加入上海分组织 (华东)
+        String token = ddClient.getAccessToken("dingy8h3bgoqsdparydl", "Zshlws58uFmezijH7TAn2LgXvh2qqXUUtpObXJQODlE5SeWAQYmvKbgIX_ona0_t");
+        //ddClient_contacts.multiOrgPermissions(token, "dingc54a4bb4def9663f35c2f4657eb6378f", null);
+        ddClient_contacts.multiOrgPermissions(token, "ding120e2a852a39a5df35c2f4657eb6378f", null);
+
+        List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
+                .appType("APP_GW0W0VPQ0FR53R258J6G")
+                .systemToken("WO966N71H84IUHE3AYRISBIWQTIH39K5R1ZRL32")
+                .formUuid("FORM-F521892655A047C693B70982B97F8027WZRN")
+                .build());
+        log.info("用户列表, {}", dataList.size());
+
+//        String ddToken = ddClient.getAccessToken("dingz2yea3codfo8c7lm", "JyytgtZZPmPVKifyiWO34cuQJZp6c99NMEJ7CfPoSVR8WceDRBPlnCU9_Zf1sQKT");
+        // 0109 专属钉账号加入上海分, 保留华东原架构
+        String ddToken = ddClient.getAccessToken("dingljptjms8t3xdmieo", "oJmnvcLjTKiwRArUqvi5K9lfGyRx1yitkPtrAIvJ_RhWlbrwUZnrF8cMkf94kOxo");
+//        dataList = Arrays.asList(dataList.get(0));
+        for (Map data : dataList) {
+            if (UtilMap.getString(data, "radioField_m3zdctzd").equals("是")) {
+                continue;
+            }
+            String name = UtilMap.getString(data, "textField_p2em9tg") + "(企)";
+            //List<Long> deptId = Arrays.asList(UtilMap.getLong(data, "textField_fngklyc"));
+//            List<Long> deptId = Arrays.asList(980751899L); // 使用隐藏部门做中转
+            List<Long> deptId = Arrays.asList(1L); // 使用隐藏部门做中转
+            // userId、姓名、工号、职位、手机号、邮箱
+//            Map extInfo = UtilMap.map("userid, job_number, title, email, exclusive_mobile", "textField_disml2c, textField_8en47ss, textField_qasx8rm, textField_vyo7anl, textField_t6o72ey", data);
+            Map extInfo = UtilMap.map("userid, job_number, title, exclusive_mobile, email", "textField_disml2c, textField_8en47ss, textField_qasx8rm, textField_t6o72e0y, textField_vyo7anl", data);
+            extInfo.put("userid", UtilMap.getString(data, "textField_disml2c"));
+//            extInfo.put("userid", UtilMap.getString(data, "textField_disml2c") + "d");
+            if (data.containsKey("dateField_m54rr91h") && UtilMap.getLong(data, "dateField_m54rr91h") > 0) {
+                extInfo.put("hired_date", UtilMap.getLong(data, "dateField_m54rr91h"));
+            }
+            Map result = UtilMap.map("radioField_m3zdctzd, textareaField_m3zajr2x", "是", "");
+            try {
+                ddClient_contacts.inviteExclusiveUser(ddToken, name, deptId, "dingf475a1e690748017acaaa37764f94726", UtilMap.getString(data, "textField_disml2c"), extInfo);
+            } catch (McException e) {
+                result.put("radioField_m3zdctzd", "否");
+                result.put("textareaField_m3zajr2x", e.getMessage());
+            }
+            ydClient.operateData(YDParam.builder()
+                    .appType("APP_GW0W0VPQ0FR53R258J6G")
+                    .systemToken("WO966N71H84IUHE3AYRISBIWQTIH39K5R1ZRL32")
+                    .formInstanceId(UtilMap.getString(data, "formInstanceId"))
+                    .updateFormDataJson(JSON.toJSONString(result))
+                    .useLatestVersion(true)
+                    .build(), YDConf.FORM_OPERATION.update);
+        }
+
+
+        return McR.success();
+    }
+
+    // 11.27 JLL用户更新
+    @GetMapping("jll/contact")
+    public McR JLL_contact() {
+
+        List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
+                .appType("APP_GW0W0VPQ0FR53R258J6G")
+                .systemToken("WO966N71H84IUHE3AYRISBIWQTIH39K5R1ZRL32")
+                .formUuid("FORM-2DD2B036B7DF4DDCA329E19BD5285EEBNCB8")
+                .build());
+        log.info("用户列表, {}", dataList.size());
+
+        // 更新企业账号信息
+        String accessToken = ddClient.getAccessToken("dingy8h3bgoqsdparydl", "Zshlws58uFmezijH7TAn2LgXvh2qqXUUtpObXJQODlE5SeWAQYmvKbgIX_ona0_t");
+        dataList.forEach(formData -> {
+            if (UtilMap.getString(formData, "radioField_m3zdctzd").equals("是")) {
+                return;
+            }
+            String userId = UtilMap.getString(formData, "textField_m3zajr2t");
+            Map userInfo = null;
+            Map result = UtilMap.map("radioField_m3zdctzd", "是");
+            try {
+                userInfo = ddClient_contacts.getUserInfoById(accessToken, userId);
+            } catch (McException e) {
+                result.put("radioField_m3zdctzd", "是");
+                result.put("textareaField_m3zajr2x", e.getMessage());
+            }
+            if (!Objects.isNull(userInfo)) {
+                List<Long> deptIds = (List<Long>) UtilMap.getList(userInfo, "dept_id_list").stream().map(item -> Long.valueOf(String.valueOf(item))).collect(Collectors.toList());
+                Map update = UtilMap.map("exclusive_mobile, email", UtilMap.getString(formData, "textField_m3zajr2u"), UtilMap.getString(formData, "textField_m3zajr2v"));
+                try {
+                    ddClient_contacts.updateUser_dingTalk(accessToken, userId, deptIds, update);
+                } catch (McException e) {
+                    result.put("radioField_m3zdctzd", "否");
+                    result.put("textareaField_m3zajr2x", e.getMessage());
+                }
+            }
+            ydClient.operateData(YDParam.builder()
+                    .appType("APP_GW0W0VPQ0FR53R258J6G")
+                    .systemToken("WO966N71H84IUHE3AYRISBIWQTIH39K5R1ZRL32")
+                    .formInstanceId(UtilMap.getString(formData, "formInstanceId"))
+                    .updateFormDataJson(JSON.toJSONString(result))
+                    .useLatestVersion(true)
+                    .build(), YDConf.FORM_OPERATION.update);
+        });
+        return McR.success();
+    }
+
+
     // 蓝云 [采购付款更新]
     // 蓝云 [采购付款更新]
     private void _lanyun0724() {
     private void _lanyun0724() {
         List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
         List<Map> dataList = ydService.queryFormData_all(YDParam.builder()

+ 8 - 0
mjava-cloudpure/src/main/java/com/malk/cloudpure/delegate/DDDelegate.java

@@ -6,6 +6,8 @@ import org.springframework.context.annotation.Primary;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
+import java.util.Map;
+
 /**
 /**
  * OA审批事件 [主项目也有实现, 添加 @Primary 优先注入主项目实现]
  * OA审批事件 [主项目也有实现, 添加 @Primary 优先注入主项目实现]
  * -
  * -
@@ -52,4 +54,10 @@ public class DDDelegate implements DDEvent {
         if (isTerminate) approveResult = "terminated";
         if (isTerminate) approveResult = "terminated";
         log.info("审批实例回调执行业务逻辑, {}", approveResult);
         log.info("审批实例回调执行业务逻辑, {}", approveResult);
     }
     }
+    
+    // 考勤打卡事件回调
+    @Override
+    public void executeEvent_attendance_check(Map<String, ?> record) {
+        log.info("executeEvent_attendance_check");
+    }
 }
 }

+ 17 - 6
mjava-fengkaili/src/main/java/com/malk/fengkaili/service/impl/FKLImplService.java

@@ -235,6 +235,10 @@ public class FKLImplService implements FKLService {
                         if (result.contains("不在考勤组并打卡") && !result.equals("不在考勤组并打卡")) {
                         if (result.contains("不在考勤组并打卡") && !result.equals("不在考勤组并打卡")) {
                             result = result.replace("不在考勤组并打卡,", "");
                             result = result.replace("不在考勤组并打卡,", "");
                         }
                         }
+                        // prd 11.26 未排班并打卡 场景兼容
+                        if (result.contains("未排班并打卡")) {
+                            result = "";
+                        }
                         log.info("人员明细, {} - {}, {}", date, po.getName(), val.get("value"));
                         log.info("人员明细, {} - {}, {}", date, po.getName(), val.get("value"));
                         String day_1 = "zc", day_2 = "zc", type = "zc"; // 异常类型
                         String day_1 = "zc", day_2 = "zc", type = "zc"; // 异常类型
                         if (result.contains("休息") || result.contains("加班") || (val.get("value").contains("休息,") && (!result.contains("出差") && !result.contains("婚假") && !result.contains("产假")))) {
                         if (result.contains("休息") || result.contains("加班") || (val.get("value").contains("休息,") && (!result.contains("出差") && !result.contains("婚假") && !result.contains("产假")))) {
@@ -254,11 +258,14 @@ public class FKLImplService implements FKLService {
                             type = "zc";
                             type = "zc";
                             day_1 = type;
                             day_1 = type;
                             day_2 = type;
                             day_2 = type;
-                        } else if (result.contains("产假") || result.contains("陪产假") || result.contains("婚假") || result.contains("丧假") || result.contains("育儿假")) {
-                            type = result.split("假")[0] + "假"; // 按天请假
-                            day_1 = type;
-                            day_2 = type;
-                        } else if (result.contains("旷工") || result.equals("未打卡")) {
+                        }
+                        // prd 250205 请假为半天模式, 全天计算有误
+//                        else if (result.contains("产假") || result.contains("陪产假") || result.contains("婚假") || result.contains("丧假") || result.contains("育儿假")) {
+//                            type = result.split("假")[0] + "假"; // 按天请假
+//                            day_1 = type;
+//                            day_2 = type;
+//                        }
+                        else if (result.contains("旷工") || result.equals("未打卡")) {
                             type = "旷工"; // 兼容异常情况
                             type = "旷工"; // 兼容异常情况
                             day_1 = type;
                             day_1 = type;
                             day_2 = type;
                             day_2 = type;
@@ -402,6 +409,10 @@ public class FKLImplService implements FKLService {
                                         String[] arr = status.split(" ");
                                         String[] arr = status.split(" ");
                                         int sstart = Integer.valueOf(status.split(" ")[1].split("到")[0].replace(":", ""));
                                         int sstart = Integer.valueOf(status.split(" ")[1].split("到")[0].replace(":", ""));
                                         float day = Float.valueOf((arr[arr.length - 1].replace("天", "")));
                                         float day = Float.valueOf((arr[arr.length - 1].replace("天", "")));
+                                        // prd 250205 部分返回异常数据日期含有年, 需去除
+                                        if (date.split("-").length > 2) {
+                                            date = date.split("-")[1] + "-" + date.split("-")[2];
+                                        }
                                         boolean sDate = date.equals(status.split(" ")[0].replace(tmp, ""));
                                         boolean sDate = date.equals(status.split(" ")[0].replace(tmp, ""));
                                         boolean eDate = date.equals(status.split(" ")[1].split("到")[1]);
                                         boolean eDate = date.equals(status.split(" ")[1].split("到")[1]);
                                         type = tmp;
                                         type = tmp;
@@ -411,7 +422,7 @@ public class FKLImplService implements FKLService {
                                             day_1 = type;
                                             day_1 = type;
                                             day_2 = type;
                                             day_2 = type;
                                         } else {
                                         } else {
-                                            if (sstart >= 1200 && sDate) {
+                                            if (sEnd >= 1700) {
                                                 day_2 = type;
                                                 day_2 = type;
                                             } else {
                                             } else {
                                                 day_1 = type;
                                                 day_1 = type;

+ 6 - 0
mjava-hake/pom.xml

@@ -24,6 +24,12 @@
             <artifactId>mjava</artifactId>
             <artifactId>mjava</artifactId>
             <version>${mjava.version}</version>
             <version>${mjava.version}</version>
         </dependency>
         </dependency>
+        <!-- ok的Http连接池 -->
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.9.3</version>
+        </dependency>
     </dependencies>
     </dependencies>
 
 
     <build>
     <build>

+ 22 - 0
mjava-hake/src/main/java/com/malk/hake/controller/DDController.java

@@ -0,0 +1,22 @@
+package com.malk.hake.controller;
+
+import com.malk.controller.DDCallbackController;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 钉钉事件回调 3_1
+ * -
+ * [子项目直接继承即可有调用, 无需实现]
+ * -
+ * 注解 @RequestMapping 路径不能重复 [主子项目属同一个项目];
+ * 获取项目回调请求地址, https://mc.cloudpure.cn/frp/xxx/dd/callback [调试代理: frp + nginx]
+ */
+@Slf4j
+@RestController
+@RequestMapping("/dd")
+public class DDController extends DDCallbackController {
+
+
+}

+ 26 - 4
mjava-hake/src/main/java/com/malk/hake/controller/HKController.java

@@ -1,6 +1,7 @@
 package com.malk.hake.controller;
 package com.malk.hake.controller;
 
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.malk.hake.service.HKClient;
 import com.malk.hake.service.HKClient;
 import com.malk.server.aliwork.YDConf;
 import com.malk.server.aliwork.YDConf;
 import com.malk.server.aliwork.YDParam;
 import com.malk.server.aliwork.YDParam;
@@ -9,6 +10,7 @@ import com.malk.server.common.McR;
 import com.malk.server.common.McREnum;
 import com.malk.server.common.McREnum;
 import com.malk.service.aliwork.YDClient;
 import com.malk.service.aliwork.YDClient;
 import com.malk.service.aliwork.YDService;
 import com.malk.service.aliwork.YDService;
+import com.malk.service.dingtalk.DDClient_Event;
 import com.malk.utils.UtilMap;
 import com.malk.utils.UtilMap;
 import com.malk.utils.UtilServlet;
 import com.malk.utils.UtilServlet;
 import lombok.SneakyThrows;
 import lombok.SneakyThrows;
@@ -105,23 +107,43 @@ public class HKController {
      * 手动同步付款数据
      * 手动同步付款数据
      */
      */
     @PostMapping("monitor/sync")
     @PostMapping("monitor/sync")
-    McR pay(String code) {
+    McR pay(HttpServletRequest request) {
 
 
-        log.info("手动触发, 拉取Monitor同步");
-        hkClient.syncMonitor(code);
+        Map data = UtilServlet.getParamMap(request);
+        log.info("手动触发, 拉取Monitor同步, {}", data);
+        hkClient.syncMonitor(UtilMap.getString(data, "code"), data);
         return McR.success();
         return McR.success();
     }
     }
 
 
-
     @Autowired
     @Autowired
     private YDClient ydClient;
     private YDClient ydClient;
 
 
     @Autowired
     @Autowired
     private YDService ydService;
     private YDService ydService;
 
 
+    @Autowired
+    private DDClient_Event ddClient_event;
+
+    @PostMapping("invoke")
+    McR invoke(@RequestBody JSONObject data) {
+
+        ddClient_event.callBackEvent_Attendance(data);
+        return McR.success();
+    }
+
     @GetMapping("test")
     @GetMapping("test")
     McR test() {
     McR test() {
 
 
+//        List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
+//                .formUuid("FORM-96C8BF4B18044DF783ADFE1CF7931FE9P4BW")
+//                .build());
+//        for (Map item : dataList) {
+//            ydClient.operateData(YDParam.builder()
+//                    .formInstanceId(UtilMap.getString(item, "instanceId"))
+//                    .updateFormDataJson(JSON.toJSONString(UtilMap.map("employeeField_lo47byyj", Arrays.asList(UtilMap.getString(item, "textField_m4sjp8pf")))))
+//                    .build(), YDConf.FORM_OPERATION.update);
+//        }
+
 //        ydClient.operateData(YDParam.builder()
 //        ydClient.operateData(YDParam.builder()
 //                .formInstanceId("125e59bb-5f1f-4307-b809-79d0add5e28e")
 //                .formInstanceId("125e59bb-5f1f-4307-b809-79d0add5e28e")
 //                .updateFormDataJson(JSON.toJSONString(UtilMap.map("textareaField_lntocd8u", "管理员拒绝")))
 //                .updateFormDataJson(JSON.toJSONString(UtilMap.map("textareaField_lntocd8u", "管理员拒绝")))

+ 4 - 3
mjava-hake/src/main/java/com/malk/hake/controller/HKController_ML.java

@@ -106,10 +106,11 @@ public class HKController_ML {
      * 手动同步付款数据
      * 手动同步付款数据
      */
      */
     @PostMapping("monitor/sync")
     @PostMapping("monitor/sync")
-    McR pay(String code) {
+    McR pay(HttpServletRequest request) {
 
 
-        log.info("手动触发, 拉取Monitor同步");
-        hkClient.syncMonitor(code);
+        Map data = UtilServlet.getParamMap(request);
+        log.info("手动触发, 拉取Monitor同步, {}", data);
+        hkClient.syncMonitor(UtilMap.getString(data, "code"), data);
         return McR.success();
         return McR.success();
     }
     }
 }
 }

+ 71 - 0
mjava-hake/src/main/java/com/malk/hake/delegate/DDDelegate.java

@@ -0,0 +1,71 @@
+package com.malk.hake.delegate;
+
+import com.malk.delegate.DDEvent;
+import com.malk.hake.service.HKClient;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Primary;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+
+/**
+ * OA审批事件 [主项目也有实现, 添加 @Primary 优先注入主项目实现]
+ * -
+ * 取消方案: 撤销和拒绝流程不继续执行连接器, 因此不使用连接器与轮询审批记录方案 [低效而且占用较高钉钉api调次数];
+ * 优化方案: 通过事件订阅实现实时同步所有审批状态, 定时查询钉钉回调失败记录 [配置钉钉事件Delegate, 添加定时任务]
+ */
+@Slf4j
+@Service
+@Primary
+public class DDDelegate implements DDEvent {
+
+    // 审批任务回调执行业务逻辑
+    @Async
+    @Override
+    public void executeEvent_Task_Finish(String processInstanceId, String processCode, boolean isAgree, String remark) {
+        log.info("executeEvent_Task_Finish");
+    }
+
+    @Async
+    @Override
+    public void executeEvent_Task_Start(String processInstanceId, String processCode) {
+        log.info("executeEvent_Task_Start");
+    }
+
+    @Async
+    @Override
+    public void executeEvent_Task_Redirect(String processInstanceId, String processCode) {
+        log.info("executeEvent_Task_Redirect");
+    }
+
+    @Async
+    @Override
+    public void executeEvent_Instance_Start(String processInstanceId, String processCode) {
+        log.info("executeEvent_Instance_Start");
+    }
+
+
+    // 审批实例回调执行业务逻辑
+    @Async
+    @Override
+    public void executeEvent_Instance_Finish(String processInstanceId, String processCode, boolean isAgree, boolean isTerminate, String staffId) {
+        log.info("executeEvent_Instance_Finish");
+        String approveResult = isAgree ? "agree" : "refuse";
+        if (isTerminate) approveResult = "terminated";
+        
+
+        log.info("审批实例回调执行业务逻辑, {}", approveResult);
+    }
+
+    @Autowired
+    private HKClient hkClient;
+
+    // 考勤打卡事件回调
+    @Override
+    public void executeEvent_attendance_check(Map<String, ?> record) {
+        log.info("executeEvent_attendance_check");
+        hkClient.callbackAttendance(record);
+    }
+}

+ 10 - 2
mjava-hake/src/main/java/com/malk/hake/schedule/HKScheduleTask.java

@@ -49,7 +49,7 @@ public class HKScheduleTask {
     @Scheduled(cron = "0 0 1 * * ? ")
     @Scheduled(cron = "0 0 1 * * ? ")
     public void timer_3() {
     public void timer_3() {
         try {
         try {
-            hkClient.syncMonitor("FKSQ");
+            hkClient.syncMonitor("FKSQ", null);
         } catch (Exception e) {
         } catch (Exception e) {
             // 记录错误信息
             // 记录错误信息
             e.printStackTrace();
             e.printStackTrace();
@@ -62,10 +62,18 @@ public class HKScheduleTask {
     @Scheduled(cron = "0 0 2 * * ? ")
     @Scheduled(cron = "0 0 2 * * ? ")
     public void timer_4() {
     public void timer_4() {
         try {
         try {
-            hkClient.syncMonitor("YFKSQ");
+            hkClient.syncMonitor("YFKSQ", null);
         } catch (Exception e) {
         } catch (Exception e) {
             // 记录错误信息
             // 记录错误信息
             e.printStackTrace();
             e.printStackTrace();
         }
         }
     }
     }
+
+    /**
+     * 清理打卡标记位: 每天早餐7点
+     */
+    @Scheduled(cron = "0 0 7 * * ? ")
+    public void timer_5() {
+        hkClient.clearClockStatus();
+    }
 }
 }

+ 11 - 1
mjava-hake/src/main/java/com/malk/hake/service/HKClient.java

@@ -22,5 +22,15 @@ public interface HKClient {
     /**
     /**
      * 付款数据同步
      * 付款数据同步
      */
      */
-    void syncMonitor(String code);
+    void syncMonitor(String code, Map data);
+    
+    /**
+     * 考勤回调
+     */
+    void callbackAttendance(Map data);
+
+    /**
+     * 清理标记位
+     */
+    void clearClockStatus();
 }
 }

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

@@ -22,5 +22,5 @@ public interface HKClient_ML {
     /**
     /**
      * 付款数据同步
      * 付款数据同步
      */
      */
-    void syncMonitor(String code);
+    void syncMonitor(String code, Map data);
 }
 }

+ 154 - 2
mjava-hake/src/main/java/com/malk/hake/service/impl/HKImplClient.java

@@ -16,6 +16,7 @@ import com.malk.utils.*;
 import lombok.SneakyThrows;
 import lombok.SneakyThrows;
 import lombok.Synchronized;
 import lombok.Synchronized;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
@@ -38,6 +39,14 @@ public class HKImplClient implements HKClient {
         return "http://116.228.113.106:10001/api/public" + path;
         return "http://116.228.113.106:10001/api/public" + path;
     }
     }
 
 
+    // monitor 考勤同步:001_1.1 测试环境;001.1 生产环境
+    String _getMonitorApi(String path) {
+        if (UtilEnv.getActiveProfile().equals(UtilEnv.ENV_PROD)) {
+            return "https://172.16.20.13:8001/zh/001.1" + path;
+        }
+        return "https://116.228.113.106:10028/zh/001.1" + path;
+    }
+
     private Map MATE;
     private Map MATE;
 
 
     private Map _getMeta() {
     private Map _getMeta() {
@@ -231,6 +240,9 @@ public class HKImplClient implements HKClient {
             _syncContact(data, map.get("userId"));
             _syncContact(data, map.get("userId"));
             log.info("离职人员, {}", JSON.toJSONString(UtilMap.map("employees", Arrays.asList(data))));
             log.info("离职人员, {}", JSON.toJSONString(UtilMap.map("employees", Arrays.asList(data))));
         }
         }
+
+        userIdReflect = null;
+        log.info("重新同步人员映射表", getUserIdReflect().size());
     }
     }
 
 
     /// 员工信息写入monitor
     /// 员工信息写入monitor
@@ -239,6 +251,12 @@ public class HKImplClient implements HKClient {
         // prd: 通过 http status 判定, 200 即为成功
         // prd: 通过 http status 判定, 200 即为成功
         String rsp = UtilHttp.doPost(_getEnvApi("/EmployeeImport"), UtilMap.map("Authorization", AUTH), null, UtilMap.map("employees", Arrays.asList(data)));
         String rsp = UtilHttp.doPost(_getEnvApi("/EmployeeImport"), UtilMap.map("Authorization", AUTH), null, UtilMap.map("employees", Arrays.asList(data)));
         Map result = (Map) JSON.parse(rsp);
         Map result = (Map) JSON.parse(rsp);
+
+        // 12.17 记录 monitor userId, 用于传递考勤同步数据
+        List<String> employIds = new ArrayList<String>();
+        if (UtilList.isNotEmpty(UtilMap.getList(result, "newPerson"))) {
+            employIds = UtilMap.getList(result, "newPerson");
+        }
         if (UtilList.isNotEmpty(UtilMap.getList(result, "fail"))) {
         if (UtilList.isNotEmpty(UtilMap.getList(result, "fail"))) {
             result = (Map) UtilMap.getList(result, "fail").get(0); // 单条同步, 记录错误人员
             result = (Map) UtilMap.getList(result, "fail").get(0); // 单条同步, 记录错误人员
         }
         }
@@ -248,6 +266,20 @@ public class HKImplClient implements HKClient {
                     .formUuid("FORM-W2A66Z91912F39B2AHY0VD4CRK9F3C4JB74OLN")
                     .formUuid("FORM-W2A66Z91912F39B2AHY0VD4CRK9F3C4JB74OLN")
                     .formDataJson(JSON.toJSONString(UtilMap.map("dateField_lo47byyd, employeeField_lo47byyj, textareaField_lo47byyo, textareaField_lo47sanj", new Date().getTime(), userId, result, data)))
                     .formDataJson(JSON.toJSONString(UtilMap.map("dateField_lo47byyd, employeeField_lo47byyj, textareaField_lo47byyo, textareaField_lo47sanj", new Date().getTime(), userId, result, data)))
                     .build(), YDConf.FORM_OPERATION.create);
                     .build(), YDConf.FORM_OPERATION.create);
+        } else {
+            // prd 新增用户会返回 newPerson 集合, 更新不会 (若包含了中横线, 则格式为: 工号-EmployeeId, 即 monitorId)
+            if (!employIds.isEmpty()) {
+                String monitorId = employIds.get(0);
+                if (monitorId.contains("-")) {
+                    monitorId = monitorId.split("-")[1];
+                }
+                List<Map> searchCondition = Arrays.asList(YDConf.searchCondition_TextFiled("textField_m4sjp8pf", userId, "eq"));
+                ydClient.operateData(YDParam.builder()
+                        .formUuid("FORM-96C8BF4B18044DF783ADFE1CF7931FE9P4BW")
+                        .searchCondition(JSON.toJSONString(searchCondition))
+                        .formDataJson(JSON.toJSONString(UtilMap.map("employeeField_lo47byyj, textField_m4sjp8pf, textField_m4sjp8pg", Arrays.asList(userId), userId, monitorId)))
+                        .build(), YDConf.FORM_OPERATION.upsert_v2);
+            }
         }
         }
     }
     }
 
 
@@ -258,15 +290,22 @@ public class HKImplClient implements HKClient {
      * 付款数据同步
      * 付款数据同步
      */
      */
     @Override
     @Override
-    public void syncMonitor(String code) {
+    public void syncMonitor(String code, Map data) {
 
 
         McException.assertAccessException(!Arrays.asList("FKSQ", "YFKSQ").contains(code), "单据编码未匹配");
         McException.assertAccessException(!Arrays.asList("FKSQ", "YFKSQ").contains(code), "单据编码未匹配");
         boolean isYF = "YFKSQ".equals(code);
         boolean isYF = "YFKSQ".equals(code);
 
 
-        String rsp = UtilHttp.doPost(_getEnvApi(isYF ? "/PrePayment" : "/Payment"), UtilMap.map("Authorization", AUTH), null, UtilMap.empty());
+        // prd 25.01.06 预付款订单数据支持筛选: 采购订单号、供应商号、供应商名称的筛选条件,如果为空就查询所有的数据,有值就查询出对应值的数据
+        Map body = UtilMap.empty();
+        if (isYF) {
+            UtilMap.putAll(body, data);
+        }
+        String rsp = UtilHttp.doPost(_getEnvApi(isYF ? "/PrePayment" : "/Payment"), UtilMap.map("Authorization", AUTH), null, body);
         Map result = (Map) JSON.parse(rsp);
         Map result = (Map) JSON.parse(rsp);
         List<Map> dataList = (List<Map>) UtilMap.getList(result, isYF ? "orders" : "rows");
         List<Map> dataList = (List<Map>) UtilMap.getList(result, isYF ? "orders" : "rows");
         log.info("付款数据同步, {} {}", code, dataList.size());
         log.info("付款数据同步, {} {}", code, dataList.size());
+
+
         // prd: 通过 http status 判定, 200 即为成功
         // prd: 通过 http status 判定, 200 即为成功
         for (Map row : dataList) {
         for (Map row : dataList) {
             Map search = null;
             Map search = null;
@@ -290,4 +329,117 @@ public class HKImplClient implements HKClient {
             upsertData(code, row);
             upsertData(code, row);
         }
         }
     }
     }
+
+    // ppExt: 基于 OkHttp 库实现, https带ip请求兼容性, 忽略证书校验 OkHttpUtil 类 [ 使用 HttpUtil 报异常 ]
+    @SneakyThrows
+    private Map okRequest(String url, String sessionId, Map data) {
+        log.info("ok request, {}, {}", url, data);
+        OkHttpClient client = new OkHttpClient().newBuilder()
+                .sslSocketFactory(OkHttpUtil.getIgnoreInitedSslContext().getSocketFactory(), OkHttpUtil.IGNORE_SSL_TRUST_MANAGER_X509)
+                .hostnameVerifier(OkHttpUtil.getIgnoreSslHostnameVerifier())
+                .build();
+        MediaType mediaType = MediaType.parse("application/json");
+        RequestBody body = RequestBody.create(mediaType, JSON.toJSONString(data));
+        Request request = new Request.Builder()
+                .url(url)
+                .addHeader("X-Monitor-SessionId", sessionId)
+                .method("POST", body)
+                .build();
+
+        Response response = client.newCall(request).execute();
+        String rsp = response.body().string();
+        log.info("ok response, {}", rsp);
+        try {
+            return JSON.parseObject(rsp);
+        } catch (Exception e) {
+            McException.exceptionAccess(rsp);
+        }
+        return null;
+    }
+
+    // 调用monitor标准接口, 需要获取sessionId
+    private String _getAuthSessionId() {
+
+        String url = _getMonitorApi("/login");
+        Map auth = UtilMap.map("Username, Password, ForceRelogin", "API.DD", "DD123456", true);
+        Map result = okRequest(url, "", auth);
+        return UtilMap.getString(result, "SessionId");
+    }
+
+    // 清理标记位
+    @Override
+    public void clearClockStatus() {
+        this.checkStatus = new HashMap<>();
+    }
+
+    // 考勤同步
+    private void _syncCheckingRecord(boolean isIn, String time, String employeeId) {
+        String type = isIn ? "ClockIn" : "ClockOut";
+        String url = _getMonitorApi("/api/v1/TimeRecording/RecordingDays/" + type);
+        // 若不传递 TimeStamp, 则会以当前时间
+        if (StringUtils.isBlank(time)) {
+            time = UtilDateTime.formatDateTime(new Date());
+        }
+        Map param = UtilMap.map("EmployeeId, TimeStamp", employeeId, time);
+        okRequest(url, _getAuthSessionId(), param);
+    }
+
+    // monitor 用户ID 与钉钉 userId 映射表 (通讯录同步后重置)
+    private List<Map> userIdReflect;
+
+    public List<Map> getUserIdReflect() {
+        if (ObjectUtil.isNull(userIdReflect)) {
+            userIdReflect = ydService.queryFormData_all(YDParam.builder()
+                    .formUuid("FORM-96C8BF4B18044DF783ADFE1CF7931FE9P4BW")
+                    .build());
+        }
+        return userIdReflect;
+    }
+
+    // 因 monitor 写入要求, 且钉钉打卡回调没有返回状态, 只有时间和人员, 添加写入标识
+    private Map<String, Boolean> checkStatus = new HashMap();
+
+    /**
+     * prd monitor clockIn, clockOut 必须成对出现, 且后一次时间必须在前一次时间之后, 否则均同步异常;
+     * [方案: in/out 若是同一个时间可以反复调用]
+     * 1. 钉钉已设置为严格打卡模式, 一次上班卡\一次下班卡, 成对出现, 不取值最早和最晚时间
+     * 2. 每天早上7点重置打卡标记状态, 避免通过标记第一次打卡就是 clock-out 情况, 导致后续与实际考勤一直异常
+     * 3. 首次 clock-in 若需要clock-out, 则先调用 clock-out 注意传递的时间为前一日 23:59:58, 然后再clock-in打卡时间; 反之传递当天 00:59:58. 以修正状态
+     */
+
+    // 钉钉打卡回调
+    @Override
+    public void callbackAttendance(Map data) {
+
+        String userId = UtilMap.getString(data, "userId");
+        String timeStamp = UtilDateTime.formatDateTime(new Date(UtilMap.getLong(data, "checkTime")));
+
+        boolean isIn = true;
+        if (checkStatus.containsKey(userId) && !checkStatus.get(userId)) {
+            isIn = false;
+        }
+        Optional optional = this.getUserIdReflect().stream().filter(item -> userId.equals(UtilMap.getString(item, "textField_m4sjp8pf"))).findAny();
+        if (!optional.isPresent()) {
+            McException.exceptionAccess("考勤同步, 用户未匹配");
+        }
+        String employeeId = UtilMap.getString((Map) optional.get(), "textField_m4sjp8pg");
+        try {
+            _syncCheckingRecord(isIn, timeStamp, employeeId);
+        } catch (Exception e) {
+            // fixme:  因 monitor 写入要求, 重复下班 expected Out got In 报错; 重复上班 expected In got Out 报错
+            if (e.getMessage().contains("expected In got Out") || e.getMessage().contains("expected In got Out")) {
+                String tTime = UtilDateTime.formatDate(new Date(UtilMap.getLong(data, "checkTime") - 24 * 60 * 60 * 1000));
+                if (e.getMessage().contains("expected In got Out")) {
+                    tTime = tTime + " 23:59:58";
+                }
+                if (e.getMessage().contains("expected Out got In")) {
+                    tTime = tTime + " 00:59:58";
+                }
+                _syncCheckingRecord(!isIn, tTime, employeeId);
+                _syncCheckingRecord(isIn, timeStamp, employeeId);
+            } else {
+                log.error("打卡异常, {}", e.getMessage());
+            }
+        }
+    }
 }
 }

+ 15 - 10
mjava-hake/src/main/java/com/malk/hake/service/impl/HKImplClient_ML.java

@@ -29,12 +29,13 @@ public class HKImplClient_ML implements HKClient_ML {
     @Autowired
     @Autowired
     private YDClient ydClient;
     private YDClient ydClient;
 
 
-    // monitor接口ip
+    // monitor接口ip [fixme 马哈使用test环境, 新建环境变量没有mjava继承文件
     String _getEnvApi(String path) {
     String _getEnvApi(String path) {
-        if (UtilEnv.getActiveProfile().equals(UtilEnv.ENV_PROD)) {
-//            return "http://172.16.20.13:10002/api/public" + path;
+        if (UtilEnv.getActiveProfile().equals(UtilEnv.ENV_TEST)) {
+            return "http://172.19.10.4:10001/api/public" + path;
         }
         }
-        return "http://hawkfiltration.fortiddns.com:10001/api/public" + path;
+//        return "http://hawkfiltration.fortiddns.com:10001/api/public" + path; // 10001 测试
+        return "http://hawkfiltration.fortiddns.com:10002/api/public" + path; // 10002 生产
     }
     }
 
 
     private Map MATE;
     private Map MATE;
@@ -189,7 +190,8 @@ public class HKImplClient_ML implements HKClient_ML {
     public void syncContact() {
     public void syncContact() {
 
 
         // prd: 非全量同步: 马来部门
         // prd: 非全量同步: 马来部门
-        List<Long> deptList = UtilList.asList(698673653L); // 马来
+//        List<Long> deptList = UtilList.asList(989604056L, 988784619L); // 马来/劳工 product
+        List<Long> deptList = UtilList.asList(989604056L); // 马来/劳工 product
         // prd: 10.21 马来没有三级部门, 只处理二级部门,一级就是大公司
         // prd: 10.21 马来没有三级部门, 只处理二级部门,一级就是大公司
         ddClient_contacts.getDepartmentId_all(ddClient.getAccessToken(), false, deptList.get(0)).forEach(deptList::add);
         ddClient_contacts.getDepartmentId_all(ddClient.getAccessToken(), false, deptList.get(0)).forEach(deptList::add);
 
 
@@ -200,9 +202,6 @@ public class HKImplClient_ML implements HKClient_ML {
             }
             }
             String dpetCascade = ddService.getUserDepartmentHierarchyJoin(ddClient.getAccessToken(), userIds.get(0), "-");
             String dpetCascade = ddService.getUserDepartmentHierarchyJoin(ddClient.getAccessToken(), userIds.get(0), "-");
             String deptName = UtilMap.getString(ddClient_contacts.getDepartmentInfo(ddClient.getAccessToken(), deptId), "name");
             String deptName = UtilMap.getString(ddClient_contacts.getDepartmentInfo(ddClient.getAccessToken(), deptId), "name");
-
-//            deptName = "Top Management";
-
             for (String userId : userIds) {
             for (String userId : userIds) {
                 Map userInfo = ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), userId);
                 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"));
                 Map data = UtilMap.map("employeeNumber, employeeName, dtPersonId, lastName", userInfo.get("job_number"), userInfo.get("name"), userInfo.get("userid"), userInfo.get("title"));
@@ -264,11 +263,17 @@ public class HKImplClient_ML implements HKClient_ML {
      * 付款数据同步
      * 付款数据同步
      */
      */
     @Override
     @Override
-    public void syncMonitor(String code) {
+    public void syncMonitor(String code, Map data) {
 
 
         McException.assertAccessException(!Arrays.asList("FKSQ", "YFKSQ").contains(code), "单据编码未匹配");
         McException.assertAccessException(!Arrays.asList("FKSQ", "YFKSQ").contains(code), "单据编码未匹配");
         boolean isYF = "YFKSQ".equals(code);
         boolean isYF = "YFKSQ".equals(code);
 
 
+        // prd 25.01.06 预付款订单数据支持筛选: 采购订单号、供应商号、供应商名称的筛选条件,如果为空就查询所有的数据,有值就查询出对应值的数据
+        Map body = UtilMap.empty();
+        if (isYF) {
+            UtilMap.putAll(body, data);
+        }
+
         String rsp = UtilHttp.doPost(_getEnvApi(isYF ? "/PrePayment" : "/Payment"), UtilMap.map("Authorization", AUTH), null, UtilMap.empty());
         String rsp = UtilHttp.doPost(_getEnvApi(isYF ? "/PrePayment" : "/Payment"), UtilMap.map("Authorization", AUTH), null, UtilMap.empty());
         Map result = (Map) JSON.parse(rsp);
         Map result = (Map) JSON.parse(rsp);
         List<Map> dataList = (List<Map>) UtilMap.getList(result, isYF ? "orders" : "rows");
         List<Map> dataList = (List<Map>) UtilMap.getList(result, isYF ? "orders" : "rows");
@@ -282,7 +287,7 @@ public class HKImplClient_ML implements HKClient_ML {
                 search = YDConf.searchCondition_TextFiled("textField_lnmzbyzu", UtilMap.getString(row, "invoiceNumber"), "eq");
                 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)));
+            List<Map> formList = ydService.queryFormData(isYF ? "FORM-98BF1EE731CD48078FEA0701C5F08E73PKQU" : "FORM-7DA9C030A3E84689AADA23BA6BAC435DVWDM", JSON.toJSONString(Arrays.asList(search)));
             if (formList.size() == 0) {
             if (formList.size() == 0) {
                 // 宜搭付款控制字段
                 // 宜搭付款控制字段
                 row.put("status", "否");       // 反馈中
                 row.put("status", "否");       // 反馈中

+ 57 - 0
mjava-hake/src/main/java/com/malk/hake/service/impl/OkHttpUtil.java

@@ -0,0 +1,57 @@
+package com.malk.hake.service.impl;
+
+import javax.net.ssl.*;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+
+/**
+ * @author Vania
+ */
+public class OkHttpUtil {
+    /**
+     * X509TrustManager instance which ignored SSL certification
+     */
+    public static final X509TrustManager IGNORE_SSL_TRUST_MANAGER_X509 = new X509TrustManager() {
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String authType) {
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String authType) {
+        }
+
+        @Override
+        public X509Certificate[] getAcceptedIssuers() {
+            return new X509Certificate[]{};
+        }
+    };
+
+    /**
+     * Get initialized SSLContext instance which ignored SSL certification
+     *
+     * @return
+     * @throws NoSuchAlgorithmException
+     * @throws KeyManagementException
+     */
+    public static SSLContext getIgnoreInitedSslContext() throws NoSuchAlgorithmException, KeyManagementException {
+        SSLContext sslContext = SSLContext.getInstance("SSL");
+        sslContext.init(null, new TrustManager[]{IGNORE_SSL_TRUST_MANAGER_X509}, new SecureRandom());
+        return sslContext;
+    }
+
+    /**
+     * Get HostnameVerifier which ignored SSL certification
+     *
+     * @return
+     */
+    public static HostnameVerifier getIgnoreSslHostnameVerifier() {
+        return new HostnameVerifier() {
+            @Override
+            public boolean verify(String arg0, SSLSession arg1) {
+                return true;
+            }
+        };
+    }
+}

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

@@ -44,7 +44,7 @@ dingtalk:
   token:
   token:
   operator: ""   # OA管理员账号
   operator: ""   # OA管理员账号
 
 
-# aliwork-上海
+## aliwork-上海
 #aliwork:
 #aliwork:
 #  appType: "APP_QWUVLI1R6XYUXWAOPF6O"
 #  appType: "APP_QWUVLI1R6XYUXWAOPF6O"
 #  systemToken: "SE766NA1XW0F9PG19UX926VYF9RS31DVAYMNLJ27"
 #  systemToken: "SE766NA1XW0F9PG19UX926VYF9RS31DVAYMNLJ27"

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

@@ -1,38 +0,0 @@
-# 环境配置
-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"

+ 2 - 2
mjava-hake/src/main/resources/application-prod.yml

@@ -28,8 +28,8 @@ dingtalk:
   appKey: ding3zk0vhfifznseopg
   appKey: ding3zk0vhfifznseopg
   appSecret: Eso_p9HrVrJClEqcwbYuuOeDbS5Lb0e8Qq_HWtJm4_GYR38E-O5UEvakWpxXAvqq
   appSecret: Eso_p9HrVrJClEqcwbYuuOeDbS5Lb0e8Qq_HWtJm4_GYR38E-O5UEvakWpxXAvqq
   corpId: dinge61fe69900ea236b35c2f4657eb6378f
   corpId: dinge61fe69900ea236b35c2f4657eb6378f
-  aesKey:
-  token:
+  aesKey: ZghoN7QoGTi21YSz8chCYor2C4gPHlI9awyqdGFw3CS
+  token: fONmatJ3EsE2TECiuHoJg0k2M77mGFuLg4rgYqO7VNVCvx
   operator: ""   # OA管理员账号
   operator: ""   # OA管理员账号
 
 
 # aliwork
 # aliwork

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

@@ -1,12 +1,12 @@
 # 环境配置
 # 环境配置
 server:
 server:
-  port: 9019
+  port: 9011
   servlet:
   servlet:
     context-path: /api/hake
     context-path: /api/hake
 
 
 # condition
 # condition
 spel:
 spel:
-  scheduling: false        # 定时任务是否执行
+  scheduling: true         # 定时任务是否执行
   multiSource: false       # 是否多数据源配置
   multiSource: false       # 是否多数据源配置
 
 
 spring:
 spring:
@@ -34,5 +34,5 @@ dingtalk:
 
 
 # aliwork
 # aliwork
 aliwork:
 aliwork:
-  appType: "APP_QWUVLI1R6XYUXWAOPF6O"
-  systemToken: "SE766NA1XW0F9PG19UX926VYF9RS31DVAYMNLJ27"
+  appType: "APP_QJOL2IQW1DTH37FXPGSY"
+  systemToken: "E3A66E810VPOPE2LCXHUJ6O6CWA12VWLSO81MOG"

+ 8 - 11
mjava-hangshi/src/main/java/com/malk/hangshi/service/impl/HSImplService.java

@@ -94,7 +94,7 @@ public class HSImplService implements HSService {
     private final static String OUTINURL = "https://eacconsole.ceair.com/api/WBPM1-eaccart/statistical/asis/list";
     private final static String OUTINURL = "https://eacconsole.ceair.com/api/WBPM1-eaccart/statistical/asis/list";
     //餐车数
     //餐车数
     private final static String CARURL = "https://eacconsole.ceair.com/api/WBPM1-eaccart/cartAbnormal/abnormal";
     private final static String CARURL = "https://eacconsole.ceair.com/api/WBPM1-eaccart/cartAbnormal/abnormal";
-    
+
     @Override
     @Override
     public void syncHangShiInfo() {
     public void syncHangShiInfo() {
 
 
@@ -105,7 +105,6 @@ public class HSImplService implements HSService {
                 .build();
                 .build();
         List<Map> dataList = (List<Map>) ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_search_form).getData();
         List<Map> dataList = (List<Map>) ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_search_form).getData();
 
 
-
         //遍历航食数据
         //遍历航食数据
         dataList.forEach(dataItem -> {
         dataList.forEach(dataItem -> {
             Map map = (Map) dataItem.get("formData");
             Map map = (Map) dataItem.get("formData");
@@ -127,13 +126,13 @@ public class HSImplService implements HSService {
             DecimalFormat df = new DecimalFormat("#.##");
             DecimalFormat df = new DecimalFormat("#.##");
             mapList.forEach(mapItem -> {
             mapList.forEach(mapItem -> {
                 Map map1 = (Map) mapItem.get("dynamicInventoryStatistical");
                 Map map1 = (Map) mapItem.get("dynamicInventoryStatistical");
-                hashMap.put("numberField_ldwi9du2",map1.get("longNumber"));  //昨日库存长
-                hashMap.put("numberField_ldwi9du3",map1.get("shortNumber"));  //昨日库存长
-                hashMap.put("numberField_ldwi9du4",map1.get("total"));  //昨日总库存
+                hashMap.put("numberField_ldwi9du2", map1.get("longNumber"));  //昨日库存长
+                hashMap.put("numberField_ldwi9du3", map1.get("shortNumber"));  //昨日库存长
+                hashMap.put("numberField_ldwi9du4", map1.get("total"));  //昨日总库存
                 Map map2 = (Map) mapItem.get("inputCartStatistical");
                 Map map2 = (Map) mapItem.get("inputCartStatistical");
-                hashMap.put("numberField_ldwi9dua",map2.get("total"));  //昨日进港量
+                hashMap.put("numberField_ldwi9dua", map2.get("total"));  //昨日进港量
                 Map map3 = (Map) mapItem.get("outputCartStatistical");
                 Map map3 = (Map) mapItem.get("outputCartStatistical");
-                hashMap.put("numberField_ldwi9du9",map3.get("total")); //昨日出港量
+                hashMap.put("numberField_ldwi9du9", map3.get("total")); //昨日出港量
 
 
             });
             });
 
 
@@ -149,7 +148,7 @@ public class HSImplService implements HSService {
             });
             });
 
 
             hashMap.put("selectField_ldwi9dtr", hsName);  //航食名称
             hashMap.put("selectField_ldwi9dtr", hsName);  //航食名称
-            hashMap.put("textField_lwhbjcoh",hsName);  //航食名称
+            hashMap.put("textField_lwhbjcoh", hsName);  //航食名称
 
 
             hashMap.put("dateField_ldwi9dtt", UtilDateTime.parse(String.valueOf(currentDate), "yyyy-MM-dd"));
             hashMap.put("dateField_ldwi9dtt", UtilDateTime.parse(String.valueOf(currentDate), "yyyy-MM-dd"));
             hashMap.put("dateField_le2jibxo", UtilDateTime.parse(String.valueOf(previousDay), "yyyy-MM-dd"));
             hashMap.put("dateField_le2jibxo", UtilDateTime.parse(String.valueOf(previousDay), "yyyy-MM-dd"));
@@ -165,14 +164,12 @@ public class HSImplService implements HSService {
             } catch (Exception e) {
             } catch (Exception e) {
                 log.info("异常数据 hashMap:{}", hashMap);
                 log.info("异常数据 hashMap:{}", hashMap);
             }
             }
-
-
         });
         });
     }
     }
 
 
 
 
     /**
     /**
-     *
+     * 获取token
      */
      */
     public String getToken() {
     public String getToken() {
 
 

+ 8 - 0
mjava-laidi/src/main/java/com/malk/laidi/delegate/DDDelegate.java

@@ -8,6 +8,8 @@ import org.springframework.context.annotation.Primary;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
+import java.util.Map;
+
 /**
 /**
  * OA审批事件 [主项目也有实现, 添加 @Primary 优先注入主项目实现]
  * OA审批事件 [主项目也有实现, 添加 @Primary 优先注入主项目实现]
  * -
  * -
@@ -56,4 +58,10 @@ public class DDDelegate implements DDEvent {
             ldClient.syncCC(processInstanceId);
             ldClient.syncCC(processInstanceId);
         }
         }
     }
     }
+
+    // 考勤打卡事件回调
+    @Override
+    public void executeEvent_attendance_check(Map<String, ?> record) {
+        log.info("executeEvent_attendance_check");
+    }
 }
 }

+ 7 - 0
mjava/src/main/java/com/malk/controller/DDCallbackController.java

@@ -61,6 +61,13 @@ public class DDCallbackController {
             ddClient_event.callBackEvent_Workflow(eventJson);
             ddClient_event.callBackEvent_Workflow(eventJson);
             return success;
             return success;
         }
         }
+
+        if (Arrays.asList(DDConf.ATTENDANCE_CHECK_RECORD).contains(eventType)) {
+            log.info("[DD]考勤回调, {}", eventJson);
+            ddClient_event.callBackEvent_Attendance(eventJson);
+            return success;
+        }
+
         log.info("----- [DD]已注册, 未处理的其它回调 -----, {}", eventJson);
         log.info("----- [DD]已注册, 未处理的其它回调 -----, {}", eventJson);
         return success;
         return success;
     }
     }

+ 6 - 0
mjava/src/main/java/com/malk/delegate/DDEvent.java

@@ -2,6 +2,8 @@ package com.malk.delegate;
 
 
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.annotation.Async;
 
 
+import java.util.Map;
+
 /**
 /**
  * 钉钉事件回调 3_2
  * 钉钉事件回调 3_2
  * -
  * -
@@ -33,4 +35,8 @@ public interface DDEvent {
 
 
     @Async
     @Async
     void executeEvent_Instance_Start(String processInstanceId, String processCode);
     void executeEvent_Instance_Start(String processInstanceId, String processCode);
+
+    // 考勤打卡事件回调
+    @Async
+    void executeEvent_attendance_check(Map<String, ?> record);
 }
 }

+ 8 - 0
mjava/src/main/java/com/malk/delegate/impl/DDImplEvent.java

@@ -5,6 +5,8 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
+import java.util.Map;
+
 /**
 /**
  * OA审批事件 [主项目若无实现, 项目启动异常; 若子项目有订阅需添加 @Primary 以实现优先注入]
  * OA审批事件 [主项目若无实现, 项目启动异常; 若子项目有订阅需添加 @Primary 以实现优先注入]
  * -
  * -
@@ -46,4 +48,10 @@ public class DDImplEvent implements DDEvent {
     public void executeEvent_Instance_Finish(String processInstanceId, String processCode, boolean isAgree, boolean isTerminate, String staffId) {
     public void executeEvent_Instance_Finish(String processInstanceId, String processCode, boolean isAgree, boolean isTerminate, String staffId) {
         log.info("executeEvent_Instance_Finish: 未被代理");
         log.info("executeEvent_Instance_Finish: 未被代理");
     }
     }
+
+    // 考勤打卡事件回调
+    @Override
+    public void executeEvent_attendance_check(Map<String, ?> record) {
+        log.info("executeEvent_attendance_check: 未被代理");
+    }
 }
 }

+ 5 - 0
mjava/src/main/java/com/malk/server/dingtalk/DDConf.java

@@ -65,6 +65,11 @@ public class DDConf {
      */
      */
     public static final String BPMS_INSTANCE_CHANGE = "bpms_instance_change";
     public static final String BPMS_INSTANCE_CHANGE = "bpms_instance_change";
 
 
+    /**
+     * 员工打卡事件回调
+     */
+    public static final String ATTENDANCE_CHECK_RECORD = "attendance_check_record";
+
     /**
     /**
      * token授权参数: 旧版本
      * token授权参数: 旧版本
      */
      */

+ 1 - 2
mjava/src/main/java/com/malk/service/aliwork/YDClient.java

@@ -18,8 +18,7 @@ public interface YDClient {
     DDR_New queryData(YDParam param, YDConf.FORM_QUERY type);
     DDR_New queryData(YDParam param, YDConf.FORM_QUERY type);
 
 
     /**
     /**
-     * w
-     * 获取宜搭附件临时免登地址
+     * 获取宜搭附件临时免登地址 todo 支持动态token
      */
      */
     String convertTemporaryUrl(String url, int timeout);
     String convertTemporaryUrl(String url, int timeout);
 
 

+ 12 - 0
mjava/src/main/java/com/malk/service/dingtalk/DDClient_Contacts.java

@@ -105,4 +105,16 @@ public interface DDClient_Contacts {
      */
      */
     Map updateUser_dingTalk(String access_token, String userId, List<Long> dept_id_list, Map body_ext);
     Map updateUser_dingTalk(String access_token, String userId, List<Long> dept_id_list, Map body_ext);
 
 
+    /**
+     * 授权企业帐号可加入多组织
+     */
+    void multiOrgPermissions(String access_token, String joinCorpId, String grantDeptIdList);
+
+    /**
+     * 邀请其他组织企业账号加入
+     * ppExt: 处理逻辑 1. 授权企业帐号可加入多组织, 目前接口不受专属安全控制; 2. 加入企业接口与创建一致, 无需传递登录相关信息, 被邀请方必须为企业账号
+     * fixme: 核心参数 [ outer_exclusive_corpid, outer_exclusive_userid ],  组织外邀请无需单独同意, 非激活状态也拉正常加入
+     * ppExt: 邀请api, 无手机号字段, 传递创建接口 exclusive_mobile 字段也生效 [备注: 昵称无效, 只对企业账号归属的组织创建有效, 邀请时会显示原组织定义]
+     */
+    Map inviteExclusiveUser(String access_token, String name, List<Long> dept_id_list, String outer_exclusive_corpid, String outer_exclusive_userid, Map body_ext);
 }
 }

+ 5 - 0
mjava/src/main/java/com/malk/service/dingtalk/DDClient_Event.java

@@ -7,6 +7,11 @@ import java.util.Map;
 
 
 public interface DDClient_Event {
 public interface DDClient_Event {
 
 
+    /**
+     * 钉钉考勤回调事件
+     */
+    void callBackEvent_Attendance(JSONObject eventJson);
+    
     /**
     /**
      * 钉钉审批回调事件
      * 钉钉审批回调事件
      * -
      * -

+ 5 - 0
mjava/src/main/java/com/malk/service/dingtalk/DDService.java

@@ -63,6 +63,11 @@ public interface DDService {
      */
      */
     Map registerJsApi(String url, String nonceStr);
     Map registerJsApi(String url, String nonceStr);
 
 
+    /**
+     * jsApi 注册 - 动态
+     */
+    Map registerJsApi(String accessToken, String url, String nonceStr);
+
     /**
     /**
      * 免登code获取用户信息
      * 免登code获取用户信息
      */
      */

+ 27 - 0
mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_Contacts.java

@@ -260,6 +260,33 @@ public class DDImplClient_Contacts implements DDClient_Contacts {
         Map body = UtilMap.map("userid, dept_id_list", userId, deptIds);
         Map body = UtilMap.map("userid, dept_id_list", userId, deptIds);
         body.putAll(body_ext);
         body.putAll(body_ext);
         return (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/v2/user/update", null, param, body).getResult();
         return (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/v2/user/update", null, param, body).getResult();
+    }
+
+    /**
+     * 邀请其他组织企业账号加入
+     *
+     * @apiNote https://open.dingtalk.com/document/orgapp/invite-other-organization-specific-accounts-to-join
+     */
+    @Override
+    public Map inviteExclusiveUser(String access_token, String name, List<Long> dept_id_list, String outer_exclusive_corpid, String outer_exclusive_userid, Map body_ext) {
+
+        Map param = UtilMap.map("access_token", access_token);
+        String deptIds = String.join(",", dept_id_list.stream().map(dept -> String.valueOf(dept)).collect(Collectors.toList()));
+        Map body = UtilMap.map("name, dept_id_list, outer_exclusive_corpid, outer_exclusive_userid", name, deptIds, outer_exclusive_corpid, outer_exclusive_userid);
+        if (ObjectUtil.isNotNull(body_ext)) {
+            body.putAll(body_ext);
+        }
+        return (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/v2/user/create", null, param, body).getResult();
+    }
 
 
+    /**
+     * 授权企业帐号可加入多组织
+     *
+     * @apiNote https://open.dingtalk.com/document/orgapp/authorize-a-dedicated-account-to-join-multiple-organizations
+     */
+    @Override
+    public void multiOrgPermissions(String access_token, String joinCorpId, String grantDeptIdList) {
+        Map body = UtilMap.map("joinCorpId, grantDeptIdList", joinCorpId, grantDeptIdList);
+        DDR_New.doPost("https://api.dingtalk.com/v1.0/contact/orgAccounts/multiOrgPermissions/auth", DDConf.initTokenHeader(access_token), null, body).getResult();
     }
     }
 }
 }

+ 19 - 0
mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_Event.java

@@ -6,6 +6,7 @@ import com.malk.delegate.DDEvent;
 import com.malk.server.dingtalk.DDConf;
 import com.malk.server.dingtalk.DDConf;
 import com.malk.server.dingtalk.DDR;
 import com.malk.server.dingtalk.DDR;
 import com.malk.service.dingtalk.DDClient_Event;
 import com.malk.service.dingtalk.DDClient_Event;
+import com.malk.utils.UtilMap;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
@@ -28,6 +29,24 @@ public class DDImplClient_Event implements DDClient_Event {
     @Autowired
     @Autowired
     private DDEvent event_delegate;
     private DDEvent event_delegate;
 
 
+    /**
+     * 钉钉考勤回调事件
+     *
+     * @apiNote https://open.dingtalk.com/document/orgapp/attendance-events
+     */
+    @Override
+    public void callBackEvent_Attendance(JSONObject eventJson) {
+
+        String eventType = eventJson.getString("EventType");
+
+        // 考勤打卡事件回调
+        if (DDConf.ATTENDANCE_CHECK_RECORD.equals(eventType)) {
+            List<Map> dataList = UtilMap.getList(eventJson, "DataList");
+            event_delegate.executeEvent_attendance_check(dataList.get(0));
+            return;
+        }
+    }
+
     /**
     /**
      * 钉钉审批回调事件
      * 钉钉审批回调事件
      *
      *

+ 11 - 0
mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplService.java

@@ -221,6 +221,17 @@ public class DDImplService implements DDService {
         return UtilMap.map("nonceStr, agentId, timeStamp, corpId, signature", nonceStr, ddConf.getAgentId(), timeStamp, ddConf.getCorpId(), signature);
         return UtilMap.map("nonceStr, agentId, timeStamp, corpId, signature", nonceStr, ddConf.getAgentId(), timeStamp, ddConf.getCorpId(), signature);
     }
     }
 
 
+    /**
+     * jsApi 注册 - 动态
+     */
+    @Override
+    public Map registerJsApi(String accessToken, String url, String nonceStr) {
+        String jsTicket = ddClient.getJsApiTicket(accessToken);
+        long timeStamp = new Date().getTime();
+        String signature = DDConfigSign.sign(jsTicket, nonceStr, timeStamp, url);
+        return UtilMap.map("nonceStr, agentId, timeStamp, corpId, signature", nonceStr, ddConf.getAgentId(), timeStamp, ddConf.getCorpId(), signature);
+    }
+
     /**
     /**
      * 免登code获取用户信息
      * 免登code获取用户信息
      */
      */