Bläddra i källkod

考勤打卡同步

yuanzhi_kuang 4 veckor sedan
förälder
incheckning
8ea0d5a075

+ 131 - 0
metadata/com/kingdee/eas/custom/synctask/SyncTranForAtsFacade.facade

@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<facade xmlns="com.kingdee.bos.metadata">
+    <package>com.kingdee.eas.custom.synctask</package>
+    <name>SyncTranForAtsFacade</name>
+    <alias>facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].alias</alias>
+    <description>facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].description</description>
+    <userDefined>true</userDefined>
+    <bosType>8E08F1CD</bosType>
+    <stereoType>false</stereoType>
+    <businessImplName>com.kingdee.eas.custom.synctask.SyncTranForAtsFacade</businessImplName>
+    <businessControllerName>com.kingdee.eas.custom.synctask.SyncTranForAtsFacadeController</businessControllerName>
+    <accessLevel>public</accessLevel>
+    <subClassingMode>normal</subClassingMode>
+    <methods>
+        <method>
+            <name>syncPunchRecord</name>
+            <isListenerMethod>false</isListenerMethod>
+            <alias>facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].methods.method[syncPunchRecord].alias</alias>
+            <description>facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].methods.method[syncPunchRecord].description</description>
+            <innerID>8d8deaef-40ef-45cd-a395-e450c235c9c4</innerID>
+            <accessLevel>public</accessLevel>
+            <subClassingMode>normal</subClassingMode>
+            <returnValueType />
+            <metadataRef />
+            <transactionAttribute>Supports</transactionAttribute>
+            <userDefined>true</userDefined>
+            <userDefinedLogic />
+            <parameters>
+                <parameter>
+                    <name>beginDate</name>
+                    <alias>facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].methods.method[syncPunchRecord].parameters.parameter[beginDate].alias</alias>
+                    <description>facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].methods.method[syncPunchRecord].parameters.parameter[beginDate].description</description>
+                    <direction>in</direction>
+                    <dataType>String</dataType>
+                    <metadataRef />
+                    <userDefined>true</userDefined>
+                </parameter>
+                <parameter>
+                    <name>endDate</name>
+                    <alias>facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].methods.method[syncPunchRecord].parameters.parameter[endDate].alias</alias>
+                    <description>facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].methods.method[syncPunchRecord].parameters.parameter[endDate].description</description>
+                    <direction>in</direction>
+                    <dataType>String</dataType>
+                    <metadataRef />
+                    <userDefined>true</userDefined>
+                </parameter>
+                <parameter>
+                    <name>offset</name>
+                    <alias>facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].methods.method[syncPunchRecord].parameters.parameter[offset].alias</alias>
+                    <description>facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].methods.method[syncPunchRecord].parameters.parameter[offset].description</description>
+                    <direction>in</direction>
+                    <dataType>Integer</dataType>
+                    <metadataRef />
+                    <userDefined>true</userDefined>
+                </parameter>
+            </parameters>
+            <exceptions>
+                <bizException>
+                    <key name="package" value="com.kingdee.eas.common" />
+                    <key name="name" value="EASBizException" />
+                </bizException>
+            </exceptions>
+            <configured>false</configured>
+        </method>
+    </methods>
+    <resource>
+        <rs key="facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].alias">
+            <lang locale="en_US" value="null" />
+            <lang locale="zh_CN" value="同步考勤" />
+            <lang locale="zh_HK" value="同步考勤" />
+            <lang locale="zh_TW" value="同步考勤" />
+        </rs>
+        <rs key="facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].description">
+            <lang locale="en_US" value="null" />
+            <lang locale="zh_CN" value="null" />
+            <lang locale="zh_HK" value="null" />
+            <lang locale="zh_TW" value="null" />
+        </rs>
+        <rs key="facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].extendedProperty.userDefined">
+            <lang locale="en_US" value="true" />
+            <lang locale="zh_CN" value="true" />
+            <lang locale="zh_TW" value="true" />
+        </rs>
+        <rs key="facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].methods.method[syncPunchRecord].alias">
+            <lang locale="en_US" value="null" />
+            <lang locale="zh_CN" value="同步打开记录" />
+            <lang locale="zh_HK" value="同步打開記錄" />
+            <lang locale="zh_TW" value="同步打開記錄" />
+        </rs>
+        <rs key="facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].methods.method[syncPunchRecord].description">
+            <lang locale="en_US" value="null" />
+            <lang locale="zh_CN" value="同步打开记录" />
+            <lang locale="zh_HK" value="同步打開記錄" />
+            <lang locale="zh_TW" value="同步打開記錄" />
+        </rs>
+        <rs key="facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].methods.method[syncPunchRecord].parameters.parameter[beginDate].alias">
+            <lang locale="en_US" value="null" />
+            <lang locale="zh_CN" value="开始日期" />
+            <lang locale="zh_HK" value="開始日期" />
+            <lang locale="zh_TW" value="開始日期" />
+        </rs>
+        <rs key="facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].methods.method[syncPunchRecord].parameters.parameter[beginDate].description">
+            <lang locale="en_US" value="null" />
+            <lang locale="zh_CN" value="" />
+            <lang locale="zh_TW" value="null" />
+        </rs>
+        <rs key="facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].methods.method[syncPunchRecord].parameters.parameter[endDate].alias">
+            <lang locale="en_US" value="null" />
+            <lang locale="zh_CN" value="结束日期" />
+            <lang locale="zh_HK" value="結束日期" />
+            <lang locale="zh_TW" value="結束日期" />
+        </rs>
+        <rs key="facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].methods.method[syncPunchRecord].parameters.parameter[endDate].description">
+            <lang locale="en_US" value="null" />
+            <lang locale="zh_CN" value="" />
+            <lang locale="zh_TW" value="null" />
+        </rs>
+        <rs key="facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].methods.method[syncPunchRecord].parameters.parameter[offset].alias">
+            <lang locale="en_US" value="null" />
+            <lang locale="zh_CN" value="日期偏移量" />
+            <lang locale="zh_HK" value="日期偏移量" />
+            <lang locale="zh_TW" value="日期偏移量" />
+        </rs>
+        <rs key="facade[com.kingdee.eas.custom.synctask.SyncTranForAtsFacade].methods.method[syncPunchRecord].parameters.parameter[offset].description">
+            <lang locale="en_US" value="null" />
+            <lang locale="zh_CN" value="默认是当天; 如果-1,则开始日期从昨天开始; 结束日期还是今天的23点59; " />
+            <lang locale="zh_HK" value="默認是當天; 如果-1,則開始日期從昨天開始; 結束日期還是今天的23點59; " />
+            <lang locale="zh_TW" value="默認是當天; 如果-1,則開始日期從昨天開始; 結束日期還是今天的23點59; " />
+        </rs>
+    </resource>
+</facade>

