Ver código fonte

Merge branch 'master' of http://111.75.220.136:10030/turborao/jyyy

tanya 2 semanas atrás
pai
commit
d2f852ccc2
28 arquivos alterados com 1525 adições e 152 exclusões
  1. 2 2
      code/base/nckd-jimin-base-helper/src/main/java/nckd/base/helper/CommonHelperUtils.java
  2. 6 11
      code/base/nckd-jimin-base-helper/src/main/java/nckd/base/helper/CusFileServiceExt.java
  3. 126 25
      code/base/nckd-jimin-base-helper/src/main/java/nckd/base/helper/FileSECUtils.java
  4. 7 2
      code/jyyy/nckd-jimin-jyyy-bd/src/main/java/nckd/jimin/jyyy/bd/plugin/msg/ecology/FanweiCommonUtil.java
  5. 20 0
      code/jyyy/nckd-jimin-jyyy-bd/src/main/java/nckd/jimin/jyyy/bd/plugin/msg/ecology/FanweiServiceHelper.java
  6. 8 0
      code/jyyy/nckd-jimin-jyyy-bd/src/main/java/nckd/jimin/jyyy/bd/task/SyncSapUtils.java
  7. 84 51
      code/jyyy/nckd-jimin-jyyy-bd/src/main/java/nckd/jimin/jyyy/bd/task/impl/SynSapServiceImpl.java
  8. 51 8
      code/jyyy/nckd-jimin-jyyy-fi/src/main/java/fi/cas/opplugin/PayBillToolUtil.java
  9. 17 2
      code/jyyy/nckd-jimin-jyyy-fi/src/main/java/fi/cas/task/AgentpaybillQueryStatusTast.java
  10. 18 2
      code/jyyy/nckd-jimin-jyyy-fi/src/main/java/fi/cas/task/PayQueryStatusTast.java
  11. 9 3
      code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/plugin/form/DailyApplyBillEditPlugin.java
  12. 53 0
      code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/plugin/form/GetParameterBillListPlugin.java
  13. 29 7
      code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/plugin/form/PayApplyBillSRMEditPlugin.java
  14. 3 2
      code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/plugin/operate/PublicReimBillReturnOpPlugin.java
  15. 44 4
      code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/plugin/operate/SRMHelperUtils.java
  16. 1 1
      code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/task/WriteBackPayResult2SRMTask.java
  17. 10 7
      code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/webapi/ContractbillApiPlugin.java
  18. 2 3
      code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/webapi/SRMSynAttacmentApiPlugin.java
  19. 40 16
      code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/webapi/SRMSynPayApiPlugin.java
  20. 6 1
      code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/webapi/TransDetailApiPlugin.java
  21. 22 0
      code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/tsrsc/plugin/form/RedirectMokaListPlugin.java
  22. 5 5
      code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/tsrsc/plugin/operate/CasRecrApplyUnAuditValidator.java
  23. 149 0
      code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/tsrsc/plugin/util/MokaApiUtil.java
  24. 221 0
      code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/tsrsc/plugin/workflow/CasRecrApplyMokaWorkFlowPlugin.java
  25. 132 0
      code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/tsrsc/plugin/workflow/YearCrApplyMokaWorkFlowPlugin.java
  26. 155 0
      code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/wtc/wtis/task/SyncPunchCardTask.java
  27. 158 0
      code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/wtc/wtis/util/DingTalkSyncUtil.java
  28. 147 0
      code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/wtc/wtis/util/SyncPunchCardHelper.java

+ 2 - 2
code/base/nckd-jimin-base-helper/src/main/java/nckd/base/helper/CommonHelperUtils.java

