|
@@ -0,0 +1,396 @@
|
|
|
|
+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.utils.ObjectUtils;
|
|
|
|
+import kd.bos.logging.Log;
|
|
|
|
+import kd.bos.logging.LogFactory;
|
|
|
|
+import kd.bos.servicehelper.BusinessDataServiceHelper;
|
|
|
|
+import kd.wtc.wtbs.common.util.WTCDateUtils;
|
|
|
|
+
|
|
|
|
+import java.io.IOException;
|
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
|
+import java.time.LocalTime;
|
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
|
+import java.util.Date;
|
|
|
|
+import java.util.Map;
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * description: 同步钉钉通用帮助类、包含休假、出差、补签
|
|
|
|
+ * 分三种情况:新增,变更,撤销
|
|
|
|
+ * 1、正常通知审批通过,单据审批通过后调用钉钉审批通过接口
|
|
|
|
+ * 2、星瀚单据变更后根据ApproveId调用钉钉接口更新,补卡没有变更
|
|
|
|
+ * 3、星瀚单据变更失效后,调用钉钉审批撤销接口,请假单判断是否失效标识:isnotleave,出差单判断是否失效标识:isnottrip,
|
|
|
|
+ * @author Tyx
|
|
|
|
+ * @date 2025/5/29
|
|
|
|
+ */
|
|
|
|
+public class SyncAttendanceHelper {
|
|
|
|
+
|
|
|
|
+ private static Log log = LogFactory.getLog(SyncAttendanceHelper.class);
|
|
|
|
+ private static String baseUrl = "";
|
|
|
|
+ private static String finishParamKey = "getFinishUrl";
|
|
|
|
+ private static String cancelParamKey = "getCancelUrl";
|
|
|
|
+ private static String fillParamKey = "getFillUrl";
|
|
|
|
+ private static String shiftParamKey = "getShiftUrl";
|
|
|
|
+ private static String billNo = "";
|
|
|
|
+ private static String accessToken = "";
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 统一入口
|
|
|
|
+ * @param bill 单据Dyo
|
|
|
|
+ * @param entityNumber 单据标识
|
|
|
|
+ * @param bizType 类型 1:加班,2:出差,3:请假,4:补卡
|
|
|
|
+ * @param isChange 是否变更
|
|
|
|
+ */
|
|
|
|
+ public static void execute (DynamicObject bill, String entityNumber, String bizType, boolean isChange) {
|
|
|
|
+ billNo = bill.getString("billno");
|
|
|
|
+ //用户映射Map key = 人员工号, value = openId
|
|
|
|
+ Map<String, String> openMap = DingTalkSyncUtil.convertMappingInfo();
|
|
|
|
+ //获取accessToken
|
|
|
|
+ try {
|
|
|
|
+ //重新查一遍单据 避免部分属性没有
|
|
|
|
+ bill = BusinessDataServiceHelper.loadSingle(bill.getPkValue(), entityNumber);
|
|
|
|
+ //获取AccessToken
|
|
|
|
+ accessToken = DingTalkSyncUtil.getAccessToken();
|
|
|
|
+ log.info("钉钉单据同步实际调用accessToken : {}", accessToken);
|
|
|
|
+ //构建参数
|
|
|
|
+ JSONObject param = buildContent(bill, entityNumber, bizType, isChange, openMap);
|
|
|
|
+ log.info("钉钉单据同步实际调用入参报文 : {}", param.toJSONString());
|
|
|
|
+ //获取调用url
|
|
|
|
+ String url = getUrl(baseUrl, accessToken);
|
|
|
|
+ log.info("钉钉单据同步实际调用url : {}", url);
|
|
|
|
+ //实际调用
|
|
|
|
+ JSONObject reponseObject = doPost(url, param);
|
|
|
|
+ log.info("钉钉单据同步实际返回信息 : {}", reponseObject.toJSONString());
|
|
|
|
+ //保存日志
|
|
|
|
+ DingTalkSyncUtil.createLog("A", billNo, param.toJSONString(),reponseObject.toJSONString(),DingTalkSyncUtil.typeMap.get(bizType));
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ DingTalkSyncUtil.createLog("C", billNo, bill.getString("billno") + "调用失败", e.getMessage(), DingTalkSyncUtil.typeMap.get(bizType));
|
|
|
|
+ throw new RuntimeException(e);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 实际调用
|
|
|
|
+ * @param url
|
|
|
|
+ * @param bodyData
|
|
|
|
+ * @return
|
|
|
|
+ * @throws IOException
|
|
|
|
+ */
|
|
|
|
+ public static JSONObject doPost (String url, JSONObject bodyData) throws IOException {
|
|
|
|
+ return DingTalkSyncUtil.doPostByHttpClient(url, bodyData);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 构建入参
|
|
|
|
+ * @param bill 单据Dyo
|
|
|
|
+ * @param entityNumber 单据标识
|
|
|
|
+ * @param bizType 类型 1:加班,2:出差,3:请假
|
|
|
|
+ * @param isChange 是否变更
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public static JSONObject buildContent(DynamicObject bill, String entityNumber, String bizType, boolean isChange, Map openMap) throws IOException {
|
|
|
|
+ JSONObject param = new JSONObject();
|
|
|
|
+ switch(bizType) {
|
|
|
|
+ case "2" :
|
|
|
|
+ buildTripContent(param, bill, entityNumber, isChange, openMap);
|
|
|
|
+ break;
|
|
|
|
+ case "3" :
|
|
|
|
+ buildVAContent(param, bill, entityNumber, isChange, openMap);
|
|
|
|
+ break;
|
|
|
|
+ case "4" :
|
|
|
|
+ buildFillContent(param, bill, entityNumber, isChange, openMap);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ return param;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 构建出差入参
|
|
|
|
+ * @param param
|
|
|
|
+ * @param bill
|
|
|
|
+ * @param entityNumber
|
|
|
|
+ * @param isChange
|
|
|
|
+ * @param openMap
|
|
|
|
+ */
|
|
|
|
+ private static void buildTripContent(JSONObject param, DynamicObject bill, String entityNumber, boolean isChange, Map openMap) {
|
|
|
|
+ log.info("构建出差入参报文 start ");
|
|
|
|
+ boolean isCancel = bill.getBoolean("isnottrip");
|
|
|
|
+ getBaseUrl(isCancel, false);
|
|
|
|
+ //判断是否出差失效
|
|
|
|
+ if(isCancel) {
|
|
|
|
+ param.put("approve_id", bill.getLong("originalid"));
|
|
|
|
+ param.put("userid", openMap.get(bill.getString("attfile.personnum")));
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ param.put("biz_type", 2);
|
|
|
|
+ param.put("jump_url", "https://open.dingtalk.com/");
|
|
|
|
+ param.put("tag_name", "出差");
|
|
|
|
+ param.put("calculate_model", "1");
|
|
|
|
+ param.put("userid", openMap.get(bill.getString("attfile.personnum")));
|
|
|
|
+ DynamicObject entry = bill.getDynamicObjectCollection("entryentity").get(0);
|
|
|
|
+ //sub_type 请假类型
|
|
|
|
+ param.put("sub_type", entry.getString("busitriptype.name"));
|
|
|
|
+ //approve_id 直接取原单ID
|
|
|
|
+ param.put("approve_id", bill.getLong("originalid"));
|
|
|
|
+ //分录单位 0-上半天 1-下半天 2-全天 3-时分
|
|
|
|
+ String startMethod = entry.getString("startmethod");
|
|
|
|
+ String endMethod = entry.getString("endmethod");
|
|
|
|
+ //duration_unit 单位 day-全天 halfDay-半天 hour-时分
|
|
|
|
+ String unitStr = "";
|
|
|
|
+ switch (startMethod) {
|
|
|
|
+ case "0":
|
|
|
|
+ case "1":
|
|
|
|
+ unitStr = "halfDay";
|
|
|
|
+ break;
|
|
|
|
+ case "2":
|
|
|
|
+ unitStr = "day";
|
|
|
|
+ break;
|
|
|
|
+ case "3":
|
|
|
|
+ unitStr = "hour";
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ param.put("duration_unit", unitStr);
|
|
|
|
+ //根据单位处理时长
|
|
|
|
+ param.put("from_time", dealHalfDaySdf(entry.getDate("startdate"), startMethod));
|
|
|
|
+ param.put("to_time", dealHalfDaySdf(entry.getDate("enddate"), endMethod));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 构建休假入参
|
|
|
|
+ * @param param
|
|
|
|
+ * @param bill
|
|
|
|
+ * @param entityNumber
|
|
|
|
+ * @param isChange
|
|
|
|
+ * @param openMap
|
|
|
|
+ */
|
|
|
|
+ private static void buildVAContent(JSONObject param, DynamicObject bill, String entityNumber, boolean isChange, Map openMap) {
|
|
|
|
+ log.info("构建休假入参报文 start ");
|
|
|
|
+ boolean isCancel = bill.getBoolean("isnotleave");
|
|
|
|
+ getBaseUrl(isCancel, false);
|
|
|
|
+ //休假失效的情况:
|
|
|
|
+ if(isCancel) {
|
|
|
|
+ param.put("approve_id", bill.getLong("originalid"));
|
|
|
|
+ param.put("userid", openMap.get(bill.getString("attfile.personnum")));
|
|
|
|
+ }
|
|
|
|
+ //非失效的情况:
|
|
|
|
+ else {
|
|
|
|
+ param.put("biz_type", 3);
|
|
|
|
+ param.put("jump_url", "https://open.dingtalk.com/");
|
|
|
|
+ param.put("tag_name", "请假");
|
|
|
|
+ param.put("calculate_model", "1");
|
|
|
|
+ param.put("userid", openMap.get(bill.getString("attfile.personnum")));
|
|
|
|
+ DynamicObject entry = bill.getDynamicObjectCollection("entryentity").get(0);
|
|
|
|
+ //sub_type 请假类型
|
|
|
|
+ param.put("sub_type", entry.getString("entryvacationtype.name"));
|
|
|
|
+ //approve_id 直接取原单ID
|
|
|
|
+ param.put("approve_id", bill.getLong("originalid"));
|
|
|
|
+ //分录单位 0-上半天 1-下半天 2-全天 3-时分
|
|
|
|
+ String startMethod = entry.getString("entrystartmethod");
|
|
|
|
+ String endMethod = entry.getString("entryendmethod");
|
|
|
|
+ //duration_unit 单位 day-全天 halfDay-半天 hour-时分
|
|
|
|
+ String unitStr = "";
|
|
|
|
+ switch (startMethod) {
|
|
|
|
+ case "0":
|
|
|
|
+ case "1":
|
|
|
|
+ unitStr = "halfDay";
|
|
|
|
+ break;
|
|
|
|
+ case "2":
|
|
|
|
+ unitStr = "day";
|
|
|
|
+ break;
|
|
|
|
+ case "3":
|
|
|
|
+ unitStr = "hour";
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ param.put("duration_unit", unitStr);
|
|
|
|
+ //根据单位处理时长
|
|
|
|
+ param.put("from_time", dealHalfDaySdf(entry.getDate("entrystartdate"), startMethod));
|
|
|
|
+ param.put("to_time", dealHalfDaySdf(entry.getDate("entryenddate"), endMethod));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 构建补卡入参
|
|
|
|
+ * @param param
|
|
|
|
+ * @param bill
|
|
|
|
+ * @param entityNumber
|
|
|
|
+ * @param isChange
|
|
|
|
+ * @param openMap
|
|
|
|
+ */
|
|
|
|
+ private static void buildFillContent(JSONObject param, DynamicObject bill, String entityNumber, boolean isChange, Map openMap) throws IOException {
|
|
|
|
+ log.info("构建补卡入参报文 start ");
|
|
|
|
+ getBaseUrl(isChange, true);
|
|
|
|
+ //补卡没有变更,isChange=true直接判断为失效
|
|
|
|
+ if(isChange) {
|
|
|
|
+ param.put("approve_id", bill.getLong("originalid"));
|
|
|
|
+ param.put("userid", openMap.get(bill.getString("attfile.personnum")));
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ param.put("jump_url", "https://open.dingtalk.com/");
|
|
|
|
+ param.put("tag_name", "补卡");
|
|
|
|
+ param.put("approve_id", bill.getLong("originalid"));
|
|
|
|
+ param.put("userid", openMap.get(bill.getString("attfile.personnum")));
|
|
|
|
+ DynamicObject entry = bill.getDynamicObjectCollection("entryentity").get(0);
|
|
|
|
+ //work_date
|
|
|
|
+ Date signDate = entry.getDate("signdate");
|
|
|
|
+ param.put("work_date", WTCDateUtils.date2Str(signDate, "yyyy-MM-dd"));
|
|
|
|
+ long time = entry.getLong("suppleworktime");
|
|
|
|
+ LocalTime localTime = WTCDateUtils.secondToTime(time);
|
|
|
|
+ LocalDateTime localDateTime = LocalDateTime.of(WTCDateUtils.toLocalDate(signDate), localTime);
|
|
|
|
+ String dateTimeStr = localDateTimeToStr(localDateTime, "yyyy-MM-dd HH:mm:ss");
|
|
|
|
+ param.put("punch_check_time",dateTimeStr);
|
|
|
|
+ param.put("user_check_time",dateTimeStr);
|
|
|
|
+ //punch_id 钉钉排班ID 另外调接口获取
|
|
|
|
+ param.put("punch_id",getDingTalkShiftId(bill, openMap));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 返回Url
|
|
|
|
+ * @param isCancel
|
|
|
|
+ * @param isFill
|
|
|
|
+ */
|
|
|
|
+ public static void getBaseUrl (boolean isCancel, boolean isFill) {
|
|
|
|
+ if(isCancel) {
|
|
|
|
+ baseUrl = DingTalkSyncUtil.getParamValue(cancelParamKey);
|
|
|
|
+ }
|
|
|
|
+ else if(isFill) {
|
|
|
|
+ baseUrl = DingTalkSyncUtil.getParamValue(fillParamKey);
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ baseUrl = DingTalkSyncUtil.getParamValue(finishParamKey);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 获取钉钉班次ID
|
|
|
|
+ * {
|
|
|
|
+ * "errcode": 0,
|
|
|
|
+ * "errmsg": "ok",
|
|
|
|
+ * "result": [
|
|
|
|
+ * {
|
|
|
|
+ * "check_type": "OnDuty",
|
|
|
|
+ * "group_id": 1250482934,
|
|
|
|
+ * "id": 814088651114,
|
|
|
|
+ * "is_rest": "N",
|
|
|
|
+ * "overtime_setting_id": 696580239,
|
|
|
|
+ * "plan_check_time": "2025-06-03 08:30:00",
|
|
|
|
+ * "shift_id": 1386234138,
|
|
|
|
+ * "shift_version": 1317657354,
|
|
|
|
+ * "userid": "13697915049",
|
|
|
|
+ * "work_date": "2025-06-03 00:00:00"
|
|
|
|
+ * },
|
|
|
|
+ * {
|
|
|
|
+ * "check_type": "OffDuty",
|
|
|
|
+ * "group_id": 1250482934,
|
|
|
|
+ * "id": 814088651115,
|
|
|
|
+ * "is_rest": "N",
|
|
|
|
+ * "overtime_setting_id": 696580239,
|
|
|
|
+ * "plan_check_time": "2025-06-03 17:30:00",
|
|
|
|
+ * "shift_id": 1386234138,
|
|
|
|
+ * "shift_version": 1317657354,
|
|
|
|
+ * "userid": "13697915049",
|
|
|
|
+ * "work_date": "2025-06-03 00:00:00"
|
|
|
|
+ * }
|
|
|
|
+ * ],
|
|
|
|
+ * "success": true,
|
|
|
|
+ * "request_id": "16kk36k9q33hf"
|
|
|
|
+ * }
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public static Long getDingTalkShiftId (DynamicObject bill, Map openMap) throws IOException {
|
|
|
|
+ DynamicObject entry = bill.getDynamicObjectCollection("entryentity").get(0);
|
|
|
|
+ Date shiftDate = entry.getDate("signdate");
|
|
|
|
+ Long shiftDateUnix = dateToUnix(shiftDate);
|
|
|
|
+ JSONObject ob = new JSONObject(true);
|
|
|
|
+ ob.put("from_date_time", shiftDateUnix);
|
|
|
|
+ ob.put("to_date_time", shiftDateUnix);
|
|
|
|
+ ob.put("op_user_id", "000082");
|
|
|
|
+ ob.put("userids", openMap.get(bill.getString("attfile.personnum")));
|
|
|
|
+ String url = DingTalkSyncUtil.getParamValue(shiftParamKey);
|
|
|
|
+ url = getUrl(url, accessToken);
|
|
|
|
+ JSONObject response = doPost(url, ob);
|
|
|
|
+ log.info("调用排班返回:{}", response);
|
|
|
|
+ DingTalkSyncUtil.createLog("A", bill.getString("billno"), ob.toJSONString(), response.toJSONString(), DingTalkSyncUtil.SyncShift);
|
|
|
|
+ if(ObjectUtils.isEmpty(response)) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ JSONArray shiftArr = response.getJSONArray("result");
|
|
|
|
+ if(ObjectUtils.isEmpty(shiftArr)) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ //根据进出卡标识获取对应标识
|
|
|
|
+ String tag = entry.getString("accesstag");
|
|
|
|
+ String checkType = "on".equals(tag) ? "OnDuty" : "OffDuty";
|
|
|
|
+
|
|
|
|
+ for(int i = 0 ; i < shiftArr.size(); i++) {
|
|
|
|
+ JSONObject shift = shiftArr.getJSONObject(i);
|
|
|
|
+ if(!checkType.equals(shift.getString("check_type")))
|
|
|
|
+ continue;
|
|
|
|
+ return shift.getLong("id");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ public static String getUrl (String url, String accessToken) {
|
|
|
|
+ return url + "?access_token=" + accessToken;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 根据单位处理时间格式
|
|
|
|
+ * method == 0 || method == 1时:返回yyyy-MM-dd AM 或者 yyyy-MM-dd PM
|
|
|
|
+ * method == 2 返回yyyy-MM-dd
|
|
|
|
+ * method == 3 返回yyyy-MM-dd HH:mm:ss
|
|
|
|
+ * @param date
|
|
|
|
+ * @param method
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public static String dealHalfDaySdf (Date date, String method) {
|
|
|
|
+ SimpleDateFormat sdf;
|
|
|
|
+ switch(method) {
|
|
|
|
+ case "0":
|
|
|
|
+ sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
|
+ return sdf.format(date) + " AM";
|
|
|
|
+ case "1":
|
|
|
|
+ sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
|
+ return sdf.format(date) + " PM";
|
|
|
|
+ case "2":
|
|
|
|
+ sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
|
+ return sdf.format(date);
|
|
|
|
+ case "3":
|
|
|
|
+ sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
|
|
+ return sdf.format(date);
|
|
|
|
+ default :
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * localDateTime转String
|
|
|
|
+ * @param localDateTime
|
|
|
|
+ * @param pattern
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public static String localDateTimeToStr (LocalDateTime localDateTime, String pattern) {
|
|
|
|
+ DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern(pattern);
|
|
|
|
+ return localDateTime.format(dateFormat);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * date转Unix
|
|
|
|
+ * @param date
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public static Long dateToUnix (Date date) {
|
|
|
|
+ return date.getTime();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+}
|