+ 27 - 0
metadata/com/kingdee/eas/custom/synctask/synctask.package

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package xmlns="com.kingdee.bos.metadata">
+    <package>com.kingdee.eas.custom.synctask</package>
+    <name>synctask</name>
+    <alias>package[com.kingdee.eas.custom.synctask.synctask].alias</alias>
+    <description>package[com.kingdee.eas.custom.synctask.synctask].description</description>
+    <basicCode />
+    <resource>
+        <rs key="package[com.kingdee.eas.custom.synctask.synctask].alias">
+            <lang locale="en_US" value="null" />
+            <lang locale="zh_CN" value="同步任务" />
+            <lang locale="zh_HK" value="同步任務" />
+            <lang locale="zh_TW" value="同步任務" />
+        </rs>
+        <rs key="package[com.kingdee.eas.custom.synctask.synctask].description">
+            <lang locale="en_US" value="null" />
+            <lang locale="zh_CN" value="null" />
+            <lang locale="zh_HK" value="null" />
+            <lang locale="zh_TW" value="null" />
+        </rs>
+        <rs key="package[com.kingdee.eas.custom.synctask.synctask].extendedProperty.userDefined">
+            <lang locale="en_US" value="true" />
+            <lang locale="zh_CN" value="true" />
+            <lang locale="zh_TW" value="true" />
+        </rs>
+    </resource>
+</package>

+ 2 - 0
properties/syncAtsConfig.properties

@@ -0,0 +1,2 @@
+urlPath=http://192.168.6.10:8089/api/v2/transaction/get/
+SECRET_KEY=6c1e7otc9ggrv4garm6ds0jwpd97ndwjs316qgzzdfpl

+ 61 - 0
src/com/kingdee/eas/custom/synctask/AbstractSyncTranForAtsFacadeControllerBean.java

@@ -0,0 +1,61 @@
+package com.kingdee.eas.custom.synctask;
+
+import javax.ejb.*;
+import java.rmi.RemoteException;
+import com.kingdee.bos.*;
+import com.kingdee.bos.util.BOSObjectType;
+import com.kingdee.bos.metadata.IMetaDataPK;
+import com.kingdee.bos.metadata.rule.RuleExecutor;
+import com.kingdee.bos.metadata.MetaDataPK;
+//import com.kingdee.bos.metadata.entity.EntityViewInfo;
+import com.kingdee.bos.framework.ejb.AbstractEntityControllerBean;
+import com.kingdee.bos.framework.ejb.AbstractBizControllerBean;
+//import com.kingdee.bos.dao.IObjectPK;
+import com.kingdee.bos.dao.IObjectValue;
+import com.kingdee.bos.dao.IObjectCollection;
+import com.kingdee.bos.service.ServiceContext;
+import com.kingdee.bos.service.IServiceContext;
+import com.kingdee.eas.framework.Result;
+import com.kingdee.eas.framework.LineResult;
+import com.kingdee.eas.framework.exception.EASMultiException;
+import com.kingdee.bos.dao.ormapping.ObjectUuidPK;
+
+import com.kingdee.eas.common.EASBizException;
+import java.lang.String;
+
+
+
+public abstract class AbstractSyncTranForAtsFacadeControllerBean extends AbstractBizControllerBean implements SyncTranForAtsFacadeController
+{
+    protected AbstractSyncTranForAtsFacadeControllerBean()
+    {
+    }
+
+    protected BOSObjectType getBOSType()
+    {
+        return new BOSObjectType("8E08F1CD");
+    }
+
+    public void syncPunchRecord(Context ctx, String beginDate, String endDate, int offset) throws BOSException, EASBizException
+    {
+        try {
+            ServiceContext svcCtx = createServiceContext(new MetaDataPK("8d8deaef-40ef-45cd-a395-e450c235c9c4"), new Object[]{ctx, beginDate, endDate, new Integer(offset)});
+            invokeServiceBefore(svcCtx);
+              if(!svcCtx.invokeBreak()) {
+            _syncPunchRecord(ctx, beginDate, endDate, offset);
+            }
+            invokeServiceAfter(svcCtx);
+        } catch (BOSException ex) {
+            throw ex;
+        } catch (EASBizException ex0) {
+            throw ex0;
+        } finally {
+            super.cleanUpServiceState();
+        }
+    }
+    protected void _syncPunchRecord(Context ctx, String beginDate, String endDate, int offset) throws BOSException, EASBizException
+    {    	
+        return;
+    }
+
+}