@@ -182,8 +182,8 @@ public class CommonHelperUtils {
      * @param value
      * @return
      */
-    public static DynamicObject queryBillDynamicObject(String entityName, String filterName, String value){
-        QFilter qFilter = new QFilter("status", QCP.equals, "C");
+    public static DynamicObject queryBillDynamicObject(String entityName, String filterName, String value, String billStatus){
+        QFilter qFilter = new QFilter("billstatus", QCP.equals, billStatus);
         qFilter.and(new QFilter(filterName, QCP.equals, value));
 
         DynamicObject objectInfo = BusinessDataServiceHelper.loadSingle(entityName, qFilter.toArray());

+ 6 - 11
code/base/nckd-jimin-base-helper/src/main/java/nckd/base/helper/CusFileServiceExt.java

@@ -16,13 +16,13 @@ import java.io.InputStream;
  * @author turborao
  * @date 2025/05/26 14:07
  */
-public class CusFileServiceExt extends FilePathService implements FileServiceExt {
+public class CusFileServiceExt extends FilePathService {
 
     private static final Log logger = LogFactory.getLog(CusFileServiceExt.class);
 
     @Override
     public InputStream encode(String originalPath, InputStream in) {
-        logger.info("--------------SEC 附件加密 "+originalPath+"----------------");
+        logger.info("--------------SEC附件加密 "+originalPath+"----------------");
 
 //        InputStream inForSEC = null;
 //        int isEncryption = FileSECUtils.checkFileIsEncryptionRest(in);
@@ -49,16 +49,11 @@ public class CusFileServiceExt extends FilePathService implements FileServiceExt
      */
     @Override
     public InputStream decode(String originalPath, InputStream in) {
-        logger.info("--------------SEC 附件解密 "+originalPath+"----------------");
+        logger.info("--------------SEC附件解密 "+originalPath+"----------------");
 
-        InputStream inForSEC = null;
-        int isEncryption = FileSECUtils.checkFileIsEncryptionRest(in);
-        if(isEncryption == 1) {
-            long filesize = FileSECUtils.getFileSizeByPath(originalPath);
-            inForSEC = FileSECUtils.decodeFileForSEC(filesize, in);
-            if (inForSEC != null) {
-                return inForSEC;
-            }
+        InputStream inForSEC = FileSECUtils.processFileWithSEC(originalPath, in);
+        if (inForSEC != null) {
+            return inForSEC;
         }
         return in;
     }

+ 126 - 25
code/base/nckd-jimin-base-helper/src/main/java/nckd/base/helper/FileSECUtils.java

@@ -11,19 +11,23 @@ import kd.bos.servicehelper.AttachmentServiceHelper;
 import kd.bos.servicehelper.BusinessDataServiceHelper;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
 import org.apache.http.conn.ssl.SSLContextBuilder;
 import org.apache.http.conn.ssl.TrustStrategy;
 import org.apache.http.entity.AbstractHttpEntity;
 import org.apache.http.entity.InputStreamEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContexts;
+
 import javax.net.ssl.SSLContext;
 
 import java.io.*;
-import java.net.HttpURLConnection;
-import java.net.URL;
+
 import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
 import java.text.DecimalFormat;
 import java.util.List;
 import java.util.Map;
@@ -86,9 +90,12 @@ public class FileSECUtils {
             AbstractHttpEntity entity = new InputStreamEntity(inputStream);
             post.setEntity(entity);
 
-            httpclient = buildSSLCloseableHttpClient();
+            logger.info("--------------SEC附件检查文件 buildSSLCloseableHttpClient() ----------------");
+            httpclient = getHttpClient();
+            logger.info("--------------SEC附件检查文件 httpclient.execute(post) ----------------");
             CloseableHttpResponse response = httpclient.execute(post);
             String result = response.getFirstHeader("data~returnFlag").getValue();
+            logger.info("--------------SEC附件检查文件返回 "+result+" ----------------");
 
             // 获取响应状态码
             if("1".equals(result)){//1表示秘文
@@ -104,10 +111,10 @@ public class FileSECUtils {
 
         } catch (IOException e) {
             e.printStackTrace();
-            logger.info("SEC附件 ERR content1: " + e.getMessage());
+            logger.info("SEC附件检查是否明文 ERR content1: " + e.getMessage());
         } catch (Exception e1){
             e1.printStackTrace();
-            logger.info("SEC附件 ERR content2: " + e1.getMessage());
+            logger.info("SEC附件检查是否明文 ERR content2: " + e1.getMessage());
         }
         return -1;
     }
@@ -136,17 +143,16 @@ public class FileSECUtils {
             AbstractHttpEntity entity = new InputStreamEntity(inputStream);
             post.setEntity(entity);
 
-            httpclient = buildSSLCloseableHttpClient();
+            httpclient = getHttpClient();
+            logger.info("--------------SEC附件加密 httpclient.execute(post) ----------------");
             CloseableHttpResponse response = httpclient.execute(post);
             String result = response.getFirstHeader("data~returnFlag").getValue();
-
+            logger.info("--------------SEC附件加密文件返回 "+result+" ----------------");
             // 获取响应状态码
             if("0".equals(result)){//1表示秘文
                 InputStream ins = response.getEntity().getContent();
-                logger.info("SEC附件加密成功: " + result);
                 return ins;
             }else{
-                logger.debug("SEC附件加密失败: " + result);
                 return null;
             }
 
@@ -182,17 +188,16 @@ public class FileSECUtils {
             AbstractHttpEntity entity = new InputStreamEntity(inputStream);
             post.setEntity(entity);
 
-            httpclient = buildSSLCloseableHttpClient();
+            httpclient = getHttpClient();
+            logger.info("--------------SEC附件解密 httpclient.execute(post) ----------------");
             CloseableHttpResponse response = httpclient.execute(post);
             String result = response.getFirstHeader("data~returnFlag").getValue();
-
+            logger.info("--------------SEC附件解密返回 "+result+" ----------------");
             // 获取响应状态码
             if("0".equals(result)){//1表示秘文
                 InputStream ins = response.getEntity().getContent();
-                logger.info("SEC附件解密成功: " + result);
                 return ins;
             }else{
-                logger.info("SEC附件解密失败: " + result);
                 return null;
             }
 
@@ -206,17 +211,21 @@ public class FileSECUtils {
         return null;
     }
 
-    private static CloseableHttpClient buildSSLCloseableHttpClient() throws Exception {
-        SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
-            @Override
-            public boolean isTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) throws CertificateException {
-                return true;
-            }
-        }).build();
-        // ALLOW_ALL_HOSTNAME_VERIFIER:这个主机名验证器基本上是关闭主机名验证的,实现的是一个空操作,并且不会抛出javax.net.ssl.SSLException异常。
-        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new String[] { "TLSv1" }, null,
-                SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
-        return HttpClients.custom().setSSLSocketFactory(sslsf).build();
+    public static CloseableHttpClient getHttpClient() {
+        CloseableHttpClient httpClient = null;
+        try {
+            TrustStrategy acceptingTrustStrategy = (chain, authType) -> true;
+            SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
+            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
+            logger.info("--------------SEC附件 buildSSLCloseableHttpClient1 ----------------");
+            HttpClientBuilder clientBuilder = HttpClients.custom();
+
+            httpClient = clientBuilder.setSSLSocketFactory(sslsf).build();
+            logger.info("--------------SEC附件 buildSSLCloseableHttpClient2 ----------------");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return httpClient;
     }
 
 
@@ -230,7 +239,7 @@ public class FileSECUtils {
         ByteArrayOutputStream outStream=null;
         long size = 0;
         try {
-            InputStream inputStream = FileServiceFactory.getAttachmentFileService().getInputStream(originalPath);
+            inStream = FileServiceFactory.getAttachmentFileService().getInputStream(originalPath);
 
             outStream = new ByteArrayOutputStream();
             byte[] buffer = new byte[1024];
@@ -268,4 +277,96 @@ public class FileSECUtils {
         return size;
     }
 
+    public static InputStream processFileWithSEC(String path,InputStream inputStream) {
+        File tempFile = null;
+        try {
+            logger.info("--------------SEC附件 processFileWithSEC1 ----------------");
+            String filename = getFileNameWithoutExtension(path);
+            String filetype = getFileType(path);
+            String filepath =  System.getProperty("java.io.tmpdir") + "/" + filename;
+            // 1. 将 InputStream 写入本地临时文件
+            tempFile = File.createTempFile(filepath,"."+filetype);
+            logger.info("--------------SEC附件临时路径"+filepath,"."+filetype+" ----------------");
+            tempFile.deleteOnExit(); // 确保 JVM 退出时删除临时文件
+
+            try (FileOutputStream fos = new FileOutputStream(tempFile)) {
+                byte[] buffer = new byte[1024];
+                int bytesRead;
+                while ((bytesRead = inputStream.read(buffer)) != -1) {
+                    fos.write(buffer, 0, bytesRead);
+                }
+            }
+
+            // 2. 调用是否加密接口方法
+            int isEncrypted = checkFileIsEncryptionRest(new FileInputStream(tempFile));
+            if (isEncrypted == -1) {
+                logger.info("--------------SEC附件检查,文件加密状态检查失败----------------");
+                return inputStream;
+            } else if (isEncrypted == 0) {
+                logger.info("--------------SEC附件检查,是明文无需解密----------------");
+                return inputStream;
+            }
+
+            // 3. 获取文件大小
+            long fileSize = tempFile.length();
+
+            // 4. 调用解密方法
+            InputStream decryptedInputStream = decodeFileForSEC(fileSize, new FileInputStream(tempFile));
+            if (decryptedInputStream == null) {
+                logger.info("--------------SEC附件解密,文件解密失败----------------");
+                return null;
+            }
+
+            // 5. 删除临时文件
+            if (!tempFile.delete()) {
+                logger.info("--------------SEC附件解密,无法删除临时文件----------------");
+            }
+
+            // 返回解密后的输入流
+            return decryptedInputStream;
+
+        } catch (IOException e) {
+            e.printStackTrace();
+            logger.info("--------------SEC附件解密 处理过程中发生错误1: " + e.getMessage());
+            return null;
+        } finally {
+            try {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+                logger.info("--------------SEC附件解密 处理过程中发生错误2: " + e.getMessage());
+            }
+        }
+    }
+
+    public static String getFileType(String fileName) {
+        if (fileName == null || fileName.isEmpty()) {
+            logger.info("--------------SEC附件 文件名为空时返回------" );
+            return null; // 文件名为空时返回 null
+        }
+
+        int lastDotIndex = fileName.lastIndexOf('.');
+        if (lastDotIndex == -1 || lastDotIndex == fileName.length() - 1) {
+            logger.info("--------------SEC附件 文件名为空时返回------" );
+            return null; // 没有扩展名或文件名以点结尾时返回 null
+
+        }
+        return fileName.substring(lastDotIndex + 1); // 返回文件扩展名
+    }
+
+    public static String getFileNameWithoutExtension(String filePath) {
+        // 提取最后一段文件名
+        int lastSlashIndex = filePath.lastIndexOf('/');
+        String fileName = (lastSlashIndex == -1) ? filePath : filePath.substring(lastSlashIndex + 1);
+
+        int dotIndex = fileName.lastIndexOf('.');
+        if (dotIndex == -1) {
+            return fileName;
+        }
+        return fileName.substring(0, dotIndex);
+    }
+
+
 }

+ 7 - 2
code/jyyy/nckd-jimin-jyyy-bd/src/main/java/nckd/jimin/jyyy/bd/plugin/msg/ecology/FanweiCommonUtil.java

@@ -802,9 +802,14 @@ public final class FanweiCommonUtil {
         }
 
         if(StringUtils.isEmpty(title) && taskInfo != null){
-            title = "请处理" + taskInfo.getString("startname")
-                    + "提交的" + taskInfo.getString("entityname") + taskInfo.getString("billno");
+            if(StringUtils.isNotEmpty(taskInfo.getString("subject"))){
+                title = taskInfo.getString("subject");
+            } else {
+                title = "请处理" + taskInfo.getString("startname")
+                        + "提交的" + taskInfo.getString("entityname") + taskInfo.getString("billno");
+            }
         }
+
         String requestname = title;
         String nodename = todoInfo.getCategory();
         String workflowname = "财务系统审批流";

+ 20 - 0
code/jyyy/nckd-jimin-jyyy-bd/src/main/java/nckd/jimin/jyyy/bd/plugin/msg/ecology/FanweiServiceHelper.java

@@ -47,12 +47,19 @@ public class FanweiServiceHelper extends AbstractMessageServiceHandler {
             return;
         }
 
+        logger.info("泛微待办 人员:" + userIds.toString());
+
         Map<String, String> params = new HashMap<>(3);
         params.put("isremark", "0");
         params.put("viewtype", "0");
 
         for(int i = 0; i < userIds.size(); i++){
             Long userId = userIds.get(i);
+
+            if(userId == null){
+                continue;
+            }
+
             params.put("userId", userId.toString());
 
             try {
@@ -90,12 +97,19 @@ public class FanweiServiceHelper extends AbstractMessageServiceHandler {
             return;
         }
 
+        logger.info("处理待办 人员:" + userIds.toString());
+
         Map<String, String> params = new HashMap<>(3);
         params.put("isremark", "2");
         params.put("viewtype", "1");
 
         for(int i = 0; i < userIds.size(); i++){
             Long userId = userIds.get(i);
+
+            if(userId == null){
+                continue;
+            }
+
             params.put("userId", userId.toString());
 
             try {
@@ -124,6 +138,8 @@ public class FanweiServiceHelper extends AbstractMessageServiceHandler {
             return;
         }
 
+        logger.info("处理待办 人员:" + userIds.toString());
+
         Map<String, String> params = new HashMap<>(3);
         params.put("isremark", "-1");
         params.put("viewtype", "1");
@@ -131,6 +147,10 @@ public class FanweiServiceHelper extends AbstractMessageServiceHandler {
         for(int i = 0; i < userIds.size(); i++){
             Long userId = userIds.get(i);
 
+            if(userId == null){
+                continue;
+            }
+
             params.put("userId", userId.toString());
 
             try {

+ 8 - 0
code/jyyy/nckd-jimin-jyyy-bd/src/main/java/nckd/jimin/jyyy/bd/task/SyncSapUtils.java

@@ -135,4 +135,12 @@ public class SyncSapUtils {
         return str.substring(0, firstDotIndex);
     }
 
+    public static String getType(String str) {
+        int firstDotIndex = str.indexOf('-');
+        if (firstDotIndex == -1) {
+            return null; // 没有找到 '.',返回NULL
+        }
+        return str.substring(0, firstDotIndex);
+    }
+
 }

+ 84 - 51
code/jyyy/nckd-jimin-jyyy-bd/src/main/java/nckd/jimin/jyyy/bd/task/impl/SynSapServiceImpl.java

@@ -11,6 +11,7 @@ import kd.bos.logging.LogFactory;
 import kd.bos.orm.query.QCP;
 import kd.bos.orm.query.QFilter;
 import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
 import kd.bos.servicehelper.operation.SaveServiceHelper;
 import kd.bos.util.StringUtils;
 import nckd.jimin.jyyy.bd.common.oauth.FanWeiUtils;
@@ -80,8 +81,10 @@ public class SynSapServiceImpl implements SynSapService {
         DynamicObject parent = BusinessDataServiceHelper.loadSingle("bos_costcenter", new QFilter[]{new QFilter("number", QCP.equals, companyCode)});
 
         QFilter qFilter = new QFilter("number", QCP.in, costNumList);
-        // QFilter qFilter1 = new QFilter("accountorg.number", QCP.equals, companyCode);
-        DynamicObject[] costDyns = BusinessDataServiceHelper.load("bos_costcenter", "id,masterid,parent,number,name,accountorg,orgduty,status,enable", new QFilter[]{qFilter});
+        List<Object> listIDs = QueryServiceHelper.queryPrimaryKeys("bos_costcenter",new QFilter[]{qFilter}, null, Integer.MAX_VALUE);
+        DynamicObject costDyn =  BusinessDataServiceHelper.newDynamicObject("bos_costcenter");
+        DynamicObject[] costDyns = BusinessDataServiceHelper.load(listIDs.toArray(),costDyn.getDynamicObjectType());
+        //DynamicObject[] costDyns = BusinessDataServiceHelper.load("bos_costcenter", "id,masterid,parent,number,name,accountorg,orgduty,status,enable", new QFilter[]{qFilter});
         Map<String, DynamicObject> costMap =
                 Arrays.stream(costDyns)
                         .collect(Collectors.toMap(
@@ -151,7 +154,8 @@ public class SynSapServiceImpl implements SynSapService {
         }
 
         if (costList.size() > 0) {
-            Object[] save = SaveServiceHelper.save(costList.toArray(new DynamicObject[0]));
+            DynamicObject[] saveDynamicObject = costList.toArray(new DynamicObject[costList.size()]);
+            Object[] save = SaveServiceHelper.save(saveDynamicObject);
             int length = save.length;
             logger.info("同步[成本]完成,本次新增数量:{}," + err, length);
             result.put("code", "200");
@@ -200,7 +204,8 @@ public class SynSapServiceImpl implements SynSapService {
 
         if (!"S".equals(etype)) {
             result.put("code", "301");
-            result.put("msg", "同步SAP主数据_项目,E_TYPE:" + etype + ",错误信息:" + rData.getString("E_MESSGE"));
+            result.put("msg", "同步SAP主数据_项目,错误信息:" + rData.getString("E_MESSGE"));
+            logger.info("同步SAP主数据_项目,参数:E_TYPE:" + etype + ",错误信息:" + rData.getString("E_MESSGE"));
             return result;
         }
 
@@ -223,6 +228,8 @@ public class SynSapServiceImpl implements SynSapService {
             JSONObject item = rItemArray.getJSONObject(i);
             String number = item.getString("POSID");
             projectNumList.add(number);
+            //String parent = SyncSapUtils.getProjectNumberForParent(number);
+            //projectNumList.add(parent);
         }
         //取组织对象
         QFilter qf = new QFilter("number", QCP.equals, companyCode);
@@ -230,7 +237,15 @@ public class SynSapServiceImpl implements SynSapService {
 
         QFilter qFilter = new QFilter("number", QCP.in, projectNumList);
         QFilter qFilter1 = new QFilter("org.number", QCP.equals, companyCode);
-        DynamicObject[] dyns = BusinessDataServiceHelper.load("bd_project", "id,number,name,fullname,parent,masterid,proleader,org,useorg,createorg,group,status,ctrlstrategy,level,isleaf,department,prostatus,enable", new QFilter[]{qFilter, qFilter1});
+
+        /**
+         * 当存在对象与新增对象同时Save时,会报错,通过以下方式解决
+         * 先用QueryServiceHelper查询出id,再用BusinessDataServiceHelper。这样就与新增的数据包保持一致了
+         */
+        List<Object> listIDs = QueryServiceHelper.queryPrimaryKeys("bd_project",new QFilter[]{qFilter, qFilter1}, null, Integer.MAX_VALUE);
+        DynamicObject projectDyn =  BusinessDataServiceHelper.newDynamicObject("bd_project");
+        DynamicObject[] dyns = BusinessDataServiceHelper.load(listIDs.toArray(),projectDyn.getDynamicObjectType());
+        //DynamicObject[] dyns = BusinessDataServiceHelper.load("bd_project", "id,number,name,fullname,parent,masterid,proleader,org,useorg,createorg,group,status,ctrlstrategy,level,isleaf,department,prostatus,enable", new QFilter[]{qFilter, qFilter1});
 
         Map<String, DynamicObject> projectMap =
                 Arrays.stream(dyns)
@@ -240,7 +255,8 @@ public class SynSapServiceImpl implements SynSapService {
                                 (existing, replacement) -> existing // 保留前面的值
                         ));
 
-        ArrayList<DynamicObject> projectList = new ArrayList<>();
+        List<DynamicObject> projectList = new ArrayList<>();
+        Map<String, DynamicObject> parentMap = new HashMap<>();
         List<Object[]> useorgList = new ArrayList<>();
         StringBuilder err = new StringBuilder();
 
@@ -253,11 +269,12 @@ public class SynSapServiceImpl implements SynSapService {
                 continue;
             }
 
-            //if(!"CD20210404.2010.22".equals(number)) continue;
+            //if(!"AT20180602.2010.10".equals(number)) continue;    ////用于测试
 
             String proleader = item.getString("USR03");
             String department = item.getString("DEPARTMENT");
-            String type = item.getString("ZBY2");
+            String typeStr = item.getString("ZBY2");
+            String type = SyncSapUtils.getType(typeStr);
             String billstatus = item.getString("STATUS");
             DynamicObject proleaderDyn = null;
             DynamicObject departmentDyn = null;
@@ -289,40 +306,43 @@ public class SynSapServiceImpl implements SynSapService {
                  * */
                 DynamicObject parentDyn = null;
                 String parent = SyncSapUtils.getProjectNumberForParent(number);
-                if (StringUtils.isNotEmpty(parent)) {
-
-                    parentDyn = BusinessDataServiceHelper.newDynamicObject("bd_project");
-                    long Id = DB.genLongId("t_bd_project");
-                    parentDyn.set("id", Id);
-                    parentDyn.set("masterid", Id);
-                    parentDyn.set("number", parent); // 编号
-                    parentDyn.set("org", DEFAULT_ORG_ID);  //一级项目放在顶层组织下
-                    parentDyn.set("createorg", DEFAULT_ORG_ID);  //一级项目放在顶层组织下
-                    if (EmptyUtils.isNotEmpty(typeDyn)) {
-                        parentDyn.set("group", typeDyn);  ///项目分类
-                    }
-                    parentDyn.set("level", 1);
-                    parentDyn.set("isleaf", 0);
-                    parentDyn.set("status", "C");        ///单据状态
-                    parentDyn.set("ctrlstrategy", "5");  //控制策略  自由分配 2 全局共享 5  私有 7
-                    parentDyn.set("enable", "1");        //使用状态
-                    // parentDyn.set("prostatus", "PASS_S"); //项目状态 PASS通过
-                    parentDyn.set("name", parent);
-                    parentDyn.set("fullname", parent);
-                    if (EmptyUtils.isNotEmpty(proleaderDyn)) {
-                        parentDyn.set("proleader", proleaderDyn);
-                    }
-                    if (EmptyUtils.isNotEmpty(departmentDyn)) {
-                        parentDyn.set("department", departmentDyn);
-                    }
-                    if (EmptyUtils.isNotEmpty(projectStatusDyn)) {
-                        parentDyn.set("prostatus", projectStatusDyn);
-                    }
-                    if (EmptyUtils.isNotEmpty(srcSysDyn)) {
-                        parentDyn.set("systemtypebase", srcSysDyn);
-                    }
-                    projectList.add(parentDyn);
 
+                if (StringUtils.isNotEmpty(parent) ) {
+                    parentDyn = parentMap.get(parent);  //会有多个相同的父项目,需要去重
+                    if (parentDyn == null) {
+                        parentDyn = BusinessDataServiceHelper.newDynamicObject("bd_project");
+                        long Id = DB.genLongId("t_bd_project");
+                        parentDyn.set("id", Id);
+                        parentDyn.set("masterid", Id);
+                        parentDyn.set("number", parent); // 编号
+                        parentDyn.set("org", DEFAULT_ORG_ID);  //一级项目放在顶层组织下
+                        parentDyn.set("createorg", DEFAULT_ORG_ID);  //一级项目放在顶层组织下
+                        if (EmptyUtils.isNotEmpty(typeDyn)) {
+                            parentDyn.set("group", typeDyn);  ///项目分类
+                        }
+                        parentDyn.set("level", 1);
+                        parentDyn.set("isleaf", 0);
+                        parentDyn.set("status", "C");        ///单据状态
+                        parentDyn.set("ctrlstrategy", "5");  //控制策略  自由分配 2 全局共享 5  私有 7
+                        parentDyn.set("enable", "1");        //使用状态
+                        // parentDyn.set("prostatus", "PASS_S"); //项目状态 PASS通过
+                        parentDyn.set("name", parent);
+                        parentDyn.set("fullname", parent);
+                        if (EmptyUtils.isNotEmpty(proleaderDyn)) {
+                            parentDyn.set("proleader", proleaderDyn);
+                        }
+                        if (EmptyUtils.isNotEmpty(departmentDyn)) {
+                            parentDyn.set("department", departmentDyn);
+                        }
+                        if (EmptyUtils.isNotEmpty(projectStatusDyn)) {
+                            parentDyn.set("prostatus", projectStatusDyn);
+                        }
+                        if (EmptyUtils.isNotEmpty(srcSysDyn)) {
+                            parentDyn.set("systemtypebase", srcSysDyn);
+                        }
+                        parentMap.put(parent, parentDyn);
+                        projectList.add(parentDyn);
+                    }
                 }
                 /***
                  * 处理二级项目,如果项目编码中包含点,则需要处理上级项目
@@ -332,7 +352,7 @@ public class SynSapServiceImpl implements SynSapService {
                 dyn.set("id", Id);
                 dyn.set("masterid", Id);
                 if (EmptyUtils.isNotEmpty(parentDyn)) {
-                    dyn.set("parent", parentDyn);
+                    dyn.set("parent", parentDyn.getPkValue());
                 }
                 dyn.set("number", number); // 编号
                 dyn.set("org", companyDyn.getPkValue());
@@ -341,7 +361,7 @@ public class SynSapServiceImpl implements SynSapService {
                 dyn.set("createorg", companyDyn.getPkValue());
                 dyn.set("useorg", companyDyn.getPkValue());
                 if (EmptyUtils.isNotEmpty(typeDyn)) {
-                    dyn.set("group", typeDyn);  ///项目分类
+                    dyn.set("group", typeDyn.getPkValue());  ///项目分类
                 }
                 dyn.set("status", "C");        ///单据状态
                 dyn.set("ctrlstrategy", "7");  //控制策略  自由分配 2 全局共享 5  私有 7
@@ -360,22 +380,27 @@ public class SynSapServiceImpl implements SynSapService {
             dyn.set("name", item.getString("POST1"));
             dyn.set("fullname", item.getString("POST1"));
             if (EmptyUtils.isNotEmpty(proleaderDyn)) {
-                dyn.set("proleader", proleaderDyn);
+                dyn.set("proleader", proleaderDyn.getPkValue());
             }
             if (EmptyUtils.isNotEmpty(departmentDyn)) {
-                dyn.set("department", departmentDyn);
+                dyn.set("department", departmentDyn.getPkValue());
             }
             if (EmptyUtils.isNotEmpty(projectStatusDyn)) {
-                dyn.set("prostatus", projectStatusDyn);
+                dyn.set("prostatus", projectStatusDyn.getPkValue());
             }
             if (EmptyUtils.isNotEmpty(srcSysDyn)) {
-                dyn.set("systemtypebase", srcSysDyn);
+                dyn.set("systemtypebase", srcSysDyn.getPkValue());
             }
             projectList.add(dyn);
         }
 
+        /**
+         * 保存项目
+         */
         if (projectList.size() > 0) {
-            Object[] save = SaveServiceHelper.save(projectList.toArray(new DynamicObject[0]));
+            // 将saveList转为数组
+            DynamicObject[] saveDynamicObject = projectList.toArray(new DynamicObject[projectList.size()]);
+            Object[] save = SaveServiceHelper.save(saveDynamicObject);
             int length = save.length;
             logger.info("同步[项目]完成,本次新增数量:{}," + err, length);
             result.put("code", "200");
@@ -497,7 +522,12 @@ public class SynSapServiceImpl implements SynSapService {
             speakerIdList.add(sfzh);
         }
         QFilter qFilter = new QFilter("number", QCP.in, speakerIdList);
-        DynamicObject[] speakerDyns = BusinessDataServiceHelper.load("nckd_speaker", "id,number,name,status,creator,enable,nckd_pdjb,nckd_zlly,nckd_shdw,nckd_rzyy", new QFilter[]{qFilter});
+        List<Object> listIDs = QueryServiceHelper.queryPrimaryKeys("nckd_speaker",new QFilter[]{qFilter}, null, Integer.MAX_VALUE);
+        DynamicObject speakerDyn =  BusinessDataServiceHelper.newDynamicObject("nckd_speaker");
+        DynamicObject[] speakerDyns = BusinessDataServiceHelper.load(listIDs.toArray(),speakerDyn.getDynamicObjectType());
+
+        //QFilter qFilter = new QFilter("number", QCP.in, speakerIdList);
+        //DynamicObject[] speakerDyns = BusinessDataServiceHelper.load("nckd_speaker", "id,number,name,status,creator,enable,nckd_pdjb,nckd_zlly,nckd_shdw,nckd_rzyy", new QFilter[]{qFilter});
         Map<String, DynamicObject> speakerMap =
                 Arrays.stream(speakerDyns)
                         .collect(Collectors.toMap(
@@ -564,7 +594,9 @@ public class SynSapServiceImpl implements SynSapService {
                         case "4":
                             dyn.set("nckd_pdjb","普通级");
                             break;
-                        default:break;
+                        default:
+                            dyn.set("nckd_pdjb",zjpdjb);
+                            break;
                     }
                 }
                 //数据状态
@@ -577,7 +609,8 @@ public class SynSapServiceImpl implements SynSapService {
             }
         }
         if(speakerDynList.size()>0) {
-            Object[] save = SaveServiceHelper.save(speakerDynList.toArray(new DynamicObject[0]));
+            DynamicObject[] saveDynamicObject = speakerDynList.toArray(new DynamicObject[speakerDynList.size()]);
+            Object[] save = SaveServiceHelper.save(saveDynamicObject);
             int length = save.length;
             logger.info("同步[讲者]完成,本次新增数量:{},"+err, length);
             result.put("code", "200");

+ 51 - 8
code/jyyy/nckd-jimin-jyyy-fi/src/main/java/fi/cas/opplugin/PayBillToolUtil.java

@@ -45,7 +45,8 @@ public class PayBillToolUtil {
 
             //EAS付款单据号
             String bbh = info.get("nckd_bbh") + "";//版本号
-            data.put("ERP_INS_ID", info.getPkValue() + "-" + bbh);
+            data.put("ERP_INS_ID", info.getPkValue() + "");
+
             //EAS付款单据号
             data.put("ERP_PAYNO", info.get("billno") + "-" + bbh);
 
@@ -162,9 +163,9 @@ public class PayBillToolUtil {
             data.put("OP_ACNT_LIST", OP_ACNT_LIST);
             //结算方式
             if(info.getDynamicObject("settletype") != null) {
-                data.put("SETTLE_TYPE", info.getDynamicObject("settletype").getString("name"));
+                data.put("settletype", info.getDynamicObject("settletype").getString("name"));
             } else {
-                data.put("SETTLE_TYPE", "");
+                data.put("settletype", "");
             }
 
             //无用字段
@@ -261,7 +262,8 @@ public class PayBillToolUtil {
 
 
         String bbh = info.get("nckd_bbh") + "";//版本号
-        data.put("ERP_INS_ID", info.getPkValue() + "-" + bbh);
+        data.put("ERP_INS_ID", info.getPkValue() + "");
+
         //EAS付款单据号
         data.put("ERP_PAYNO", info.get("billno") + "-" + bbh);
 
@@ -325,9 +327,9 @@ public class PayBillToolUtil {
         //结算方式
         //结算方式
         if(info.getDynamicObject("settletype") != null) {
-            data.put("SETTLE_TYPE", info.getDynamicObject("settletype").getString("name"));
+            data.put("settletype", info.getDynamicObject("settletype").getString("name"));
         } else {
-            data.put("SETTLE_TYPE", "");
+            data.put("settletype", "");
         }
 
         //无用字段
@@ -492,7 +494,28 @@ public class PayBillToolUtil {
             //存入日志表
             saveAgentlog(payBillEntity, qjjson, apiResult, "1");
 
-            JSONObject cbsReturnJson = new JSONObject(apiResult);
+            JSONObject esbReturnJson = new JSONObject(apiResult);
+            if(esbReturnJson.get("responseData") == null){
+                errMsg.append("单据号:").append(billNum).append(",推送资金系统失败,");
+                errMsg.append("错误号:").append("XXXXXXX");
+                errMsg.append(",错误原因:").append("未正常返回responseData参数");
+                errMsg.append("\r\n");
+
+                return errMsg.toString();
+            }
+
+            JSONArray esbJSONArray = esbReturnJson.getJSONArray("responseData");
+            if(esbJSONArray == null || esbJSONArray.length() == 0){
+                errMsg.append("单据号:").append(billNum).append(",推送资金系统失败,");
+                errMsg.append("错误号:").append("XXXXXXX");
+                errMsg.append(",错误原因:").append("未正常返回responseData参数");
+                errMsg.append("\r\n");
+
+                return errMsg.toString();
+            }
+
+            JSONObject cbsReturnJson = esbJSONArray.getJSONObject(0);
+
             /************************************************************/
 
             if (cbsReturnJson == null) {
@@ -708,7 +731,27 @@ public class PayBillToolUtil {
             //存入日志表
             savelog(payBillEntity, qjjson, apiResult);
 
-            JSONObject cbsReturnJson = new JSONObject(apiResult);
+            JSONObject esbReturnJson = new JSONObject(apiResult);
+            if(esbReturnJson.get("responseData") == null){
+                errMsg.append("单据号:").append(billNum).append(",推送资金系统失败,");
+                errMsg.append("错误号:").append("XXXXXXX");
+                errMsg.append(",错误原因:").append("未正常返回responseData参数");
+                errMsg.append("\r\n");
+
+                return errMsg.toString();
+            }
+
+            JSONArray esbJSONArray = esbReturnJson.getJSONArray("responseData");
+            if(esbJSONArray == null || esbJSONArray.length() == 0){
+                errMsg.append("单据号:").append(billNum).append(",推送资金系统失败,");
+                errMsg.append("错误号:").append("XXXXXXX");
+                errMsg.append(",错误原因:").append("未正常返回responseData参数");
+                errMsg.append("\r\n");
+
+                return errMsg.toString();
+            }
+
+            JSONObject cbsReturnJson = esbJSONArray.getJSONObject(0);
             /************************************************************/
 
             if (cbsReturnJson == null) {

+ 17 - 2
code/jyyy/nckd-jimin-jyyy-fi/src/main/java/fi/cas/task/AgentpaybillQueryStatusTast.java

@@ -43,7 +43,8 @@ public class AgentpaybillQueryStatusTast extends AbstractTask {
             JSONObject param = new JSONObject();
             PayBillToolUtil.headJson(param);
             JSONObject data = new JSONObject();
-            data.put("ERP_INS_ID",dataEntity.getPkValue()+"-"+dataEntity.get("nckd_bbh"));
+            data.put("ERP_INS_ID",dataEntity.getPkValue()+"");
+
             param.put("data",data);
             param.put("txDateTime",Utils.getData(new Date(), "yyyyMMddHHmmss"));
             param.put("batchNo",TypeUtils.nullToString(dataEntity.get("billno"))+"-"+TypeUtils.nullToString(dataEntity.get("nckd_bbh"))+"-"+Utils.getData(new Date(), "yyyyMMddHHmmss"));
@@ -81,7 +82,21 @@ public class AgentpaybillQueryStatusTast extends AbstractTask {
 
             log.info("单据号:" + billNum + "返回参数" + apiResult);
 
-            JSONObject cbsReturnJson = new JSONObject(apiResult);
+//            JSONObject cbsReturnJson = new JSONObject(apiResult);
+
+            JSONObject esbReturnJson = new JSONObject(apiResult);
+            if(esbReturnJson.get("responseData") == null){
+                log.info("单据号:" + billNum + ", 未正常返回responseData参数");
+                return;
+            }
+
+            JSONArray esbJSONArray = esbReturnJson.getJSONArray("responseData");
+            if(esbJSONArray == null || esbJSONArray.length() == 0){
+                log.info("单据号:" + billNum + ", 未正常返回responseData参数");
+                return;
+            }
+
+            JSONObject cbsReturnJson = esbJSONArray.getJSONObject(0);
             /************************************************************/
 
             log.info("单据号:"+billNum+"返回参数"+cbsReturnJson.toString());

+ 18 - 2
code/jyyy/nckd-jimin-jyyy-fi/src/main/java/fi/cas/task/PayQueryStatusTast.java

@@ -12,6 +12,7 @@ import kd.bos.orm.query.QFilter;
 import kd.bos.schedule.executor.AbstractTask;
 import kd.bos.servicehelper.BusinessDataServiceHelper;
 import kd.bos.servicehelper.operation.SaveServiceHelper;
+import org.json.JSONArray;
 import org.json.JSONObject;
 
 import java.io.IOException;
@@ -38,7 +39,8 @@ public class PayQueryStatusTast extends AbstractTask {
             JSONObject param = new JSONObject();
             PayBillToolUtil.headJson(param);
             JSONObject data = new JSONObject();
-            data.put("ERP_INS_ID",dataEntity.getPkValue()+"-"+dataEntity.get("nckd_bbh"));
+            data.put("ERP_INS_ID",dataEntity.getPkValue()+"");
+
             param.put("data",data);
             param.put("txDateTime",Utils.getData(new Date(), "yyyyMMddHHmmss"));
             param.put("batchNo",TypeUtils.nullToString(dataEntity.get("billno"))+"-"+TypeUtils.nullToString(dataEntity.get("nckd_bbh"))+"-"+Utils.getData(new Date(), "yyyyMMddHHmmss"));
@@ -77,7 +79,21 @@ public class PayQueryStatusTast extends AbstractTask {
 
             log.info("单据号:" + billNum + "返回参数" + apiResult);
 
-            JSONObject cbsReturnJson = new JSONObject(apiResult);
+//            JSONObject cbsReturnJson = new JSONObject(apiResult);
+
+            JSONObject esbReturnJson = new JSONObject(apiResult);
+            if(esbReturnJson.get("responseData") == null){
+                log.info("单据号:" + billNum + ", 未正常返回responseData参数");
+                return;
+            }
+
+            JSONArray esbJSONArray = esbReturnJson.getJSONArray("responseData");
+            if(esbJSONArray == null || esbJSONArray.length() == 0){
+                log.info("单据号:" + billNum + ", 未正常返回responseData参数");
+                return;
+            }
+
+            JSONObject cbsReturnJson = esbJSONArray.getJSONObject(0);
             /************************************************************/
 
 

+ 9 - 3
code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/plugin/form/DailyApplyBillEditPlugin.java

@@ -1,10 +1,12 @@
 package nckd.jimin.jyyy.fi.plugin.form;
 
 import kd.bos.bill.AbstractBillPlugIn;
+import kd.bos.bill.BillShowParameter;
 import kd.bos.dataentity.entity.DynamicObject;
 import kd.bos.dataentity.entity.DynamicObjectCollection;
 import kd.bos.entity.datamodel.events.BizDataEventArgs;
 import kd.bos.entity.property.BasedataProp;
+import kd.bos.ext.fi.plugin.ArApConvert.util.EmptyUtils;
 import kd.bos.form.field.BasedataEdit;
 import kd.bos.form.field.events.BeforeF7SelectEvent;
 import kd.bos.form.field.events.BeforeF7SelectListener;
@@ -13,6 +15,7 @@ import kd.bos.list.ListShowParameter;
 import kd.bos.orm.query.QCP;
 import kd.bos.orm.query.QFilter;
 import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
 
 import java.nio.charset.StandardCharsets;
 import java.util.*;
@@ -68,13 +71,16 @@ public class DailyApplyBillEditPlugin extends AbstractBillPlugIn implements Befo
     @Override
     public void afterCreateNewData(EventObject e) {
         super.afterCreateNewData(e);
-
         Map<String, Object> customParams = this.getView().getFormShowParameter().getCustomParams();
+        BillShowParameter parameter = new BillShowParameter();
+        String formId =customParams.get("formId").toString();
+        if(EmptyUtils.isNotEmpty(formId)){
+            parameter.setFormId(formId);
+            this.getModel().setValue("nckd_pageid", formId);
+        }
         if(customParams.get("viewType") != null){
             String[] typeStrings = customParams.get("viewType").toString().split(",");
-
             QFilter qFilter = new QFilter("number", QCP.in, typeStrings);
-
             DynamicObject[] viewTypeCols = BusinessDataServiceHelper.load("nckd_payviewtype", "id, number, name", qFilter.toArray());
             if(viewTypeCols != null && viewTypeCols.length > 0){
                 //页面类型赋值

+ 53 - 0
code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/plugin/form/GetParameterBillListPlugin.java

@@ -0,0 +1,53 @@
+package nckd.jimin.jyyy.fi.plugin.form;
+
+import kd.bos.bill.BillShowParameter;
+import kd.bos.bill.OperationStatus;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.entity.datamodel.ListSelectedRow;
+import kd.bos.ext.fi.plugin.ArApConvert.util.EmptyUtils;
+import kd.bos.form.ShowType;
+import kd.bos.form.events.BillListHyperLinkClickEvent;
+import kd.bos.form.events.HyperLinkClickArgs;
+import kd.bos.list.plugin.AbstractListPlugin;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+
+import java.util.EventObject;
+
+/**
+ * 动态表单插件AbstractListPlugin
+ */
+public class GetParameterBillListPlugin extends AbstractListPlugin {
+    @Override
+    public void billListHyperLinkClick(HyperLinkClickArgs args) {
+        if ("billno".equals(args.getFieldName())) {
+            //获取当前数据行
+            BillListHyperLinkClickEvent evt = (BillListHyperLinkClickEvent) args.getHyperLinkClickEvent();
+            ListSelectedRow currentRow = evt.getCurrentRow();
+            Long billId = (Long) currentRow.getPrimaryKeyValue();
+            String billStatus = currentRow.getBillStatus();
+            String formId= "er_publicreimbursebill";
+            //跳转到指定PC布局
+            BillShowParameter showParameter = new BillShowParameter();
+            QFilter qf = new QFilter("id", QCP.equals, billId);
+            //跳转到指定PC布局
+            DynamicObject billDyn = BusinessDataServiceHelper.loadSingle("er_publicreimbursebill", new QFilter[]{qf});
+            if(billDyn!=null){
+                if(EmptyUtils.isNotEmpty(billDyn.getString("nckd_pageid"))){
+                    formId = billDyn.getString("nckd_pageid");
+                }
+
+            }
+            showParameter.setFormId(formId);
+            showParameter.setPkId(billId);
+            showParameter.getOpenStyle().setShowType(ShowType.MainNewTabPage);
+            showParameter.setStatus(OperationStatus.VIEW);
+            this.getView().showForm(showParameter);
+        }
+
+
+    }
+
+
+}

+ 29 - 7
code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/plugin/form/PayApplyBillSRMEditPlugin.java

@@ -10,6 +10,7 @@ import kd.bos.form.events.AfterDoOperationEventArgs;
 import kd.bos.orm.query.QCP;
 import kd.bos.orm.query.QFilter;
 import kd.bos.servicehelper.BusinessDataServiceHelper;
+import nckd.jimin.jyyy.fi.plugin.operate.SRMHelperUtils;
 
 import java.util.EventObject;
 import java.util.HashMap;
@@ -28,18 +29,14 @@ public class PayApplyBillSRMEditPlugin extends AbstractBillPlugIn {
         super.afterDoOperation(afterDoOperationEventArgs);
 
         String opKey = afterDoOperationEventArgs.getOperateKey();
+        String srmUrl = (String)this.getModel().getValue("nckd_srmurl");
+
         if(StringUtils.equals("nckd_returnsrm", opKey)){
             OperationResult operationResult = afterDoOperationEventArgs.getOperationResult();
             operationResult.setShowMessage(false);
 
-            String ssoUrl = "";
             String message = operationResult.getMessage();
 
-            if(!StringUtils.isEmpty(message) && message.contains("获取单点地址:")){
-                ssoUrl = message.replace("获取单点地址:", "");
-
-                message = "";
-            }
             List<IOperateInfo> errorInfos = operationResult.getAllErrorOrValidateInfo();
             boolean isSuccess = operationResult.isSuccess();
             if(errorInfos.size()>0){
@@ -53,12 +50,37 @@ public class PayApplyBillSRMEditPlugin extends AbstractBillPlugIn {
 
             if(!StringUtils.isEmpty(message)) {
                 this.getView().showTipNotification(message);
+            } else if(!StringUtils.isEmpty(srmUrl)){
+                openSRMSSOLink();
+            }
+        } else if(StringUtils.equals("nckd_ssosrm", opKey)){
+            if(StringUtils.isEmpty(srmUrl)){
+                this.getView().showTipNotification("不是SRM生成的单据,不能单点到SRM系统");
             } else {
+                openSRMSSOLink();
+            }
+        }
+    }
+
+    /**
+     * 单点登录到srm系统
+     */
+    public void openSRMSSOLink(){
+        String ssourl = (String) this.getModel().getValue("nckd_srmurl");
+        Map<String, String> returnMap = SRMHelperUtils.buildSSOUrl(ssourl);
+
+        if(returnMap != null){
+            if("0".equals(returnMap.get("code"))) {
+                ssourl = returnMap.get("msg");
                 IClientViewProxy proxy = this.getView().getService(IClientViewProxy.class);
                 Map<String, String> mpUrl = new HashMap();
-                mpUrl.put("url", ssoUrl);
+                mpUrl.put("url", ssourl);
                 proxy.addAction("openUrl", mpUrl);
+            } else {
+                this.getView().showTipNotification(returnMap.get("msg"));
             }
+        } else {
+            this.getView().showTipNotification("获取单点url失败");
         }
     }
 

+ 3 - 2
code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/plugin/operate/PublicReimBillReturnOpPlugin.java

@@ -71,9 +71,10 @@ public class PublicReimBillReturnOpPlugin extends AbstractOperationServicePlugIn
                             errMessage.append(",");
                         }
                         errMessage.append("单据(" + billno + ")," + returnMap.get("msg"));
-                    } else {
-                        errMessage.append("获取单点地址:" + returnMap.get("msg"));
                     }
+//                    else {
+//                        errMessage.append("获取单点地址:" + returnMap.get("msg"));
+//                    }
                 }
             }
 

+ 44 - 4
code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/plugin/operate/SRMHelperUtils.java

@@ -18,9 +18,11 @@ import okhttp3.*;
 import javax.crypto.Cipher;
 import java.io.IOException;
 import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
 import java.security.KeyFactory;
 import java.security.interfaces.RSAPrivateKey;
 import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Base64;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
@@ -88,6 +90,7 @@ public class SRMHelperUtils {
             } else {//调接口成功后,获取单点登录链接
                 try {
                     billInfo.set("billstatus", "H");
+
                     SaveServiceHelper.update(new DynamicObject[]{billInfo});
                 } catch (Exception e){
                     returnMap.put("code", "1");
@@ -96,9 +99,11 @@ public class SRMHelperUtils {
                     return returnMap;
                 }
 
-                Map<String,String> ssomap = buildSSOUrl(srmurl);
+                return doReturnMap;
 
-                return ssomap;
+//                Map<String,String> ssomap = buildSSOUrl(srmurl);
+//
+//                return ssomap;
             }
         }
 
@@ -303,6 +308,9 @@ public class SRMHelperUtils {
         headerMap.put("User-Agent", "apifox/1.0.0 (https://www.apifox.cn)");
         headerMap.put("Authorization", "Bearer " + token);
 
+        logger.info("SRMHelperUtils writeApproveStatus:body " + body.toJSONString());
+        logger.info("SRMHelperUtils writeApproveStatus:header " + headerMap.toString());
+
         try {
 //            String response = doHttpClientPost(url,  body.toJSONString(), token);
             String response = KHttpClientUtils.postjson(url, headerMap, body.toJSONString());
@@ -418,8 +426,8 @@ public class SRMHelperUtils {
         headerMap.put("User-Agent", "apifox/1.0.0 (https://www.apifox.cn)");
         headerMap.put("Authorization", "Bearer " + token);
 
-        logger.info("SRMHelperUtils:body " + body.toJSONString());
-        logger.info("SRMHelperUtils:header " + headerMap.toString());
+        logger.info("SRMHelperUtils writeBackPayResulst:body " + body.toJSONString());
+        logger.info("SRMHelperUtils writeBackPayResulst:header " + headerMap.toString());
 
         try {
             String response = KHttpClientUtils.postjson(url, headerMap, body.toJSONString());
@@ -494,6 +502,8 @@ public class SRMHelperUtils {
             returnMap.put("code", "0");
             returnMap.put("msg", url);
 
+            logger.info("SRMHelperUtils:获取srm单点url:" + returnMap.toString());
+
             return returnMap;
         } catch(Exception e){
             returnMap.put("code", "1");
@@ -534,6 +544,36 @@ public class SRMHelperUtils {
         }
     }
 
+    /**
+     * 私钥加密
+     *
+     * @param content 加密内容
+     * @param keyStr  私钥
+     * @return
+     */
+    public static String encryptOld(String content, String keyStr) {
+        try {
+            //获取当前时间戳拼接加密,加密内容使用有效期3分钟
+            long nowTime = System.currentTimeMillis();
+            String contentWithTime = content + "|" + String.valueOf(nowTime);
+            Cipher cipher = Cipher.getInstance("RSA");
+            RSAPrivateKey privateKey = getPrivateKeyOld(keyStr);
+            cipher.init(1, privateKey);
+            return Base64.getUrlEncoder().encodeToString(cipher.doFinal(contentWithTime.getBytes(StandardCharsets.UTF_8)));
+        } catch (Exception var3) {
+            throw new RuntimeException("RSA encrypt failed!", var3);
+        }
+    }
+
+
+    private static RSAPrivateKey getPrivateKeyOld(String privateKey) {
+        try {
+            return (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
+        } catch (Exception var2) {
+            throw new RuntimeException("RSA get Private Key failed!", var2);
+        }
+    }
+
     /**
      * 获取SRM token
      * @return

+ 1 - 1
code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/task/WriteBackPayResult2SRMTask.java

@@ -48,7 +48,7 @@ public class WriteBackPayResult2SRMTask extends AbstractTask implements StopTask
         filter.and(new QFilter("nckd_srmbillno", QCP.is_notnull, null));
         filter.and(new QFilter("nckd_srmbillno", QCP.not_equals2, " "));
         filter.and(new QFilter("nckd_srmbillno", QCP.not_equals2, ""));
-        filter.and(new QFilter("billno", QCP.equals, "BX2010202505-0130"));
+//        filter.and(new QFilter("billno", QCP.equals, "BX2010202505-0130"));
 
         DynamicObjectType type = EntityMetadataCache.getDataEntityType(entityName);
         //先找到批量的pkid

+ 10 - 7
code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/webapi/ContractbillApiPlugin.java

@@ -117,14 +117,12 @@ public class ContractbillApiPlugin implements Serializable {
         String fcontractname = (String)billData.get("contractname"); // 合同名称
         String fdescription = (String)billData.get("description"); // 合同说明
         if (StringUtils.isEmpty(fdescription)) {
-            result.setMessage("合同说明description未传值");
-            result.setStatus(false);
-            return result;
+            fdescription = "无";
         }
 
         String fsigndate = (String)billData.get("signdate"); // 签订日期
         if (StringUtils.isEmpty(fsigndate)) {
-            result.setMessage("签订日期fsigndate未传值");
+            result.setMessage("签订日期signdate未传值");
             result.setStatus(false);
             return result;
         }
@@ -138,6 +136,12 @@ public class ContractbillApiPlugin implements Serializable {
             result.setStatus(false);
             return result;
         }
+        DynamicObject bizhong = BusinessDataServiceHelper.loadSingle("bd_currency","id,name",new QFilter[]{new QFilter("number",QCP.equals,contractcurrency)});
+        if (bizhong == null) {
+            result.setMessage("币种编码contractcurrency:" + contractcurrency + ",星瀚未维护");
+            result.setStatus(false);
+            return result;
+        }
         String projectower = (String)billData.get("projectower"); // 合同干系人
         if (StringUtils.isEmpty(projectower)) {
             result.setMessage("合同干系人projectower未传值");
@@ -154,7 +158,7 @@ public class ContractbillApiPlugin implements Serializable {
 
         String fjiafangcontactperson = (String)billData.get("jiafangcontactperson"); // 甲方联系人
         if (StringUtils.isEmpty(fjiafangcontactperson)) {
-            result.setMessage("甲方联系人fjiafangcontactperson未传值");
+            result.setMessage("甲方联系人jiafangcontactperson未传值");
             result.setStatus(false);
             return result;
         }
@@ -261,7 +265,6 @@ public class ContractbillApiPlugin implements Serializable {
 
         dynamicObject.set("iscurrency",true); // 默认设置多币种
 
-        DynamicObject bizhong = BusinessDataServiceHelper.loadSingle("bd_currency","id,name",new QFilter[]{new QFilter("number",QCP.equals,contractcurrency)});
         dynamicObject.set("contractcurrency",bizhong); // 币种
 
         BigDecimal huilv = new BigDecimal(0); // 汇率
@@ -276,7 +279,7 @@ public class ContractbillApiPlugin implements Serializable {
             DynamicObject[] huilvAry = BusinessDataServiceHelper.load("bd_exrate_tree","id,orgcur,cur,excval,effectdate",
                     new QFilter[]{new QFilter("orgcur",QCP.equals,bizhong.getLong("id"))
                             .and("cur",QCP.equals,CNY.getLong("id"))
-                            .and("enable",QCP.equals,'1')},"effectdate desc");
+                            .and("enable",QCP.equals,'1')},"effectdate desc"); // 日期倒序排
 
 
             if(huilvAry.length > 0) {

+ 2 - 3
code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/webapi/SRMSynAttacmentApiPlugin.java

@@ -317,9 +317,8 @@ public class SRMSynAttacmentApiPlugin implements Serializable {
         JSONObject reslutData = new JSONObject();
 
         //0000-表示成功其他-表示失败
-        reslutData.put("RSPCODE", message);
-        reslutData.put("RSPMSG", code);
-
+        reslutData.put("RSPCODE", code);
+        reslutData.put("RSPMSG", message);
 //        reslutData.put("data", responseData);
 
         return CustomApiResult.success(reslutData);

+ 40 - 16
code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/webapi/SRMSynPayApiPlugin.java

@@ -22,6 +22,8 @@ import kd.bos.servicehelper.BusinessDataServiceHelper;
 import kd.bos.servicehelper.DispatchServiceHelper;
 import kd.bos.servicehelper.operation.SaveServiceHelper;
 import nckd.base.helper.CommonHelperUtils;
+import scala.Dynamic;
+
 import javax.validation.Valid;
 import java.io.Serializable;
 import java.math.BigDecimal;
@@ -318,16 +320,7 @@ public class SRMSynPayApiPlugin implements Serializable {
         bizAcctOutBill.set("isbeforeshare", "0");//费用分摊
         bizAcctOutBill.set("sharerule", "orgrule");//
         bizAcctOutBill.set("sharemethod", "rate");//
-
-//        String[] typeStrings = new String[]{"1008", "1009"};
-//        QFilter qFilter111 = new QFilter("number", QCP.in, typeStrings);
-//        DynamicObject[] viewTypeCols = BusinessDataServiceHelper.load("nckd_payviewtype", "id, number, name", qFilter111.toArray());
-//        DynamicObjectCollection collection = new DynamicObjectCollection();
-//        for(DynamicObject col : viewTypeCols){
-//            collection.add(col);
-//        }
-//
-//        bizAcctOutBill.set("nckd_payviewtypemul", collection);//多选页面类型
+        bizAcctOutBill.set("nckd_pageid", "nckd_er_publicsrmapply");//默认页面布局
 
         BigDecimal totalReimburseAmount = BigDecimal.ZERO;//报销金额合计
 
@@ -529,17 +522,45 @@ public class SRMSynPayApiPlugin implements Serializable {
                 return buildReturnData(code, srmBillNo, returnMessage, null, null, null);
             }
 
-            DynamicObject prepayBillInfo = CommonHelperUtils.queryBillDynamicObject("er_prepaybill", "nckd_srmbillno", sourceBillNumber);
-            if(currency == null){
-                returnMessage = "预付单(" + sourceBillNumber + ")在星瀚系统中未匹配到数据!";
+            BigDecimal exchangeRate = BigDecimal.ONE;
+            if(currency.getLong("id") != 1L){
+                exchangeRate = CommonHelperUtils.getExchangeRate(exchangeTableId, currency.getLong("id"), 1L, bizDate);
+            }
+
+            DynamicObject prepayBillInfo = CommonHelperUtils.queryBillDynamicObject("er_prepaybill", "nckd_srmbillno", sourceBillNumber, "G");
+            if(prepayBillInfo == null){
+                returnMessage = "预付单(" + sourceBillNumber + ")在星瀚系统中未匹配到已付款状态的数据!";
                 return buildReturnData(code, srmBillNo, returnMessage, null, null, null);
             }
 
+            DynamicObjectCollection prepayEntrys = prepayBillInfo.getDynamicObjectCollection("expenseentryentity");
+            DynamicObject prepayEntry = prepayEntrys.get(0);
+            DynamicObjectCollection prepayAccounts = prepayBillInfo.getDynamicObjectCollection("accountentry");
+            DynamicObject prepayAccount = prepayAccounts.get(0);
+
             DynamicObject entry = new DynamicObject(type);
 
-            entry.set("loancurrency", currency);//币别
+            entry.set("loanbillnov1", prepayBillInfo.getString("billno"));//单据编号
+            entry.set("sourceentrycostdept", prepayBillInfo.getDynamicObject("costdept"));//费用承担部门
+            entry.set("sourceentrycostcompany", prepayBillInfo.getDynamicObject("costcompany"));//费用承担公司
+            entry.set("std_srcentrycostcenter", prepayBillInfo.getDynamicObject("std_costcenter"));//成本中心
+            entry.set("sourceexpenseitem", prepayEntry.getDynamicObject("expenseitem"));//费用项目
+            entry.set("srcentrywltype", "bd_supplier");//往来类型
+            entry.set("srcentrywlunit", prepayAccount.getDynamicObject("supplier"));// 往来单位
+            entry.set("writeoffquotetype", "0");//换算方式
+            entry.set("loanperson", prepayBillInfo.getDynamicObject("applier").getString("name"));//借款人
+            entry.set("loanapplydatev1", prepayBillInfo.getDate("bizdate"));//申请日期
+            entry.set("loandescriptionv1", prepayBillInfo.getString("description"));//事由
+            entry.set("loancurrency", currency);//币种
+            entry.set("loanexchangerate", exchangeRate);//汇率
+            entry.set("loanamount", prepayAccount.getBigDecimal("oriaccbalamount"));//借款余额
+            entry.set("currloanamount", prepayAccount.getBigDecimal("accbalamount"));//借款余额(本位币)
             entry.set("accloanamount", checkAmountOri);//冲销金额
-            entry.set("sourcebillid", prepayBillInfo.getPkValue().toString());//源单id
+            entry.set("curraccloanamount", checkAmountOri.multiply(exchangeRate));//冲销金额(本位币)
+            entry.set("sourcebillid", prepayBillInfo.getString("id"));//源单id
+            entry.set("sourceentryid", prepayEntry.getString("id"));//源单分录id
+            entry.set("srcbilltype", "er_prepaybill");//源单类型
+
             entry.set("seq", srmseq);
 
             writeoffmoney.add(entry);
@@ -994,6 +1015,7 @@ public class SRMSynPayApiPlugin implements Serializable {
         dailyLoanBill.set("nckd_srmstatus", "1");//srm状态,1:SRM已推送;2:已退回SRM;3:已反写SRM;4:反写SRM失败
 //        dailyLoanBill.set("repaymentdate", repaymentDate);//预计冲销日期
         dailyLoanBill.set("nckd_duigong", "01");//是否对公业务
+        dailyLoanBill.set("nckd_pageid", "er_prepaybill");//默认页面布局
 
         setDefaultMultViewType(dailyLoanBill);//多选页面类型
 
@@ -1288,6 +1310,8 @@ public class SRMSynPayApiPlugin implements Serializable {
         String billNO = "";
 
         QFilter qFilter = new QFilter("nckd_srmbillno", QCP.equals, srmBillNo);
+        qFilter.and(new QFilter("billstatus", QCP.not_equals2, "H"));
+
         DynamicObject info = BusinessDataServiceHelper.loadSingle(entityName, qFilter.toArray());
 
         if(info != null){
@@ -1302,7 +1326,7 @@ public class SRMSynPayApiPlugin implements Serializable {
      * @return
      */
     private void setDefaultMultViewType(DynamicObject objectInfo){
-        String[] typeStrings = new String[]{"1008", "1009"};
+        String[] typeStrings = new String[]{"1007", "1008"};
 
         QFilter qFilter = new QFilter("number", QCP.in, typeStrings);
         DynamicObject[] viewTypeCols = BusinessDataServiceHelper.load("nckd_payviewtype", "id, number, name", qFilter.toArray());

+ 6 - 1
code/jyyy/nckd-jimin-jyyy-fi/src/main/java/nckd/jimin/jyyy/fi/webapi/TransDetailApiPlugin.java

@@ -51,7 +51,7 @@ public class TransDetailApiPlugin implements Serializable {
      */
     @ApiPostMapping(value = "/transdetail", desc = "操作九恒星交易明细")
     public CustomApiResult<Object> oprationTransDetail(
-            @ApiParam(value = "操作类型", required = true) String operation,
+            @ApiParam(value = "操作类型") String operation,
             @ApiParam(value = "交易明细数据", required = true) String jsondata
     ) {
         log.info("收到保存交易明细请求:\r\n操作类型:{}\r\n交易明细数据:{}",
@@ -65,6 +65,11 @@ public class TransDetailApiPlugin implements Serializable {
             return CustomApiResult.fail("803","操作交易明细失败: 只允许接收1条数据,请重新传输!");
         }
 
+        //如果未传operation参数,则默认为sava保存
+        if(StringUtils.isEmpty(operation)){
+            operation = "save";
+        }
+
         try {
             // 根据操作类型处理不同的逻辑
             switch (operation) {

+ 22 - 0
code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/tsrsc/plugin/form/RedirectMokaListPlugin.java

@@ -0,0 +1,22 @@
+package nckd.jimin.jyyy.hr.tsrsc.plugin.form;
+
+import kd.bos.form.control.events.ItemClickEvent;
+import kd.bos.list.plugin.AbstractListPlugin;
+import kd.sdk.plugin.Plugin;
+import nckd.jimin.jyyy.hr.tsrsc.plugin.util.MokaApiUtil;
+
+/**
+ * 跳转Moka通用插件
+ * Tyx
+ * 2025-05-28
+ */
+public class RedirectMokaListPlugin extends AbstractListPlugin implements Plugin {
+    @Override
+    public void itemClick(ItemClickEvent evt) {
+        String itemKey = evt.getItemKey();
+        if ("nckd_moka".equals(itemKey)) {
+            String mokaRedirectUrl = MokaApiUtil.getParamValue("moka_redirect_url", "https://staging-3.mokahr.com/");
+            this.getView().openUrl(mokaRedirectUrl);
+        }
+    }
+}

+ 5 - 5
code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/tsrsc/plugin/operate/CasRecrApplyUnAuditValidator.java

@@ -48,11 +48,11 @@ public class CasRecrApplyUnAuditValidator extends AbstractOperationServicePlugIn
                                     return String.valueOf(entry.getPkValue());
                                 })
                                 .collect(Collectors.toList());
-                        DynamicObject[] load = BusinessDataServiceHelper.load("tstpm_srscarfmrsm", "id", (new QFilter("nckd_mokahcnum", QCP.in, objectList)).toArray());
-                        if(load.length > 0){
-                            this.addErrorMessage(dataEntity, "已存在候选人信息,不允许反审核!");
-                            break;
-                        }
+//                        DynamicObject[] load = BusinessDataServiceHelper.load("tstpm_srscarfmrsm", "id", (new QFilter("nckd_mokahcnum", QCP.in, objectList)).toArray());
+//                        if(load.length > 0){
+//                            this.addErrorMessage(dataEntity, "已存在候选人信息,不允许反审核!");
+//                            break;
+//                        }
                     }
                 }
             }

+ 149 - 0
code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/tsrsc/plugin/util/MokaApiUtil.java

@@ -0,0 +1,149 @@
+package nckd.jimin.jyyy.hr.tsrsc.plugin.util;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.utils.ObjectUtils;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.bos.util.HttpClientUtils;
+import nckd.jimin.jyyy.hr.wtc.wtis.util.DingTalkSyncUtil;
+import org.apache.commons.net.util.Base64;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Moka工具类
+ * @author :Tyx
+ * @since :2025-05-28
+ */
+public class MokaApiUtil {
+    private static Log logger = LogFactory.getLog(MokaApiUtil.class);
+    /**
+     * 新增招聘需求
+     * @param recruitTypeFlag 招聘模式可选值:1:社招  2:校招
+     * @param bodyData            请求体
+     * @return 返回体
+     */
+    public static JSONObject addMokaCurrentHire(String recruitTypeFlag, JSONObject bodyData) throws IOException {
+        String url = getParamValue("moka_url")
+                + "/api-platform/v1/headcount"
+                + "?currentHireMode=" + recruitTypeFlag;
+        Map<String, String> headers = new HashMap();
+        headers.put("Content-Type", "application/json");
+        headers.put("Accept", "*/*");
+        headers.put("Authorization", "Basic " + Base64.encodeBase64String((getParamValue("moka_apikey") + ":").getBytes()));
+        logger.info(String.format("url[%s],data[%s]", url, bodyData.toJSONString()));
+        System.out.println(String.format("url[%s],data[%s]", url, bodyData.toJSONString()));
+        String responseEntify = HttpClientUtils.postjson(url, headers, bodyData.toJSONString(), 10000, 10000);
+
+        logger.info(responseEntify);
+        JSONObject result = (JSONObject)JSONObject.parse(responseEntify);
+        return result;
+    }
+
+    /**
+     * 调用Moka接口
+     * @param url
+     * @param bodyData
+     * @return
+     * @throws IOException
+     */
+    public static JSONObject doPostByHttpClient(String url, JSONObject bodyData) throws IOException {
+        Map<String, String> headers = new HashMap();
+        headers.put("Content-Type", "application/json");
+        headers.put("Accept", "*/*");
+        headers.put("Authorization", "Basic " + Base64.encodeBase64String((getParamValue("moka_apikey") + ":").getBytes()));
+        logger.info(String.format("url[%s],data[%s]", url, bodyData.toJSONString()));
+        System.out.println(String.format("url[%s],data[%s]", url, bodyData.toJSONString()));
+        String responseEntify = HttpClientUtils.postjson(url, headers, bodyData.toJSONString(), 10000, 10000);
+
+        logger.info(responseEntify);
+        JSONObject result = (JSONObject)JSONObject.parse(responseEntify);
+        return result;
+    }
+
+
+    /**
+     * 新增调用日志
+     *
+     * @param docNumber 调用单据标识
+     * @param docName   调用单据名称
+     * @param system    调用系统
+     * @param url       调用接口url
+     * @param request   请求报文
+     * @param response  返回报文
+     */
+    public static void newApiLog(String docNumber, String docName, String system, String url, String request, String response) {
+        DynamicObject apiLog = BusinessDataServiceHelper.newDynamicObject("nckd_mokaapilog");
+        // 调用单据标识
+        apiLog.set("number", docNumber);
+        // 调用单据名称
+        apiLog.set("name", docName);
+        // 数据状态:已审核
+        apiLog.set("status", "C");
+        // 调用人
+        apiLog.set("creator", RequestContext.get().getCurrUserId());
+        // 使用状态:可用
+        apiLog.set("enable", "1");
+        // 调用接口url
+        apiLog.set("nckd_interfaceurl", url);
+        // 调用接口时间
+        apiLog.set("createtime", new Date());
+        if (request.length() < 200) {
+            apiLog.set("nckd_request", request);
+        } else {
+            apiLog.set("nckd_request", request.substring(0, 200) + "...");
+        }
+        apiLog.set("nckd_request_tag", request);
+
+        if (response.length() < 200) {
+            apiLog.set("nckd_response", response);
+        } else {
+            apiLog.set("nckd_response", response.substring(0, 200) + "...");
+        }
+        apiLog.set("nckd_response_tag", response);
+        SaveServiceHelper.save(new DynamicObject[]{apiLog});
+    }
+
+    /**
+     * 获取参数值
+     * @param key
+     * @return
+     */
+    public static String getParamValue(String key) {
+        QFilter filter = new QFilter("number", QCP.equals, "Moka");
+        filter.and("nckd_entryentity.nckd_key",QCP.equals, key);
+        DynamicObject bill = QueryServiceHelper.queryOne("nckd_commonparams", "nckd_entryentity.nckd_value", new QFilter[]{filter});
+        return bill.getString("nckd_entryentity.nckd_value");
+    }
+
+    /**
+     * 获取参数值,没找到返回默认值
+     * @param key
+     * @param def
+     * @return
+     */
+    public static String getParamValue(String key, String def) {
+        QFilter filter = new QFilter("number", QCP.equals, "Moka");
+        filter.and("nckd_entryentity.nckd_key",QCP.equals, key);
+        DynamicObject bill = QueryServiceHelper.queryOne("nckd_commonparams", "nckd_entryentity.nckd_value", new QFilter[]{filter});
+        if(ObjectUtils.isEmpty(bill)) {
+            return def;
+        }
+        else {
+            return bill.getString("nckd_entryentity.nckd_value");
+        }
+    }
+
+
+}

+ 221 - 0
code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/tsrsc/plugin/workflow/CasRecrApplyMokaWorkFlowPlugin.java

@@ -0,0 +1,221 @@
+package nckd.jimin.jyyy.hr.tsrsc.plugin.workflow;
+
+import com.alibaba.fastjson.JSONObject;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.exception.KDException;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.workflow.api.AgentExecution;
+import kd.bos.workflow.engine.extitf.IWorkflowPlugin;
+import nckd.jimin.jyyy.hr.tsrsc.plugin.util.MokaApiUtil;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 临时招聘申请审批流程-工作流插件<br>
+ * 流程编码:Proc_nckd_casrecrapply_audit_1<br>
+ * 审批通过的临时招聘申请同步给 Moka 的招聘需求界面
+ * @author :Tyx
+ * @since :2025-05-28
+ */
+public class CasRecrApplyMokaWorkFlowPlugin implements IWorkflowPlugin {
+    private static Log log = LogFactory.getLog(CasRecrApplyMokaWorkFlowPlugin.class);
+
+    @Override
+    public void notify(AgentExecution execution) {
+        // 单据的BusinessKey(业务ID)
+        String businessKey = execution.getBusinessKey();
+        // 目标单据
+        DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle(
+                businessKey,
+                execution.getEntityNumber()
+        );
+        // 申请人数为零
+        if (dynamicObject == null || dynamicObject.getInt("nckd_applynum") <= 0) {
+            return;
+        }
+        // 错误列表
+        ArrayList<String> errList = new ArrayList<>();
+
+        // 对外招聘组织 的 对外招聘组织编码(number)
+        Map<String, String> recruitOrgMap = getRecruitOrgMap();
+
+        DynamicObjectCollection entryEntity = dynamicObject.getDynamicObjectCollection("entryentity");
+        for (DynamicObject entry : entryEntity) {
+            // 招聘人数为零,跳过
+            // 原单编号
+            String billNo = ((DynamicObject) entry.getParent()).getString("billno");
+            // 排序号
+            int seq = entry.getInt("seq");
+
+            // 需求人数
+            String name = ((DynamicObject) entry.getParent()).getDynamicObjectType().getName();
+            int needNumber = 0;
+            try {
+                needNumber = "nckd_yearcasreplan".equals(name) ? entry.getInt("nckd_approvednum") : entry.getInt("nckd_recruitnum");
+            } catch (NullPointerException e) {
+                String msg = String.format("单据编号为【%s】的第%s条数据:%s",
+                        billNo, seq, "需求人数或公司核定人数未填写"
+                );
+                errList.add(msg);
+                continue;
+            }
+
+            if (needNumber > 0) {
+                // 生产请求json
+                JSONObject body = getRequestBody(entry, needNumber, recruitOrgMap);
+
+                // 新建 招聘需求
+                // 招聘类型
+                String recruitTypeFlag;
+                switch (entry.getString("nckd_recruittype.number")) {
+                    case "1010_S":
+                        // 校园招聘
+                        recruitTypeFlag = "2";
+                        break;
+                    case "1020_S":
+                        // 社会招聘
+                        recruitTypeFlag = "1";
+                        break;
+                    default:
+                        continue;
+                }
+
+                // 发送新建招聘需求请求
+                JSONObject responseObj = null;
+                try {
+                    responseObj = MokaApiUtil.addMokaCurrentHire(recruitTypeFlag, body);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+
+                // 记录调用日志
+                MokaApiUtil.newApiLog(
+                        "nckd_casrecrapply",
+                        "临时招聘申请",
+                        "lszp",
+                        MokaApiUtil.getParamValue("moka_url")
+                                + "/api-platform/v1/headcount"
+                                + "?currentHireMode=" + recruitTypeFlag,
+                        body.toString(),
+                        responseObj.toString()
+                );
+
+                if (responseObj.getInteger("code") != 0) {
+                    String msg = String.format("第%s条数据:%s",
+                            seq, responseObj.getString("msg")
+                    );
+                    errList.add(msg);
+                }
+            }
+        }
+
+        if (!errList.isEmpty()) {
+            throw new KDException("该临时招聘申请新建Moka招聘需求时,发生以下错误:\r\n" + String.join("\r\n", errList));
+        }
+    }
+
+    /**
+     * 获取新建招聘需求请求体
+     *
+     * @param entry         单据体的行
+     * @param needNumber
+     * @param recruitOrgMap 对外招聘组织编码Map
+     * @return 请求体
+     */
+    public static JSONObject getRequestBody(DynamicObject entry, int needNumber, Map<String, String> recruitOrgMap) {
+        // 招聘部门的编码
+        String orgNumber = entry.getString("nckd_recruitorg.number");
+
+        // 专业类别
+        DynamicObjectCollection majorTypes = entry.getDynamicObjectCollection("nckd_majortype");
+        // 把专业类别里的所有值用;拼接
+        String majorType = majorTypes.stream()
+                .map(major -> ((DynamicObject) major.get("fbasedataid")).getString("name"))
+                .reduce((a, b) -> a + ";" + b)
+                .orElse("");
+
+        String recruitTypeFlag = "";
+        switch (entry.getString("nckd_recruittype.number")) {
+            case "1010_S":
+                // 校园招聘
+                recruitTypeFlag = "校园招聘";
+                break;
+            case "1020_S":
+                // 社会招聘
+                recruitTypeFlag = "社会招聘";
+                break;
+        }
+
+//        JSONObject customData = new JSONObject()
+//                // 自定义字段-专业类别
+//                .fluentPut(MokaApiUtil.getParamValue("moka_majortype"), majorType)
+//                // 自定义字段-招聘类型
+//                .fluentPut(MokaApiUtil.getParamValue("moka_recruitype"), recruitTypeFlag);
+
+        // 分录id
+        String pkId = entry.getPkValue().toString();
+
+        JSONObject body = new JSONObject()
+                // 招聘需求编号,编号全局唯一,且不可修改
+                .fluentPut("number", pkId)
+                // 招聘需求名称
+                .fluentPut("jobName", entry.getString("nckd_recruitpost.name"))
+                // 需求人数
+                .fluentPut("needNumber", needNumber)
+                // 部门code,组织架构同步接口传的department_code
+                //.fluentPut("departmentCode", entry.getString("nckd_recruitorg.number"))
+                // 学历要求
+                .fluentPut("education", entry.getString("nckd_education.name"))
+                // 招聘部门
+                .fluentPut("departmentCode", recruitOrgMap.get(orgNumber))
+                // 最低薪资
+                .fluentPut("minSalary", entry.getInt("nckd_payrangemin"))
+                // 最高薪资
+                .fluentPut("maxSalary", entry.getInt("nckd_payrange"))
+                // 薪资单位 0:k/月
+                .fluentPut("salaryUnit", 0)
+                // 自定义字段
+                //.fluentPut("customData", customData)
+                // 招聘需求开始时间。日期格式为:ISO8601
+                .fluentPut("startDate", LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME));
+        return body;
+    }
+
+
+    /**
+     * 对外招聘组织
+     * @return Map:key 为 组织编码,value 为 对外招聘组织编码
+     */
+    public static Map<String, String> getRecruitOrgMap() {
+        DynamicObject[] recruitOrgs = BusinessDataServiceHelper.load(
+                "tsrbs_foreignadminorg",
+                "id,name,number,entryentity,entryentity.realadminorg",
+                new QFilter[]{null}
+        );
+        HashMap<String, String> recruitOrgMap = new HashMap<>();
+        for (DynamicObject recruitOrg : recruitOrgs) {
+            DynamicObjectCollection entryEntity = recruitOrg.getDynamicObjectCollection("entryentity");
+            for (DynamicObject obj : entryEntity) {
+                recruitOrgMap.put(obj.getString("realadminorg.number"), recruitOrg.getString("number"));
+            }
+        }
+        return recruitOrgMap;
+    }
+}
+
+
+
+
+
+
+
+

+ 132 - 0
code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/tsrsc/plugin/workflow/YearCrApplyMokaWorkFlowPlugin.java

@@ -0,0 +1,132 @@
+package nckd.jimin.jyyy.hr.tsrsc.plugin.workflow;
+
+import com.alibaba.fastjson.JSONObject;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.exception.KDException;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.workflow.api.AgentExecution;
+import kd.bos.workflow.engine.extitf.IWorkflowPlugin;
+import nckd.jimin.jyyy.hr.tsrsc.plugin.util.MokaApiUtil;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Map;
+
+/**
+ * 年度招聘申请审批流程-工作流插件
+ * 流程编码:Proc_nckd_yearapply_audit_1
+ * 审批通过的年度招聘申请同步给 Moka 的招聘需求
+ * @author :Tyx
+ * @since :2025-05-28
+ */
+public class YearCrApplyMokaWorkFlowPlugin implements IWorkflowPlugin {
+    private static Log log = LogFactory.getLog(YearCrApplyMokaWorkFlowPlugin.class);
+
+    @Override
+    public void notify(AgentExecution execution) {
+        // 单据的BusinessKey(业务ID)
+        String businessKey = execution.getBusinessKey();
+        // 目标单据
+        DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle(
+                businessKey,
+                execution.getEntityNumber()
+        );
+        // 申请人数为零
+        if (dynamicObject == null || dynamicObject.getInt("nckd_applynum") <= 0) {
+            return;
+        }
+        // 错误列表
+        ArrayList<String> errList = new ArrayList<>();
+
+        // 对外招聘组织 的 对外招聘组织编码(number)
+        Map<String, String> recruitOrgMap = CasRecrApplyMokaWorkFlowPlugin.getRecruitOrgMap();
+
+        DynamicObjectCollection entryEntity = dynamicObject.getDynamicObjectCollection("entryentity");
+        for (DynamicObject entry : entryEntity) {
+            // 招聘人数为零,跳过
+            // 原单编号
+            String billNo = ((DynamicObject) entry.getParent()).getString("billno");
+            // 排序号
+            int seq = entry.getInt("seq");
+
+            // 需求人数
+            String name = ((DynamicObject) entry.getParent()).getDynamicObjectType().getName();
+            int needNumber = 0;
+            try {
+                needNumber = "nckd_yearcasreplan".equals(name) ? entry.getInt("nckd_approvednum") : entry.getInt("nckd_recruitnum");
+            } catch (NullPointerException e) {
+                String msg = String.format("单据编号为【%s】的第%s条数据:%s",
+                        billNo, seq, "需求人数或公司核定人数未填写"
+                );
+                errList.add(msg);
+                continue;
+            }
+
+            if (needNumber > 0) {
+                // 生产请求json
+                JSONObject body = CasRecrApplyMokaWorkFlowPlugin.getRequestBody(entry, needNumber, recruitOrgMap);
+
+                // 新建 招聘需求
+                // 招聘类型
+                String recruitTypeFlag;
+                switch (entry.getString("nckd_recruittype.number")) {
+                    case "1010_S":
+                        // 校园招聘
+                        recruitTypeFlag = "2";
+                        break;
+                    case "1020_S":
+                        // 社会招聘
+                        recruitTypeFlag = "1";
+                        break;
+                    default:
+                        continue;
+                }
+
+                // 发送新建招聘需求请求
+                JSONObject responseObj = null;
+                try {
+                    responseObj = MokaApiUtil.addMokaCurrentHire(recruitTypeFlag, body);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+                // 记录调用日志
+                MokaApiUtil.newApiLog(
+                        "nckd_yearcasreplan",
+                        "年度招聘计划",
+                        "ndzp",
+                        MokaApiUtil.getParamValue("moka_url")
+                                + "/api-platform/v1/headcount"
+                                + "?currentHireMode=" + recruitTypeFlag,
+                        body.toString(),
+                        responseObj.toString()
+                );
+
+                if (responseObj.getInteger("code") != 0) {
+                    String msg = String.format("第%s条数据:%s",
+                            seq, responseObj.getString("msg")
+                    );
+                    errList.add(msg);
+                }
+            }
+        }
+
+        if (!errList.isEmpty()) {
+            throw new KDException("该年度招聘计划新建Moka招聘需求时,发生以下错误:\r\n" + String.join("\r\n", errList));
+        }
+    }
+}
+
+
+
+
+
+
+
+
+
+
+
+

+ 155 - 0
code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/wtc/wtis/task/SyncPunchCardTask.java

@@ -0,0 +1,155 @@
+package nckd.jimin.jyyy.hr.wtc.wtis.task;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.plugin.support.util.StringUtils;
+import kd.bos.exception.KDException;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.schedule.executor.AbstractTask;
+import kd.hr.hbp.business.servicehelper.HRBaseServiceHelper;
+import kd.sdk.plugin.Plugin;
+import nckd.jimin.jyyy.hr.wtc.wtis.util.DingTalkSyncUtil;
+import nckd.jimin.jyyy.hr.wtc.wtis.util.SyncPunchCardHelper;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Module           :工时假勤云-打卡管理-打卡数据
+ * Description      :同步钉钉打卡记录插件
+ * @author Tyx
+ * @date  2025/5/22
+ * 标识 wtis_punchcarddata
+ */
+
+public class SyncPunchCardTask extends AbstractTask implements Plugin {
+
+    private static Log logger = LogFactory.getLog(SyncPunchCardTask.class);
+    private static String startTime = "";
+    private static String endTime = "";
+    private static String accessToken = "";
+    private static String tokenParamKey = "getTokenUrl";
+    private static String punchParamKey = "getPunchUrl";
+
+    private static HRBaseServiceHelper PUNCH_CARD_HELPER = new HRBaseServiceHelper("wtis_punchcarddata");
+    /**
+     * 获取钉钉打卡数据
+     * 钉钉接口限制:50个人,查询时间跨度不能超过半年,查询时间范围不能超过一周
+     * @param requestContext
+     * @param map
+     * @throws KDException
+     */
+    @Override
+    public void execute(RequestContext requestContext, Map<String, Object> map) throws KDException {
+        logger.info("开始执行同步钉钉打卡数据:{}", System.currentTimeMillis());
+        //初始化时间范围
+        try {
+            initDateRange(map);
+            //获取映射用户信息
+            DynamicObjectCollection mappingCols = DingTalkSyncUtil.getMappingInfo();
+            //转换为Map, key = openId, value = 人员工号
+            Map<String, String> openMap = mappingCols.stream().collect(Collectors.toMap((dyx) -> {
+                return dyx.getString("openid");
+            }, (dyx) -> {
+                return dyx.getString("user.number");
+            }));
+            List openList = new ArrayList<>(openMap.keySet());
+            logger.info("本次需要同步打卡数据人员数量 : {}", openList.size());
+            accessToken = DingTalkSyncUtil.getAccessToken();
+            logger.info("获取access_token : {}", accessToken);
+            if(StringUtils.isEmpty(accessToken)) {
+                logger.info("获取钉钉access_token失败");
+                DingTalkSyncUtil.createLog(DingTalkSyncUtil.v_error, "获取钉钉access_token失败", "", DingTalkSyncUtil.SyncPunch);
+            }
+            else {
+                String punchUrl = DingTalkSyncUtil.getParamValue(punchParamKey);
+                punchUrl = punchUrl + "access_token=" + accessToken;
+                logger.info("调用打卡数据同步url : {}" + punchUrl);
+                batchDoPost(openList, punchUrl, openMap);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+
+    /**
+     * 批量调用Post请求
+     * @param openList
+     * @return
+     */
+    public List batchDoPost(List openList, String url, Map openMap) throws IOException {
+        int size = 50;   //每批调用人数
+        int totalSize = openList.size();    //总人数
+        int totalPage = totalSize % size == 0 ? totalSize / size : totalSize / size + 1;
+        List responseList = new ArrayList();
+        for (int i = 1; i <= totalPage; i++) {
+            int startIndex = (i - 1) * size;
+            int endIndex = Math.min(startIndex + size, totalSize);
+            JSONArray actualArr = new JSONArray();
+            for (int m = startIndex; m < endIndex; m++) {
+                actualArr.add(openList.get(m));
+            }
+            JSONObject param = buildContent(actualArr);
+            //先批量调用完,然后再批量处理打卡数据
+            JSONObject response = DingTalkSyncUtil.doPostByHttpClient(url, param);
+            logger.info("分页调用钉钉接口返回信息,第{}页:{}", String.valueOf(i), response);
+            if(response.getInteger("errcode") == 0 ) {
+                SyncPunchCardHelper.savePunchCardData(response.getJSONArray("recordresult"), openMap);
+            }
+            else {
+                logger.info("分页调用钉钉打卡数据接口第{}页出错 : {}" ,String.valueOf(i), response);
+                DingTalkSyncUtil.createLog(DingTalkSyncUtil.v_error, param.toJSONString(), response.toJSONString(), DingTalkSyncUtil.SyncPunch);
+            }
+        }
+        return responseList;
+    }
+
+    /**
+     * 构造参数
+     * @param actualArr 人员openID列表
+     * @return
+     */
+    public JSONObject buildContent (JSONArray actualArr) {
+        JSONObject ob = new JSONObject();
+        ob.put("checkDateFrom", startTime);
+        ob.put("checkDateTo", endTime);
+        ob.put("isI18n", "false");
+        ob.put("userIds", actualArr);
+        return ob;
+    }
+
+    /**
+     * 根据调度作业上配置的参数初始化时间范围
+     * @param map
+     */
+    public void initDateRange(Map<String, Object> map) throws ParseException {
+        boolean isAuto = Boolean.valueOf(map.get("isAuto").toString());
+        //因为考勤机有离线上传,实际打卡时间可能非当天,这里预留一个补偿机制
+        if(!isAuto) {
+            startTime = map.get("startTime").toString();
+            endTime = map.get("endTime").toString();
+        }
+        //取当天时间00:00:00 - 23:59:59
+        else {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            Calendar calendar = Calendar.getInstance();
+            calendar.set(Calendar.HOUR_OF_DAY, 00);
+            calendar.set(Calendar.MINUTE, 00);
+            calendar.set(Calendar.SECOND, 00);
+            startTime = sdf.format(calendar.getTime());
+            calendar.set(Calendar.HOUR_OF_DAY, 23);
+            calendar.set(Calendar.MINUTE, 59);
+            calendar.set(Calendar.SECOND, 59);
+            endTime = sdf.format(calendar.getTime());
+        }
+    }
+
+}

+ 158 - 0
code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/wtc/wtis/util/DingTalkSyncUtil.java

@@ -0,0 +1,158 @@
+package nckd.jimin.jyyy.hr.wtc.wtis.util;
+
+
+import com.alibaba.fastjson.JSONObject;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.bos.util.HttpClientUtils;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * 2025-05-22 Tyx
+ * 钉钉同步工具类
+ */
+public class DingTalkSyncUtil {
+    private static Log logger = LogFactory.getLog(DingTalkSyncUtil.class);
+
+    private static String mapping_entity = "bas_immapping";
+    private static String tokenParamKey = "getTokenUrl";
+    private static String punchParamKey = "getPunchUrl";
+    private static String appKeyParamKey = "appKey";
+    private static String appSecretParamKey = "appSecret";
+    private static String KEY_ENTITY_LOG = "nckd_syncdingtalklog";
+    private static long l1 = 0L;
+    private static long l2 = 0L;
+    private static String accessToken = "";
+    public static String v_success = "A";
+    public static String v_partsuccess = "B";
+    public static String v_error = "C";
+    public static String SyncPunch = "打卡数据同步";
+
+
+    /**
+     * 获取参数值
+     * @param key
+     * @return
+     */
+    public static String getParamValue(String key) {
+        QFilter filter = new QFilter("number", QCP.equals, "DingTalk");
+        filter.and("nckd_entryentity.nckd_key",QCP.equals, key);
+        DynamicObject bill = QueryServiceHelper.queryOne("nckd_commonparams", "nckd_entryentity.nckd_value", new QFilter[]{filter});
+        return bill.getString("nckd_entryentity.nckd_value");
+    }
+
+    /**
+     * POST请求
+     * @param url
+     * @param bodyData
+     * @return
+     * @throws IOException
+     */
+    public static JSONObject doPostByHttpClient(String url, JSONObject bodyData) throws IOException {
+        Map<String, String> headers = new HashMap();
+        headers.put("Content-Type", "application/json");
+        headers.put("Accept", "*/*");
+        logger.info(String.format("url[%s],data[%s]", url, bodyData.toJSONString()));
+        System.out.println(String.format("url[%s],data[%s]", url, bodyData.toJSONString()));
+        String responseEntify = HttpClientUtils.postjson(url, headers, bodyData.toJSONString(), 10000, 10000);
+
+        logger.info(responseEntify);
+        JSONObject result = (JSONObject)JSONObject.parse(responseEntify);
+        return result;
+    }
+
+    /**
+     * get请求
+     * @param url
+     * @param connectionTimeout
+     * @param readTimeout
+     * @return
+     * @throws Exception
+     */
+    public static String doGet(String url, int connectionTimeout, int readTimeout) throws Exception {
+        return HttpClientUtils.get(url, connectionTimeout, readTimeout);
+    }
+
+
+    /**
+     * 获取钉钉AccessToken
+     * @return
+     * @throws Exception
+     */
+    public static String getAccessToken() throws Exception {
+        String url = DingTalkSyncUtil.getParamValue(tokenParamKey);
+        String appKey = DingTalkSyncUtil.getParamValue(appKeyParamKey);
+        String appSecret = DingTalkSyncUtil.getParamValue(appSecretParamKey);
+        String doGetUrl = url + "appkey=" + appKey + "&appsecret=" + appSecret;
+        l2 = System.currentTimeMillis();
+        long diff = l2 - l1;
+        if(diff % (1000 * 60) > 60) {
+            String responseStr = DingTalkSyncUtil.doGet(doGetUrl, 10000, 10000);
+            JSONObject ob = JSONObject.parseObject(responseStr);
+            if (ob.getInteger("errcode") == 0) {
+                l1 = System.currentTimeMillis();
+                accessToken = ob.getString("access_token");
+            } else {
+                return null;
+            }
+        }
+        return accessToken;
+    }
+
+    /**
+     * 获取钉钉用户集成数据
+     * @return
+     */
+    public static DynamicObjectCollection getMappingInfo() {
+        QFilter filter = new QFilter("imtype.id", QCP.equals, 2L);
+        String selectFields = "user.number,openid";
+        return QueryServiceHelper.query(mapping_entity, selectFields, new QFilter[]{filter});
+    }
+
+
+
+    /**
+     * 创建同步日志
+     * @param status 同步状态 A-成功 B-部分成功 C-失败
+     * @param request   请求报文
+     * @param response  返回报文
+     * @param syncType  同步类型
+     */
+    public static void createLog (String status, String request, String response, String syncType) {
+        DynamicObject dynamicObject = BusinessDataServiceHelper.newDynamicObject(KEY_ENTITY_LOG);
+        String uuid = UUID.randomUUID().toString().replace("-", "");
+        dynamicObject.set("enable", "1");
+        dynamicObject.set("status", "C");
+        dynamicObject.set("number", uuid.substring(0,29));
+        dynamicObject.set("nckd_status", status);
+        dynamicObject.set("nckd_synctype", syncType);
+        if (request.length() < 200) {
+            dynamicObject.set("nckd_request", request);
+        } else {
+            dynamicObject.set("nckd_request", request.substring(0, 200) + "...");
+        }
+        dynamicObject.set("nckd_request_tag", request);
+
+        if (response.length() < 200) {
+            dynamicObject.set("nckd_response", response);
+        } else {
+            dynamicObject.set("nckd_response", response.substring(0, 200) + "...");
+        }
+        dynamicObject.set("nckd_response_tag", response);
+        SaveServiceHelper.save(new DynamicObject[]{dynamicObject});
+        logger.info("-------- 保存日志 --------");
+    }
+
+
+}

+ 147 - 0
code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/wtc/wtis/util/SyncPunchCardHelper.java

@@ -0,0 +1,147 @@
+package nckd.jimin.jyyy.hr.wtc.wtis.util;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.exception.ErrorCode;
+import kd.bos.exception.KDBizException;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.hr.hbp.business.servicehelper.HRBaseServiceHelper;
+import kd.wtc.wtis.business.punchcarddata.PunchCardDataHelper;
+import kd.wtc.wtis.business.punchcarddata.PunchCardDataService;
+import kd.wtc.wtis.webapi.punchcard.PunchCardSyncSupport;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class SyncPunchCardHelper {
+
+    private static HRBaseServiceHelper PUNCH_CARD_HELPER = new HRBaseServiceHelper("wtis_punchcarddata");
+    private static final Log logger = LogFactory.getLog(SyncPunchCardHelper.class);
+
+    /**
+     * 保存打卡数据
+     * @param resultArr
+     * @param openMap
+     */
+    public static void savePunchCardData(JSONArray resultArr, Map openMap) {
+        //批次编码
+        String batchNumber = String.valueOf(System.currentTimeMillis());
+        List<DynamicObject> saveList = resultArr.stream().map((data) -> {
+            return dealPunchCardData((JSONObject) data, openMap, batchNumber);
+        }).collect(Collectors.toList());
+        PUNCH_CARD_HELPER.save((DynamicObject[])saveList.toArray(new DynamicObject[0]));
+
+        try {
+            long l1 = System.currentTimeMillis();
+            PunchCardSyncSupport.execute(() -> {
+                syncCardRecord(batchNumber);
+            });
+            long l2 = System.currentTimeMillis();
+            logger.info("SyncPunchCardHelper_sync_cardData_{}", l2 - l1);
+        } catch (Exception e) {
+            logger.warn("SyncPunchCardHelper_sync_cardData_error ", e);
+            DingTalkSyncUtil.createLog(DingTalkSyncUtil.v_error,resultArr.toJSONString(), e.getMessage(), DingTalkSyncUtil.SyncPunch);
+        }
+
+    }
+
+    /**
+     * 处理打卡数据
+     * @param data 钉钉接口返回的打卡数据明细
+     * @param batchNumber 批次编码
+     * @param openMap 人员映射 key = openId, value = 人员工号
+     */
+    public static DynamicObject dealPunchCardData(JSONObject data, Map openMap, String batchNumber) {
+        DynamicObject dyo = PUNCH_CARD_HELPER.generateEmptyDynamicObject();
+        dyo.set("batchnumber", batchNumber);
+        String card = openMap.get(data.getString("userId")).toString();
+        dyo.set("number", card);
+        dyo.set("card", card);
+        //实际打卡时间
+        Long unix = data.getLong("userCheckTime");
+        dyo.set("punchcarddate", unixToDate(unix));
+        dyo.set("punchcardtime", unixToTimeStamp(unix));
+        //打卡地点
+        dyo.set("place", data.getString("userAddress"));
+        //进出卡
+        String checkType = data.getString("checkType");
+        if("OnDuty".equals(checkType)) {
+            dyo.set("accesstag", "on");
+        }
+        else if("OffDuty".equals(checkType)) {
+            dyo.set("accesstag", "off");
+        }
+        //时区
+        dyo.set("timezone", 320881823238577152L);
+        //打卡来源
+        dyo.set("signsourcename", "钉钉同步");
+        //打卡设备 设备编码
+        dyo.set("equipment", "钉钉同步");
+        dyo.set("equipnumber", "1020_S");
+        // dataId
+        dyo.set("dataid", data.getString("id"));
+
+        //默认值
+        dyo.set("times", 0);
+        dyo.set("status", 0);
+
+        return dyo;
+    }
+
+    /**
+     * 同步打卡记录
+     * @param batchNumber
+     */
+    private static void syncCardRecord(String batchNumber) {
+        try {
+            long l1 = System.currentTimeMillis();
+            DynamicObject[] date = PunchCardDataHelper.getDataByBatchNumber(batchNumber);
+            PunchCardDataService.dealWithData(date);
+            long l2 = System.currentTimeMillis();
+            logger.info("SyncPunchCardHelper_sync_cardData:{}", l2 - l1);
+            DingTalkSyncUtil.createLog(DingTalkSyncUtil.v_success,batchNumber + "批次号同步成功", "", DingTalkSyncUtil.SyncPunch);
+        } catch (Exception e) {
+            logger.warn("SyncPunchCardHelper_sync_cardData_error", e);
+            DingTalkSyncUtil.createLog(DingTalkSyncUtil.v_error,"syncCardRecordError", e.getMessage(), DingTalkSyncUtil.SyncPunch);
+            throw new KDBizException(e, new ErrorCode("", e.getMessage()), new Object[0]);
+        } catch (Throwable e1) {
+            logger.warn("SyncPunchCardHelper_sync_cardData_thr", e1);
+            DingTalkSyncUtil.createLog(DingTalkSyncUtil.v_error,"syncCardRecordThrow", e1.getMessage(), DingTalkSyncUtil.SyncPunch);
+            throw new KDBizException(e1, new ErrorCode("", e1.getMessage()), new Object[0]);
+        }
+    }
+
+
+    /**
+     * 时间戳转yyyy-MM-dd
+     * @param unix
+     * @return
+     */
+    public static Date unixToDate (Long unix) {
+        Date date = new Date(unix);
+        return date;
+    }
+
+
+    /**
+     * 时间戳转HH:mm:ss
+     * @param unix
+     * @return
+     */
+    public static String unixToTimeStamp (Long unix) {
+        Date date = new Date(unix);
+        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
+        return sdf.format(date);
+    }
+}