+ 19 - 0
src/com/kingdee/eas/custom/synctask/ISyncTranForAtsFacade.java

@@ -0,0 +1,19 @@
+package com.kingdee.eas.custom.synctask;
+
+import com.kingdee.bos.BOSException;
+//import com.kingdee.bos.metadata.*;
+import com.kingdee.bos.framework.*;
+import com.kingdee.bos.util.*;
+import com.kingdee.bos.Context;
+
+import com.kingdee.bos.BOSException;
+import com.kingdee.bos.util.*;
+import com.kingdee.eas.common.EASBizException;
+import java.lang.String;
+import com.kingdee.bos.Context;
+import com.kingdee.bos.framework.*;
+
+public interface ISyncTranForAtsFacade extends IBizCtrl
+{
+    public void syncPunchRecord(String beginDate, String endDate, int offset) throws BOSException, EASBizException;
+}

+ 52 - 0
src/com/kingdee/eas/custom/synctask/SyncTranForAtsFacade.java

@@ -0,0 +1,52 @@
+package com.kingdee.eas.custom.synctask;
+
+import com.kingdee.bos.framework.ejb.EJBRemoteException;
+import com.kingdee.bos.util.BOSObjectType;
+import java.rmi.RemoteException;
+import com.kingdee.bos.framework.AbstractBizCtrl;
+import com.kingdee.bos.orm.template.ORMObject;
+
+import com.kingdee.bos.BOSException;
+import com.kingdee.bos.util.*;
+import com.kingdee.eas.common.EASBizException;
+import java.lang.String;
+import com.kingdee.bos.Context;
+import com.kingdee.bos.framework.*;
+import com.kingdee.eas.custom.synctask.*;
+
+public class SyncTranForAtsFacade extends AbstractBizCtrl implements ISyncTranForAtsFacade
+{
+    public SyncTranForAtsFacade()
+    {
+        super();
+        registerInterface(ISyncTranForAtsFacade.class, this);
+    }
+    public SyncTranForAtsFacade(Context ctx)
+    {
+        super(ctx);
+        registerInterface(ISyncTranForAtsFacade.class, this);
+    }
+    public BOSObjectType getType()
+    {
+        return new BOSObjectType("8E08F1CD");
+    }
+    private SyncTranForAtsFacadeController getController() throws BOSException
+    {
+        return (SyncTranForAtsFacadeController)getBizController();
+    }
+    /**
+     *同步打开记录-User defined method
+     *@param beginDate 开始日期
+     *@param endDate 结束日期
+     *@param offset 默认是当天; 如果-1,则开始日期从昨天开始; 结束日期还是今天的23点59; 
+     */
+    public void syncPunchRecord(String beginDate, String endDate, int offset) throws BOSException, EASBizException
+    {
+        try {
+            getController().syncPunchRecord(getContext(), beginDate, endDate, offset);
+        }
+        catch(RemoteException err) {
+            throw new EJBRemoteException(err);
+        }
+    }
+}

+ 22 - 0
src/com/kingdee/eas/custom/synctask/SyncTranForAtsFacadeController.java

@@ -0,0 +1,22 @@
+package com.kingdee.eas.custom.synctask;
+
+import com.kingdee.bos.BOSException;
+//import com.kingdee.bos.metadata.*;
+import com.kingdee.bos.framework.*;
+import com.kingdee.bos.util.*;
+import com.kingdee.bos.Context;
+
+import com.kingdee.bos.BOSException;
+import com.kingdee.bos.util.*;
+import com.kingdee.eas.common.EASBizException;
+import java.lang.String;
+import com.kingdee.bos.Context;
+import com.kingdee.bos.framework.*;
+
+import java.rmi.RemoteException;
+import com.kingdee.bos.framework.ejb.BizController;
+
+public interface SyncTranForAtsFacadeController extends BizController
+{
+    public void syncPunchRecord(Context ctx, String beginDate, String endDate, int offset) throws BOSException, EASBizException, RemoteException;
+}

+ 369 - 0
src/com/kingdee/eas/custom/synctask/SyncTranForAtsFacadeControllerBean.java

@@ -0,0 +1,369 @@
+package com.kingdee.eas.custom.synctask;
+
+import com.kingdee.bos.dao.IObjectPK;
+import com.kingdee.bos.metadata.entity.EntityViewInfo;
+import com.kingdee.bos.metadata.entity.FilterInfo;
+import com.kingdee.bos.metadata.entity.FilterItemInfo;
+import com.kingdee.bos.metadata.entity.SelectorItemInfo;
+import com.kingdee.bos.metadata.query.util.CompareType;
+import com.kingdee.eas.base.permission.UserInfo;
+import com.kingdee.eas.basedata.person.PersonCollection;
+import com.kingdee.eas.basedata.person.PersonInfo;
+import com.kingdee.eas.common.EASBizException;
+import com.kingdee.eas.custom.synctask.util.HttpClientUtil;
+import com.kingdee.eas.hr.ats.*;
+import com.kingdee.eas.hr.ats.holidayLimit.generate.util.HRTimeWebUtils;
+
+import org.apache.log4j.Logger;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import com.kingdee.bos.*;
+import com.kingdee.eas.basedata.person.IPerson;
+import com.kingdee.eas.basedata.person.PersonFactory;
+import com.kingdee.eas.framework.CoreBaseCollection;
+import com.kingdee.util.StringUtils;
+
+import oadd.org.apache.commons.codec.digest.DigestUtils;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+
+public class SyncTranForAtsFacadeControllerBean extends AbstractSyncTranForAtsFacadeControllerBean
+{
+    private static Logger logger =  Logger.getLogger(SyncTranForAtsFacadeControllerBean.class);
+
+    private volatile Properties propt;  // 使用 volatile 保证可见性
+    private final Object configLock = new Object();
+
+    private void loadProperties() throws BOSException {
+        if (propt == null) {  // 第一次检查
+            synchronized (configLock) {
+                if (propt == null) {  // 第二次检查(双重检查锁)
+                    Properties temp = new Properties();
+                    String path = System.getProperty("EAS_HOME") + "/server/properties/abk/syncAtsConfig.properties";
+                    try (FileInputStream fis = new FileInputStream(path)) {  // try-with-resources 自动关闭流
+                        temp.load(fis);
+                        propt = temp;  // 原子性赋值
+                    } catch (IOException e) {
+                        String errorMsg = "加载配置文件失败: " + path + " | " + e.getMessage();
+                        logger.error(errorMsg, e);  // 使用日志记录代替 e.printStackTrace()
+                        throw new BOSException(errorMsg);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void _syncPunchRecord(Context ctx, String beginDate, String endDate, int offset) throws BOSException {
+        loadProperties();
+        // 0. 参数校验与日期解析
+        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        LocalDate start = LocalDate.now();
+        LocalDate end = LocalDate.now();
+        //如果时间参数都为空,则使用offset参数
+        if(StringUtils.isEmpty(beginDate) && StringUtils.isEmpty(endDate)){
+    		if( offset > 0 ){
+                //eDate增加offset天
+                end = end.plusDays(offset);
+    		}else if( offset < 0 ) {
+                start = start.plusDays(offset);
+    		}
+        }else  if(StringUtils.isEmpty(beginDate) ){
+            //开始日期字定义,结束日期都是今天
+            start = LocalDate.parse(beginDate, dateFormatter);
+        }else  if(StringUtils.isEmpty(endDate)){
+            //开始日期,结束日期都是今天
+            end = LocalDate.parse(endDate, dateFormatter);
+        }
+        if (start.isAfter(end)) {
+            throw new BOSException("开始日期不能晚于结束日期");
+        }
+
+        // 1. 按天循环处理
+        LocalDate currentDay = start;
+        while (!currentDay.isAfter(end)) {
+            try {
+                // 当天日期字符串
+                String dayStr = currentDay.format(dateFormatter);
+
+                // 生成 Token 和 URL 参数
+                long currentTime = Instant.now().getEpochSecond();
+                String secretKey = propt.getProperty("SECRET_KEY");
+                String token = DigestUtils.md5Hex(secretKey + currentTime);
+                // 构建查询参数
+                Map<String, String> queryParams = new HashMap<>();
+                queryParams.put("timestamp", String.valueOf(currentTime));
+                queryParams.put("token", token);
+
+                // 构建请求体(自动处理时间范围)
+                JSONObject requestBody = buildRequestParams(dayStr, dayStr); // 同一天
+
+                // 调用接口
+                String urlPath = propt.getProperty("urlPath");
+                String response = HttpClientUtil.sendPostRequest(urlPath, requestBody, queryParams);
+
+                // 处理响应
+                if (response != null) {
+                    JSONObject result = JSONObject.parseObject(response);
+                    if ("0".equals(result.getString("ret"))) {
+                    	JSONObject data = result.getJSONObject("data");
+                        JSONArray items = data.getJSONArray("items");
+                        logger.error("日期 " + dayStr + " 打卡同步接口,接口共传输记录数: " + items.size()+"条数据");
+                        saveMJPunchCardData(ctx, items);
+                    }
+                }
+            } catch (Exception e) {
+                // 记录错误但继续执行
+                logger.error("日期 " + currentDay + " 数据同步失败: " + e.getMessage(), e);
+            } finally {
+                currentDay = currentDay.plusDays(1); // 处理下一天
+            }
+        }
+    }
+
+
+    
+    
+    
+    private JSONObject buildRequestParams(String startDate, String endDate) {
+        JSONObject params = new JSONObject();
+        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        DateTimeFormatter datetimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        
+        LocalDate today = LocalDate.now();
+        // 处理 startDate:默认为当天 00:00:00
+        if (StringUtils.isEmpty(startDate)) {
+            startDate = today.atStartOfDay().format(datetimeFormatter);
+        } else {
+            startDate = LocalDate.parse(startDate, dateFormatter)
+                                .atStartOfDay()
+                                .format(datetimeFormatter);
+        }
+        
+        // 处理 endDate:默认为当天 23:59:59
+        if (StringUtils.isEmpty(endDate)) {
+            endDate = today.atTime(23, 59, 59).format(datetimeFormatter);
+        } else {
+            endDate = LocalDate.parse(endDate, dateFormatter)
+                              .atTime(23, 59, 59)
+                              .format(datetimeFormatter);
+        }
+        params.put("starttime", startDate);
+        params.put("endtime", endDate);
+        return params;
+    }
+    
+    
+
+
+    /**
+     * 保存门禁打卡记录
+     * @param ctx
+     * @param jsonArray
+     */
+    public void saveMJPunchCardData(Context ctx, JSONArray jsonArray) {
+        logger.error("saveMJPunchCardData----");
+        try {
+            int success = 0;
+            Set<String> setCardId = new HashSet();
+
+            CoreBaseCollection cardColl = new CoreBaseCollection();
+            //没有考勤档案的人员数据
+
+            //获取员工编码
+            IPerson iPerson = PersonFactory.getLocalInstance(ctx);
+            PersonCollection personCollection = iPerson.getPersonCollection("select number ");
+            Set<String> persNoSet = new HashSet();
+            for (int i = 0; i < personCollection.size(); i++) {
+                PersonInfo personInfo = personCollection.get(i);
+                persNoSet.add(personInfo.getNumber().toString());
+            }
+
+            //"id": 928547,
+            //				"pin": "1234",
+            //				"ename": "何霞兰",
+            //				"deptnumber": "1",
+            //				"deptname": "总部门",
+            //				"checktime": "2025-04-25 00:00:12",
+            //				"sn": "FAC1234701127",
+            //				"alias": "惠州1号楼1层",
+            //				"verify": 15,
+            //				"stateno": "5",
+            //				"state": ""
+
+            //查询本此打卡机过来的数据的id
+            HashSet<String> attIds = new HashSet();
+            for (int j = 0; j < jsonArray.size(); j++) {
+                JSONObject resultData = jsonArray.getJSONObject(j);
+                attIds.add(resultData.get("id").toString());
+            }
+
+            //相同的数据
+            HashSet<String> existedPunchCardRecordCol = getExistedPunchCardRecordCol(ctx, attIds);
+            for (int j = 0; j < jsonArray.size(); j++) {
+                JSONObject resultData = jsonArray.getJSONObject(j);
+                String attId = resultData.getString("id");
+                if (existedPunchCardRecordCol.contains(attId)) {
+                    continue;
+                }
+                ++success;
+                String personNumber = resultData.getString("pin");//员工编码
+                String checktime = resultData.getString("checktime"); //打卡时间
+                String RecDate = checktime.substring(0, 10);// 考勤日期
+                String address = resultData.getString("alias"); //打卡地点
+                String EquNo = resultData.getString("sn"); //考勤机编码
+                SimpleDateFormat sdfymd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                Date punchDate = sdfymd.parse(RecDate + " 00:00:00");
+//                Date punchDate = HRTimeWebUtils.stringToShortDate(RecDate, true);
+                Timestamp punchCardTime = HRTimeWebUtils.stringToTimestamp(checktime, true);
+                // 处理数据
+                if (punchDate != null && punchCardTime != null) {
+                    PunchCardRecordInfo cardInfo = new PunchCardRecordInfo();
+                    cardInfo.setNumber(attId);
+                    //考勤机编码
+                    cardInfo.setEquipmentNum(EquNo);
+                    //员工编码
+                    cardInfo.setAttendanceNum(personNumber);
+                    //打卡日期
+                    cardInfo.setPunchCardDate(punchDate);
+                    //打卡时间
+                    cardInfo.setPunchCardTime(punchCardTime);
+                    // 打卡来源
+                    cardInfo.setPunchCardSource(PunchCardSourceEnum.attenceMachine);
+                    // 打卡位置
+                    cardInfo.setPunchCardPlace(address);
+                    // 有效
+                    PunchCardStateEnum punchCardStateEnum;
+                    //if ((boolean) resultData.get("isActive")) {
+                    //    punchCardStateEnum = PunchCardStateEnum.normal;
+                    //} else {
+                    //    punchCardStateEnum = PunchCardStateEnum.cancelled;
+                    //}
+                    punchCardStateEnum = PunchCardStateEnum.normal;
+                    cardInfo.setPunchCardState(punchCardStateEnum);
+                    ////简称
+                    //cardInfo.setSimpleName(exceptionType);
+                    //描述
+                    cardInfo.setDescription("考勤机同步");
+                    if (!StringUtils.isEmpty(personNumber)) {
+                        //shr系统存在的人员才添加;
+                        if (!persNoSet.contains(personNumber)) {
+                            logger.error("打卡同步:personNumber not exist in shr system, personNumber=" + personNumber);
+                            continue;
+                        }
+                        setCardId.add(personNumber);
+                        cardColl.add(cardInfo);
+                    }
+                }
+            }
+            syncAttendanceRecords(setCardId, cardColl, ctx);
+        } catch (Exception e) {
+            logger.error("保存门禁打卡记录失败: " + e.getMessage(), e);
+        }
+            // 考勤档案
+
+    }
+
+
+
+
+
+    private void syncAttendanceRecords(Set<String> setCardId, CoreBaseCollection  cardColl, Context ctx) throws BOSException, EASBizException {
+        // 获取考勤档案
+        Map<String, AttendanceFileInfo> attendanceFileMap = AttendanceFileFactory.getLocalInstance(ctx).getPersonByAttendanceNum(setCardId);
+        logger.error("attendanceFileMap-----" + attendanceFileMap);
+        logger.error("setCardId-----" + setCardId);
+
+        CoreBaseCollection cardCollFinally = new CoreBaseCollection();
+        Collection<Map> seccussArray = new ArrayList<>();
+        Collection<JSONObject> notExistRecords = new ArrayList<>();
+        int success = 0;
+
+        for (int i = 0; i < cardColl.size(); ++i) {
+            Map<String, String> map = new HashMap<>();
+            // 原始打卡记录实体
+            PunchCardRecordInfo cardInfo = (PunchCardRecordInfo) cardColl.get(i);
+            JSONObject jsObject = new JSONObject();
+            jsObject.put("deviceid", cardInfo.getEquipmentNum()); // deviceid --EquNo
+            jsObject.put("userid", cardInfo.getAttendanceNum()); // userid --CardId
+            jsObject.put("RecDate", HRTimeWebUtils.dateShortToString(cardInfo.getPunchCardDate())); //checkin_time --RecDate
+            jsObject.put("RecTime", HRTimeWebUtils.timestampToString(cardInfo.getPunchCardTime()).substring(11)); //checkin_time --RecTime
+            map.put("userid", cardInfo.getAttendanceNum());
+            map.put("RecDate", HRTimeWebUtils.dateShortToString(cardInfo.getPunchCardDate()));
+            map.put("RecTime", HRTimeWebUtils.timestampToString(cardInfo.getPunchCardTime()).substring(11));
+
+            // 员工是否有考勤档案
+            if (attendanceFileMap.containsKey(cardInfo.getAttendanceNum())) {
+                ++success;
+                // 打卡位置 ,考勤机 的地址
+                cardInfo.setPunchCardPlace(cardInfo.getPunchCardPlace());
+                // 打卡来源
+                cardInfo.setPunchCardSource(PunchCardSourceEnum.attenceMachine);
+                // 考勤实体
+                AttendanceFileInfo attendanceFileInfo = attendanceFileMap.get(cardInfo.getAttendanceNum());
+                // 姓名
+                cardInfo.setProposer(attendanceFileInfo.getProposer());
+                // HR 组织
+                cardInfo.setHrOrgUnit(attendanceFileInfo.getHrOrgUnit());
+                // 行政组织
+                cardInfo.setAdminOrgUnit(attendanceFileInfo.getAdminOrgUnit());
+                cardInfo.setCreator((UserInfo) ctx.get("UserInfo"));
+                cardInfo.setLastUpdateUser((UserInfo) ctx.get("UserInfo"));
+                cardInfo.setCreateTime(new Timestamp(new Date().getTime()));
+                cardInfo.setLastUpdateTime(new Timestamp(new Date().getTime()));
+                cardCollFinally.add(cardInfo);
+                seccussArray.add(map);
+            } else {
+                notExistRecords.add(jsObject);
+            }
+        }
+
+        logger.error("打卡同步保存到hr数量-----" + cardCollFinally.size());
+        logger.error("打卡同步符合条件的写入数量-----" + success);
+        logger.error("打卡同步时不存在考勤档案---" + notExistRecords);
+        logger.error("打卡同步时不存在考勤档案数量-----" + notExistRecords.size());
+        logger.error("seccussArray--Size-----" + seccussArray.size());
+        logger.error("seccussArray-----" + seccussArray);
+
+        if (cardCollFinally.size() > 0) {
+            IObjectPK[] iObjectPKS = PunchCardRecordFactory.getLocalInstance(ctx).saveBatchData(cardCollFinally);
+            logger.error("iObjectPKS----" + iObjectPKS.length);
+        }
+    }
+
+
+
+
+
+
+
+
+
+
+    private HashSet<String>  getExistedPunchCardRecordCol(Context ctx, HashSet<String> attId) throws BOSException {
+    	HashSet<String> hashSet = new HashSet<String>();
+        EntityViewInfo evi = new EntityViewInfo();
+        FilterInfo fi = new FilterInfo();
+        evi.setFilter(fi);
+        evi.getSelector().add(new SelectorItemInfo("number"));
+        fi.getFilterItems().add(new FilterItemInfo("number", attId, CompareType.INCLUDE));
+        PunchCardRecordCollection existColl = PunchCardRecordFactory.getLocalInstance(ctx).getPunchCardRecordCollection(evi);
+        for(int i =0 ;i<existColl.size();i++) {
+        	PunchCardRecordInfo punchCardRecordInfo = existColl.get(i);
+        	hashSet.add(punchCardRecordInfo.getNumber());
+        }
+        return hashSet;
+
+    }
+
+
+
+    
+}

+ 30 - 0
src/com/kingdee/eas/custom/synctask/SyncTranForAtsFacadeFactory.java

@@ -0,0 +1,30 @@
+package com.kingdee.eas.custom.synctask;
+
+import com.kingdee.bos.BOSException;
+import com.kingdee.bos.BOSObjectFactory;
+import com.kingdee.bos.util.BOSObjectType;
+import com.kingdee.bos.Context;
+
+public class SyncTranForAtsFacadeFactory
+{
+    private SyncTranForAtsFacadeFactory()
+    {
+    }
+    public static com.kingdee.eas.custom.synctask.ISyncTranForAtsFacade getRemoteInstance() throws BOSException
+    {
+        return (com.kingdee.eas.custom.synctask.ISyncTranForAtsFacade)BOSObjectFactory.createRemoteBOSObject(new BOSObjectType("8E08F1CD") ,com.kingdee.eas.custom.synctask.ISyncTranForAtsFacade.class);
+    }
+    
+    public static com.kingdee.eas.custom.synctask.ISyncTranForAtsFacade getRemoteInstanceWithObjectContext(Context objectCtx) throws BOSException
+    {
+        return (com.kingdee.eas.custom.synctask.ISyncTranForAtsFacade)BOSObjectFactory.createRemoteBOSObjectWithObjectContext(new BOSObjectType("8E08F1CD") ,com.kingdee.eas.custom.synctask.ISyncTranForAtsFacade.class, objectCtx);
+    }
+    public static com.kingdee.eas.custom.synctask.ISyncTranForAtsFacade getLocalInstance(Context ctx) throws BOSException
+    {
+        return (com.kingdee.eas.custom.synctask.ISyncTranForAtsFacade)BOSObjectFactory.createBOSObject(ctx, new BOSObjectType("8E08F1CD"));
+    }
+    public static com.kingdee.eas.custom.synctask.ISyncTranForAtsFacade getLocalInstance(String sessionID) throws BOSException
+    {
+        return (com.kingdee.eas.custom.synctask.ISyncTranForAtsFacade)BOSObjectFactory.createBOSObject(sessionID, new BOSObjectType("8E08F1CD"));
+    }
+}

+ 60 - 0
src/com/kingdee/eas/custom/synctask/util/HttpClientUtil.java

@@ -0,0 +1,60 @@
+package com.kingdee.eas.custom.synctask.util;
+
+import okhttp3.*;
+import org.apache.log4j.Logger;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import com.alibaba.fastjson.JSONObject;
+
+public class HttpClientUtil {
+    private static final Logger logger = Logger.getLogger(HttpClientUtil.class);
+    private static final OkHttpClient client = new OkHttpClient.Builder()
+            .connectTimeout(30, TimeUnit.SECONDS)
+            .readTimeout(30, TimeUnit.SECONDS)
+            .build();
+
+    /**
+     * 发送 POST 请求(JSON 格式)
+     * @param urlPath   接口地址(可含路径参数,如 `/api/v2/transaction/get`)
+     * @param params    JSON 请求体
+     * @param queryParams URL 查询参数(如 `timestamp` 和 `token`)
+     * @return 响应体字符串(失败返回 null)
+     */
+    public static String sendPostRequest(String urlPath, JSONObject params, Map<String, String> queryParams) {
+        try {
+            // 1. 构建完整 URL(含查询参数)
+            HttpUrl.Builder urlBuilder = HttpUrl.parse(urlPath).newBuilder();
+            if (queryParams != null) {
+                queryParams.forEach(urlBuilder::addQueryParameter);
+            }
+            String fullUrl = urlBuilder.build().toString();
+
+            // 2. 创建请求体
+            MediaType mediaType = MediaType.parse("application/json");
+            RequestBody body = RequestBody.create(mediaType, params.toJSONString());
+
+            // 3. 构建请求
+            Request request = new Request.Builder()
+                    .url(fullUrl)
+                    .post(body)
+                    .addHeader("Content-Type", "application/json")
+                    .build();
+
+            // 4. 发送请求并处理响应
+            try (Response response = client.newCall(request).execute()) {
+                if (response.isSuccessful() && response.body() != null) {
+                    return response.body().string();
+                } else {
+                    logger.error("请求失败,状态码: " + response.code());
+                }
+            }
+        } catch (IOException e) {
+            logger.error("请求异常: " + e.getMessage(), e);
+        }
+        return null;
+    }
+
+
+}

+ 146 - 0
src/com/kingdee/shr/compensation/service/CalFunctionServiceExt.java

@@ -0,0 +1,146 @@
+package com.kingdee.shr.compensation.service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.codec.binary.StringUtils;
+import com.kingdee.bos.BOSException;
+import com.kingdee.bos.Context;
+import com.kingdee.eas.util.app.DbUtil;
+import com.kingdee.jdbc.rowset.IRowSet;
+import com.kingdee.shr.compensation.CalSchemeInfo;
+import com.kingdee.shr.compensation.app.formula.SalaryCalFunction;
+import com.kingdee.shr.compensation.app.formula.data.Tools;
+
+
+
+/**
+ * 函数二开
+ * @author coyle
+ * 202505
+ */
+public class CalFunctionServiceExt extends SalaryCalFunction {
+
+	
+	
+	/**
+	 * 水电费取数
+	 * @param type
+	 * @param paramMap
+	 * @param tempMap
+	 * @param calScheme
+	 * @param mainTableRowFilter
+	 * @return
+	 */
+	public double getWaterOrElectPay(String type, Map paramMap, HashMap tempMap, CalSchemeInfo calScheme, String mainTableRowFilter) {
+		Context ctx = Tools.getInstance().getCtx();
+		String key = "getWaterOrElectPay" + calScheme.getNumber() + calScheme.getPeriodYear() + calScheme.getPeriodMonth();
+		 Map<String, Double> dataMap = new  HashMap<String, Double>();
+		String personId = paramMap.get("T_HR_SCMPCALTABLE_FPERSONID") == null ? null : paramMap.get("T_HR_SCMPCALTABLE_FPERSONID").toString();
+		BigDecimal amt = BigDecimal.ZERO;
+		if (personId == null) return 0.0;
+		if (null == tempMap || null == tempMap.get(key)) {
+		    String sqlStr = getPerformanceDataSql(personId, mainTableRowFilter, calScheme);
+		    try {
+		        IRowSet rowSet = DbUtil.executeQuery(ctx, sqlStr);
+		        while (rowSet.next()) {
+		            personId = rowSet.getString("cFPersonId");
+		            // 处理可能的null值,默认为BigDecimal.ZERO
+		            BigDecimal wamt = rowSet.getBigDecimal("wamt") == null ? BigDecimal.ZERO : rowSet.getBigDecimal("wamt");
+		            BigDecimal spwamt = rowSet.getBigDecimal("spwamt") == null ? BigDecimal.ZERO : rowSet.getBigDecimal("spwamt");
+		            BigDecimal eamt = rowSet.getBigDecimal("eamt") == null ? BigDecimal.ZERO : rowSet.getBigDecimal("eamt");
+		            BigDecimal speamt = rowSet.getBigDecimal("speamt") == null ? BigDecimal.ZERO : rowSet.getBigDecimal("speamt");
+
+		            if (StringUtils.equals("1", type)) {
+		                amt = wamt.add(spwamt);
+		            } else if (StringUtils.equals("2", type)) {
+		                amt = eamt.add(speamt);
+		            } else {
+		                amt = wamt.add(spwamt).add(eamt).add(speamt);
+		            }
+
+		            // 设置两位小数并四舍五入
+		            amt = amt.setScale(2, RoundingMode.HALF_UP);
+		            // 转换为double类型
+		            double amtDouble = amt.doubleValue();
+		            dataMap.put(personId + "_" + type, amtDouble);
+		        }
+		    } catch (BOSException | SQLException e) {
+		        e.printStackTrace();
+		    }
+		}
+		tempMap.put(key, dataMap);
+		dataMap = (  Map<String, Double>) tempMap.get(key);
+		return dataMap.get(personId + "_" + type) == null ? 0.00 : dataMap.get(personId + "_" + type);
+	}
+	
+	
+	
+	
+	
+	
+	
+	
+	/**
+	 * 取数sql
+	 * @param personId
+	 * @param mainTableRowFilter
+	 * @param calScheme
+	 * @return
+	 */
+	private String getPerformanceDataSql(String personId, String mainTableRowFilter,  CalSchemeInfo calScheme){
+		//mainTableRowFilter 格式:t_hr_scmpcaltable.fid in (select FID from T_HR_SCmpCalTable where FCalState in (1,2,3,4,5,6,7)
+		//and FID in (select * from VTZD0RDVX15O2LMJ0F0TWRZ7UYW8YKVSD8D))
+		StringBuffer sqlStr = new StringBuffer();
+		sqlStr.append("SELECT cFPersonId, ");
+		sqlStr.append("       SUM(wamt) AS wamt, ");
+		sqlStr.append("       SUM(eamt) AS eamt, ");
+		sqlStr.append("       SUM(spwamt) AS spwamt, ");
+		sqlStr.append("       SUM(speamt) AS speamt ");
+		sqlStr.append("FROM ( ");
+
+		// 第一部分:普通表数据(CT_WOE_WaterElectricity)
+		sqlStr.append("    SELECT defu.cFPersonId, ");
+		sqlStr.append("           defu.cfmonthWaterAmount AS wamt, ");
+		sqlStr.append("           defu.cfmonthElectricityAmount AS eamt, ");
+		sqlStr.append("           0 AS spwamt, ");  // 普通表无特殊字段,设为0(方便SUM)
+		sqlStr.append("           0 AS speamt ");
+		sqlStr.append("    FROM CT_WOE_WaterElectricity defu ");
+		sqlStr.append("    WHERE ");
+		if (null == mainTableRowFilter) {
+		    sqlStr.append("        defu.cFPersonId = '").append(personId).append("' ");
+		} else {
+		    sqlStr.append("        defu.cFPersonId IN (SELECT FPersonId FROM t_hr_scmpcaltable ");
+		    sqlStr.append("                          WHERE ").append(mainTableRowFilter).append(") ");
+		}
+		sqlStr.append("    AND defu.CFYearMonth = '").append(calScheme.getPeriodYear()).append("-").append(calScheme.getPeriodMonth()).append("' ");
+		sqlStr.append("    AND defu.fbillstate = '3' ");
+
+		sqlStr.append("    UNION ALL ");
+
+		// 第二部分:特殊表数据(CT_WOE_WaterElectricitySpecial)
+		sqlStr.append("    SELECT special.CFPersonId, ");
+		sqlStr.append("           0 AS wamt, ");  // 特殊表无普通字段,设为0(方便SUM)
+		sqlStr.append("           0 AS eamt, ");
+		sqlStr.append("           special.CFMonthWaterAmount AS spwamt, ");
+		sqlStr.append("           special.CFelectricityTotalCost AS speamt ");
+		sqlStr.append("    FROM CT_WOE_WaterElectricitySpecial special ");
+		sqlStr.append("    WHERE ");
+		if (null == mainTableRowFilter) {
+		    sqlStr.append("        special.CFPersonID = '").append(personId).append("' ");
+		} else {
+		    sqlStr.append("        special.CFPersonID IN (SELECT FPersonId FROM t_hr_scmpcaltable ");
+		    sqlStr.append("                              WHERE ").append(mainTableRowFilter).append(") ");
+		}
+		sqlStr.append("    AND special.CFYearMonth = '").append(calScheme.getPeriodYear()).append("-").append(calScheme.getPeriodMonth()).append("' ");
+		sqlStr.append("    AND special.fbillstate = '3' ");
+
+		sqlStr.append(") combined_data ");  // 子查询别名
+		sqlStr.append("GROUP BY cFPersonId ");  // 按人员ID分组汇总
+
+		return sqlStr.toString();
+	}
+
+}