|
@@ -1,13 +1,19 @@
|
|
|
package com.malk.fengkaili.service.impl;
|
|
|
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
import com.malk.fengkaili.repository.dao.FKLDdContactDao;
|
|
|
import com.malk.fengkaili.repository.entity.FKLDdContactPo;
|
|
|
import com.malk.fengkaili.service.FKLService;
|
|
|
import com.malk.service.dingtalk.DDClient;
|
|
|
+import com.malk.service.dingtalk.DDClient_Attendance;
|
|
|
import com.malk.service.dingtalk.DDClient_Contacts;
|
|
|
+import com.malk.service.dingtalk.DDService;
|
|
|
+import com.malk.utils.UtilDateTime;
|
|
|
import com.malk.utils.UtilList;
|
|
|
import com.malk.utils.UtilMap;
|
|
|
+import com.malk.utils.UtilNumber;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang3.ObjectUtils;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.data.domain.Page;
|
|
@@ -18,15 +24,17 @@ import org.springframework.data.jpa.domain.Specification;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
import javax.persistence.criteria.Predicate;
|
|
|
-import java.util.ArrayList;
|
|
|
-import java.util.List;
|
|
|
-import java.util.Map;
|
|
|
+import java.time.Duration;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.util.*;
|
|
|
+import java.util.concurrent.atomic.AtomicReference;
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
@Service
|
|
|
@Slf4j
|
|
|
public class FKLImplService implements FKLService {
|
|
|
|
|
|
-
|
|
|
@Autowired
|
|
|
private DDClient ddClient;
|
|
|
|
|
@@ -36,6 +44,12 @@ public class FKLImplService implements FKLService {
|
|
|
@Autowired
|
|
|
private FKLDdContactDao fklDdContactDao;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private DDClient_Attendance ddClient_attendance;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private DDService ddService;
|
|
|
+
|
|
|
/**
|
|
|
* 同步用户信息
|
|
|
*/
|
|
@@ -43,23 +57,39 @@ public class FKLImplService implements FKLService {
|
|
|
public void syncUserInfo() {
|
|
|
// 匹配部门信息, 全量
|
|
|
ddClient_contacts.getDepartmentId_all(ddClient.getAccessToken(), true).forEach(deptId -> {
|
|
|
- String deptName = ddClient_contacts.getDepartmentInfo(ddClient.getAccessToken(), deptId).get("name").toString();
|
|
|
- for (String userId : ddClient_contacts.listDepartmentUserId(ddClient.getAccessToken(), deptId)) {
|
|
|
- if (fklDdContactDao.existsByUserId(userId)) {
|
|
|
- continue;
|
|
|
+ // String deptName = ddClient_contacts.getDepartmentInfo(ddClient.getAccessToken(), deptId).get("name").toString();
|
|
|
+ List<String> userIds = ddClient_contacts.listDepartmentUserId(ddClient.getAccessToken(), deptId);
|
|
|
+ if (userIds.size() > 0) {
|
|
|
+ // 获取部门层级拼接
|
|
|
+ String deptName = ddService.getUserDepartmentHierarchyJoin(ddClient.getAccessToken(), userIds.get(0), "-");
|
|
|
+ for (String userId : userIds) {
|
|
|
+ // 牧语
|
|
|
+ if ("0953580166811961653".equals(userId) || fklDdContactDao.existsByUserId(userId)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ Map userinfo = ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), userId);
|
|
|
+ // 员工信息表, 落库
|
|
|
+ fklDdContactDao.save(FKLDdContactPo.builder()
|
|
|
+ .userId(userId)
|
|
|
+ .name(UtilMap.getString(userinfo, "name"))
|
|
|
+ .jobNumber(UtilMap.getString(userinfo, "job_number"))
|
|
|
+ .deptId(deptId)
|
|
|
+ .deptName(deptName)
|
|
|
+ .mobile(UtilMap.getString(userinfo, "mobile"))
|
|
|
+ .hiredDate(userinfo.containsKey("hired_date") ? new Date(UtilMap.getLong(userinfo, "hired_date")) : null)
|
|
|
+ .remark(UtilMap.getString(userinfo, "remark")) // 无需打卡 标记
|
|
|
+ .build());
|
|
|
+ log.info("同步#入职人员, {}", userinfo);
|
|
|
}
|
|
|
- Map userinfo = ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), userId);
|
|
|
- // 员工信息表, 落库
|
|
|
- fklDdContactDao.save(FKLDdContactPo.builder()
|
|
|
- .userId(userId)
|
|
|
- .name(UtilMap.getString(userinfo, "name"))
|
|
|
- .jobNumber(UtilMap.getString(userinfo, "job_number"))
|
|
|
- .deptId(deptId)
|
|
|
- .deptName(deptName)
|
|
|
- .mobile(UtilMap.getString(userinfo, "mobile"))
|
|
|
- .build());
|
|
|
}
|
|
|
});
|
|
|
+
|
|
|
+ // 同步离职人员, 标记离职日期
|
|
|
+ Date start = UtilDateTime.convertToDateFromLocalDateTime(UtilDateTime.firstDayOfLastMonth(LocalDateTime.now()));
|
|
|
+ ddClient_contacts.getLeaveEmployeeRecords(ddClient.getAccessToken(), start, null).forEach(item -> {
|
|
|
+ log.info("同步#离职人员, {}", item);
|
|
|
+ fklDdContactDao.updateLeaveDate(item.get("userId"), UtilDateTime.parse(item.get("leaveTime"), "yyyy-MM-dd'T'HH:mm:ss"));
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -69,7 +99,7 @@ public class FKLImplService implements FKLService {
|
|
|
public Page<FKLDdContactPo> queryUserInfos(int page, int size, String name, List<Long> deptIds) {
|
|
|
|
|
|
// 分页 & 排序
|
|
|
- Sort sort = Sort.by(Sort.Direction.DESC, "deptId");
|
|
|
+ Sort sort = Sort.by(Sort.Direction.ASC, "deptName");
|
|
|
Pageable pageable = PageRequest.of(page - 1, size, sort);
|
|
|
|
|
|
// 查询条件: 姓名, 所属部门
|
|
@@ -86,4 +116,446 @@ public class FKLImplService implements FKLService {
|
|
|
// 无数据时返回空列表
|
|
|
return fklDdContactDao.findAll(specification, pageable);
|
|
|
}
|
|
|
+
|
|
|
+ /// 累计月度汇总数字
|
|
|
+ private Object _reduceAttendance(Map column, String name, String keyList) {
|
|
|
+ Object value;
|
|
|
+ List<Map> vals = (List<Map>) column.get(keyList);
|
|
|
+ // 异常信息, 保留备注
|
|
|
+ if (name.equals("考勤结果")) {
|
|
|
+ List<String> tmps = new ArrayList<>(); // 同行出差会重复, 考勤结果要过滤
|
|
|
+ vals.stream().forEach(item -> {
|
|
|
+ // prd 异常补录当前日期
|
|
|
+ String content = UtilMap.getString(item, "value");
|
|
|
+ String svalue = content;
|
|
|
+ if (!content.contains("-")) {
|
|
|
+ content += UtilMap.getString(item, "date").split(" ")[0];
|
|
|
+ }
|
|
|
+ content = content.replace("未打卡,", "").replace("正常,", "").replace("休息并打卡,", "").replace("休息,", "");
|
|
|
+ // 休息有外出/出差 , 正常带其他状态情况 || 超过90未打卡静默用户 || 被添加为协同人后, 钉钉也会记录一条出差
|
|
|
+ if (content.contains("出差")) {
|
|
|
+ // 兼容出差中还有其他考勤结果, 以及还存在跨天的情况下
|
|
|
+ List<String> arr = new ArrayList<>();
|
|
|
+ for (String t : content.split(",")) {
|
|
|
+ if (!arr.contains(t) && !tmps.stream().filter(s -> s.contains(t)).findAny().isPresent()) {
|
|
|
+ arr.add(t);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (arr.size() == 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ content = String.join(",", arr);
|
|
|
+ }
|
|
|
+ boolean isFuture = UtilDateTime.parseLocalDateTime(UtilMap.getString(item, "date")).isAfter(LocalDateTime.now());
|
|
|
+ if (!isFuture && StringUtils.isNotBlank(svalue) && !tmps.contains(content) && !content.contains("休息") && !svalue.equals("正常") && !svalue.equals("未打卡")) {
|
|
|
+ tmps.add(content);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ value = String.join("; ", tmps);
|
|
|
+ } else {
|
|
|
+ value = vals.stream().map(item -> UtilMap.getFloat(item, "value")).reduce(0.f, (a, b) -> {
|
|
|
+ // ddExt: 出差默认是可重复提交, 且若被添加为协同人, 也会多累计一天出差 [但工作时长是正常]. 可开启不允许重复提交, 同样的同行人会冲突
|
|
|
+ if (name.equals("出差时长") && b > 1.0f) {
|
|
|
+ b = 1.0f;
|
|
|
+ }
|
|
|
+ return a + b;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /// 缓存考勤自定义列
|
|
|
+ private List<Map> columns;
|
|
|
+
|
|
|
+ List<Map> getColumns() {
|
|
|
+ if (UtilList.isEmpty(columns)) {
|
|
|
+ columns = ddClient_attendance.getAttColumns(ddClient.getAccessToken());
|
|
|
+ }
|
|
|
+ return columns;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 考勤数据统计
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public List<Map> queryAttendanceList(String start, String end, List<FKLDdContactPo> userInfos, List<String> days) {
|
|
|
+
|
|
|
+ // 考勤列, 假期信息定义
|
|
|
+ List<String> columnNames = Arrays.asList("旷工天数", "出勤天数", "工作时长", "考勤结果", "出差时长", "迟到次数", "早退次数", "下班缺卡次数", "上班缺卡次数", "外出时长", "休息日加班", "工作日加班", "节假日加班", "严重迟到次数", "应出勤天数");
|
|
|
+ List<Map> columns = getColumns();
|
|
|
+ Map columnIds = new HashMap();
|
|
|
+ // 假期单独返回, 钉钉产品规则
|
|
|
+ List<String> leaveNames = columns.stream().filter(column -> {
|
|
|
+ // 列类型储存id映射名称为map, 考勤数据返回仅保留列id
|
|
|
+ if (columnNames.contains(column.get("name"))) {
|
|
|
+ columnIds.put(column.get("id").toString(), column.get("name"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return column.get("alias").equals("leave_");
|
|
|
+ }
|
|
|
+ ).map(column -> String.valueOf(column.get("name"))).collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 考勤汇总数据
|
|
|
+ List<Map> attendanceInfos = new ArrayList<>();
|
|
|
+ List<String> queryIds = new ArrayList<>(columnIds.keySet()); // 考勤列定义
|
|
|
+ userInfos.forEach(po -> {
|
|
|
+ Map attendanceInfo = UtilMap.map("员工ID, 员工姓名, 员工工号, 所属部门, 考勤状态", po.getUserId(), po.getName(), po.getJobNumber(), po.getDeptName(), po.getRemark());
|
|
|
+ // 累计月度汇总
|
|
|
+ ddClient_attendance.getAttColumnVal(ddClient.getAccessToken(), po.getUserId(), queryIds, start, end).forEach(column -> {
|
|
|
+ String id = ((Map) column.get("column_vo")).get("id").toString();
|
|
|
+ String name = String.valueOf(columnIds.get(id)); // 接口仅返回列id, 通过map映射
|
|
|
+ attendanceInfo.put(name, _reduceAttendance(column, name, "column_vals"));
|
|
|
+ // prd [sheet2]每天考勤结果统计
|
|
|
+ if (!Objects.isNull(days) && name.equals("考勤结果")) {
|
|
|
+ List<Map> vals = (List<Map>) column.get("column_vals");
|
|
|
+ int index = 0;
|
|
|
+ for (Map<String, String> val : vals) {
|
|
|
+ index++;
|
|
|
+ String date = val.get("date").replace(" 00:00:00", "").replace(LocalDate.now().getYear() + "-", "");
|
|
|
+ String result = val.get("value").replace("休息并打卡,", "").replace("休息,", ""); // 休息有外出/出差;
|
|
|
+ log.info("人员明细, {} - {}, {}", date, po.getName(), val.get("value"));
|
|
|
+ String day_1 = "zc", day_2 = "zc", type = "zc"; // 异常类型
|
|
|
+ if (result.contains("休息") || result.contains("加班") || (val.get("value").contains("休息,") && (!result.contains("出差") && !result.contains("婚假") && !result.contains("产假")))) {
|
|
|
+ type = "公假"; // 包含休息, 休息加班打卡, 忽略跨休息日连续请假情况, prd 钉钉后台配置: 产假, 婚假按自然日
|
|
|
+ day_1 = type;
|
|
|
+ day_2 = type;
|
|
|
+ } else if (StringUtils.isBlank(result)) {
|
|
|
+ type = "/"; // 新入职
|
|
|
+ day_1 = "/";
|
|
|
+ day_2 = "/";
|
|
|
+ } else if (result.equals("正常") || (result.split(",").length == 2 && result.contains("外勤") && result.contains("补卡")) || result.equals("下班外勤") || result.equals("上班外勤") || result.equals("上班外勤,下班外勤")) {
|
|
|
+ // 包含补卡, 一次外勤补卡, 外勤考勤情况 [调休会被标识为考勤正常]
|
|
|
+ type = "zc";
|
|
|
+ day_1 = type;
|
|
|
+ day_2 = type;
|
|
|
+ } else if (result.contains("产假") || result.contains("陪产假") || result.contains("婚假") || result.contains("丧假")) {
|
|
|
+ type = result.split("假")[0] + "假"; // 按天请假
|
|
|
+ day_1 = type;
|
|
|
+ day_2 = type;
|
|
|
+ } else if (result.contains("旷工") || result.equals("未打卡")) {
|
|
|
+ type = "旷工"; // 兼容异常情况
|
|
|
+ day_1 = type;
|
|
|
+ day_2 = type;
|
|
|
+ } else if (result.contains("缺卡") && !result.contains("到")) {
|
|
|
+ // prd 8点上班, 8点后请假或外出都是缺卡记录
|
|
|
+ if (result.equals("上班缺卡")) {
|
|
|
+ type = "缺卡";
|
|
|
+ day_1 = type;
|
|
|
+ }
|
|
|
+ if (result.equals("下班缺卡")) {
|
|
|
+ // prd 离职操作是直接删除, 会有一次打卡, 符合标记为zc
|
|
|
+ if (ObjectUtil.isNotNull(po.getLeaveDate()) && date.equals(UtilDateTime.format(po.getLeaveDate(), "MM-dd"))) {
|
|
|
+ type = type.length() > 0 ? type : "zc";
|
|
|
+ day_2 = "zc";
|
|
|
+ } else {
|
|
|
+ type = "缺卡";
|
|
|
+ day_2 = type;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (result.split(",").length <= 2 && (result.contains("迟到") || result.contains("早退"))) {
|
|
|
+ // 兼容早退和迟到情况下, 还存在请假情况
|
|
|
+ if (result.contains("迟到") && !result.contains("补卡申请")) {
|
|
|
+ type = "迟到"; // 迟到状态标记
|
|
|
+ float exception_duration = Float.valueOf((result.split(",")[0].split("分钟")[0].replace("上班迟到", "")));
|
|
|
+ if (exception_duration >= 180f) {
|
|
|
+ // 设置早退、迟到3小时以上则在表中记录为早退、迟到. 保留迟到次数记录
|
|
|
+ day_1 = "迟到";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (result.contains("早退") && !result.contains("补卡申请")) {
|
|
|
+ type += type.length() > 0 ? " 早退" : "早退"; // 早退状态标记
|
|
|
+ float exception_duration = Float.valueOf((result.split(",")[result.split(",").length - 1].split("分钟")[0].replace("下班早退", "")));
|
|
|
+ if (exception_duration >= 180f) {
|
|
|
+ // 设置早退、迟到3小时以上则在表中记录为早退、迟到. 保留迟到次数记录
|
|
|
+ day_2 = "早退";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ type = "";
|
|
|
+ day_1 = "";
|
|
|
+ day_2 = "";
|
|
|
+ // 请假 & 出差
|
|
|
+ for (String status : result.split(",")) {
|
|
|
+ /// 过滤异常情况 & 未打卡判定为status, 非result
|
|
|
+ if (status.contains("补卡申请") || status.contains("正常") || status.equals("未打卡")) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (status.contains("缺卡") || status.equals("未打卡") || status.contains("迟到") || status.contains("早退")) {
|
|
|
+ if (status.equals("上班缺卡")) {
|
|
|
+ type = "缺卡";
|
|
|
+ day_1 = "缺卡";
|
|
|
+ }
|
|
|
+ if (status.equals("下班缺卡")) {
|
|
|
+ type += "缺卡";
|
|
|
+ day_2 = "缺卡";
|
|
|
+ }
|
|
|
+ // 兼容早退和迟到情况下, 还存在请假情况
|
|
|
+ if (status.contains("迟到") || status.contains("早退")) {
|
|
|
+ if (status.contains("迟到")) {
|
|
|
+ type = "迟到"; // 迟到状态标记
|
|
|
+ float exception_duration = Float.valueOf((status.split(",")[0].split("分钟")[0].replace("上班迟到", "")));
|
|
|
+ if (exception_duration >= 180f) {
|
|
|
+ // 设置早退、迟到3小时以上则在表中记录为早退、迟到. 保留迟到次数记录
|
|
|
+ day_1 = "迟到";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (status.contains("早退")) {
|
|
|
+ type += type.length() > 0 ? " 早退" : "早退"; // 早退状态标记
|
|
|
+ float exception_duration = Float.valueOf((status.split(",")[0].split("分钟")[0].replace("下班早退", "")));
|
|
|
+ if (exception_duration >= 180f) {
|
|
|
+ // 设置早退、迟到3小时以上则在表中记录为早退、迟到. 保留迟到次数记录
|
|
|
+ day_2 = "早提";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ /// 请假数据处理 [小时情况]
|
|
|
+ String tmp = status.contains("调休") ? "调休" : status.split("假")[0] + "假"; // 异常类型
|
|
|
+ if (status.contains("外出") || Arrays.asList("调休", "哺乳假", "事假").contains(tmp)) {
|
|
|
+ if (result.contains("外出")) {
|
|
|
+ tmp = "外出";
|
|
|
+ }
|
|
|
+ // 外出, 调休, 事假, 哺乳假: 兼容9点申请, 排班是8点情况, 不记录缺卡
|
|
|
+ if (day_1.equals("缺卡") && result.contains("09:00")) {
|
|
|
+ day_1 = "";
|
|
|
+ }
|
|
|
+ // prd 请假3小时以内标记为zc, 按照小时请假 [调休, 哺乳假, 事假]
|
|
|
+ String[] arr = status.split(" ");
|
|
|
+ float hour = Float.valueOf((arr[arr.length - 1].replace("小时", "")));
|
|
|
+ if (hour < 3.0f) { // <3 同时9点申请标识zc, 避免不能统计外出情况
|
|
|
+ continue;
|
|
|
+ } else {
|
|
|
+ // prd 请假3小时以内标记为zc, 区分上午与下午, 午休从12-13分割
|
|
|
+ String sStart = status.split(" ")[1].split("到")[0].replace(":", "");
|
|
|
+ type = type.length() > 0 && !tmp.equals(type) ? type + " " + tmp : tmp; // 兼容一天提交两次外出情况
|
|
|
+ if (Integer.valueOf(sStart) >= 1200) {
|
|
|
+ day_2 = tmp;
|
|
|
+ } else {
|
|
|
+ String sEnd = status.split(" ")[2].replace(":", "");
|
|
|
+ if (Integer.valueOf(sStart) < 800) {
|
|
|
+ sStart = "0800";
|
|
|
+ }
|
|
|
+ float hourZao = Duration.between(UtilDateTime.parseLocal(sStart, "HHmm"), UtilDateTime.parseLocal("1200", "HHmm")).toMinutes() / 60f;
|
|
|
+ if (hourZao >= 3.0f) {
|
|
|
+ day_1 += day_1.length() > 0 ? " " + tmp : tmp;
|
|
|
+ }
|
|
|
+ if (Integer.valueOf(sEnd) > 1700) {
|
|
|
+ sEnd = "1700";
|
|
|
+ }
|
|
|
+ float hourWan = Duration.between(UtilDateTime.parseLocal("1300", "HHmm"), UtilDateTime.parseLocal(sEnd, "HHmm")).toMinutes() / 60f;
|
|
|
+ if (hourWan > 3.0f) {
|
|
|
+ day_2 += day_2.length() > 0 ? " " + tmp : tmp;
|
|
|
+ }
|
|
|
+ // 外出兼容9点申请, 排班是8点情况
|
|
|
+ if (result.contains("外出") && Integer.valueOf(sStart) <= 900 && Integer.valueOf(sEnd) >= 1700) {
|
|
|
+ type = "外出";
|
|
|
+ day_1 = type;
|
|
|
+ day_2 = type;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (status.contains("出差")) {
|
|
|
+ // 出差兼容, 半天, 外出, 请假等情况
|
|
|
+ type += type.length() > 0 ? (type.contains("出差") ? "" : " 出差") : "出差";
|
|
|
+ // 半天出差场景以及被添加为协同人后, 钉钉也会记录一条出差; 均循环进行处理, 即时出差覆盖即当天多次出差也可兼容
|
|
|
+ int sStart = Integer.valueOf(status.split(" ")[1].split("到")[0].replace(":", ""));
|
|
|
+ int sEnd = Integer.valueOf(status.split(" ")[2].replace(":", ""));
|
|
|
+ if (val.get("value").contains("休息")) {
|
|
|
+ day_1 = day_1.equals("") ? "公假" : day_1;
|
|
|
+ day_2 = day_2.equals("") ? "公假" : day_2;
|
|
|
+ }
|
|
|
+ if (sStart >= 1200 && date.equals(status.split(" ")[0].replace("出差", ""))) {
|
|
|
+ // 跨天: 日期相等, 且下午时间
|
|
|
+ day_2 = "出差";
|
|
|
+ } else if (sEnd <= 1300 && date.equals(status.split(" ")[1].split("到")[1])) {
|
|
|
+ // 跨天: 日期相等, 且上午时间
|
|
|
+ day_1 = "出差";
|
|
|
+ } else {
|
|
|
+ day_1 = "出差";
|
|
|
+ day_2 = "出差";
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ /// 请假数据处理 [半天情况]
|
|
|
+ String[] arr = status.split(" ");
|
|
|
+ int sstart = Integer.valueOf(status.split(" ")[1].split("到")[0].replace(":", ""));
|
|
|
+ float day = Float.valueOf((arr[arr.length - 1].replace("天", "")));
|
|
|
+ type = tmp;
|
|
|
+ if (day == 0.5) {
|
|
|
+ if (sstart >= 1200) {
|
|
|
+ day_2 = type;
|
|
|
+ } else {
|
|
|
+ day_1 = type;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ day_1 = type;
|
|
|
+ day_2 = type;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 日期动态列头
|
|
|
+ if (!days.contains(date)) {
|
|
|
+ days.add(date);
|
|
|
+ }
|
|
|
+ attendanceInfo.put(date, type.length() == 0 ? "zc" : type);
|
|
|
+ attendanceInfo.put("day" + index + "_1", day_1.length() == 0 ? "zc" : day_1);
|
|
|
+ attendanceInfo.put("day" + index + "_2", day_2.length() == 0 ? "zc" : day_2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ // 累计假期数据
|
|
|
+ float leave_duration = 0f;
|
|
|
+ float leave_all = 0f;
|
|
|
+ for (Map column : ddClient_attendance.getLeaveTimeByNames(ddClient.getAccessToken(), po.getUserId(), leaveNames, start, end)) {
|
|
|
+ String name = ((Map) column.get("columnvo")).get("name").toString(); // 接口返回列名称
|
|
|
+ float value = (Float) _reduceAttendance(column, name, "columnvals");
|
|
|
+ // prd 法定假期[除病假、事件、调休、产假外]请假时长 [调休, 事假, 哺乳假为小时, 其余半天为最小单位]
|
|
|
+ if (!Arrays.asList("病假", "事假", "调休", "产假").contains(name)) {
|
|
|
+ if (name.equals("哺乳假")) {
|
|
|
+ leave_duration += value * 60f;
|
|
|
+ } else {
|
|
|
+ leave_duration += value * 60f * 8f;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (Arrays.asList("调休", "事假", "哺乳假").contains(name)) {
|
|
|
+ leave_all += value / 8f;
|
|
|
+ } else {
|
|
|
+ leave_all += value;
|
|
|
+ }
|
|
|
+ attendanceInfo.put(name, value);
|
|
|
+ }
|
|
|
+ // 数据处理, 请假折算天
|
|
|
+ float overTime = UtilMap.getFloat(attendanceInfo, "节假日加班") + UtilMap.getFloat(attendanceInfo, "节假日加班") + UtilMap.getFloat(attendanceInfo, "节假日加班");
|
|
|
+ attendanceInfo.put("加班总时长", UtilNumber.formatPrecisionValue(overTime));
|
|
|
+ attendanceInfo.put("事假天", UtilNumber.formatPrecisionValue(UtilMap.getFloat(attendanceInfo, "事假") / 8f));
|
|
|
+ attendanceInfo.put("哺乳假天", UtilNumber.formatPrecisionValue(UtilMap.getFloat(attendanceInfo, "哺乳假") / 8f));
|
|
|
+ attendanceInfo.put("调休天", UtilNumber.formatPrecisionValue(UtilMap.getFloat(attendanceInfo, "调休") / 8f));
|
|
|
+ // prd 标记人离职时间, 提示异常考勤
|
|
|
+ float exception_duration = 0f;
|
|
|
+ if (ObjectUtils.isNotEmpty(po.getHiredDate()) && UtilDateTime.beforeAndEqual(UtilDateTime.parseDateTime(start), po.getHiredDate()) && UtilDateTime.afterAndEqual(UtilDateTime.parseDateTime(end), po.getHiredDate())) {
|
|
|
+ Optional optional = Arrays.stream(attendanceInfo.get("考勤结果").toString().split("; ")).filter(item -> item.contains("迟到") && item.contains(UtilDateTime.formatDate(po.getHiredDate()))).findAny();
|
|
|
+ if (optional.isPresent()) {
|
|
|
+ exception_duration = Float.valueOf((optional.get().toString().split("分钟")[0].replace("上班迟到", "")));
|
|
|
+ attendanceInfo.put("迟到次数", UtilNumber.formatPrecisionValue(UtilMap.getFloat(attendanceInfo, "迟到次数") - 1));
|
|
|
+ }
|
|
|
+ attendanceInfo.put("考勤结果", "入职日期" + UtilDateTime.formatDate(po.getHiredDate()) + "; " + attendanceInfo.get("考勤结果"));
|
|
|
+ }
|
|
|
+ attendanceInfo.put("缺卡次数", UtilNumber.formatPrecisionValue(UtilMap.getFloat(attendanceInfo, "上班缺卡次数") + UtilMap.getFloat(attendanceInfo, "下班缺卡次数")));
|
|
|
+ if (ObjectUtils.isNotEmpty(po.getLeaveDate()) && UtilDateTime.parseDateTime(start).before(po.getLeaveDate()) && UtilDateTime.parseDateTime(end).after(po.getLeaveDate())) {
|
|
|
+ Optional optional = Arrays.stream(attendanceInfo.get("考勤结果").toString().split("; ")).filter(item -> item.equals("下班缺卡" + UtilDateTime.formatDate(po.getLeaveDate()))).findAny();
|
|
|
+ if (optional.isPresent()) {
|
|
|
+ exception_duration = 480f;
|
|
|
+ attendanceInfo.put("缺卡次数", UtilNumber.formatPrecisionValue(UtilMap.getFloat(attendanceInfo, "缺卡次数") - 1));
|
|
|
+ }
|
|
|
+ attendanceInfo.put("考勤结果", "离职日期" + UtilDateTime.formatDate(po.getLeaveDate()) + "; " + attendanceInfo.get("考勤结果"));
|
|
|
+ }
|
|
|
+ attendanceInfo.put("缺卡调整时长", UtilNumber.formatPrecisionValue(exception_duration));
|
|
|
+ // prd 总时长 = 工作时长 + 法定假期[除病假、事件、调休、产假外]请假时长 + 调休时长 - 加班时长【出差、外出不考勤但需要计入总工时,以申请时长为准,但外出可能为不足一天情况, 当天还有打卡: 目前先取系统默认】
|
|
|
+ float system_duration = UtilMap.getFloat(attendanceInfo, "工作时长");
|
|
|
+ float tiaoxiu_duration = UtilMap.getFloat(attendanceInfo, "调休") * 60f;
|
|
|
+ attendanceInfo.put("调休时长", UtilNumber.formatPrecisionValue(tiaoxiu_duration));
|
|
|
+ attendanceInfo.put("法定假调整时长", UtilNumber.formatPrecisionValue(leave_duration));
|
|
|
+ attendanceInfo.put("总时长", UtilNumber.formatPrecisionValue(system_duration + leave_duration + tiaoxiu_duration + exception_duration - overTime));
|
|
|
+ // ppExt 钉钉接口休息如出差半天系统也返回出勤天数1, 存在异常; 休息日加班也会记录为出勤, 考勤字段调整无效
|
|
|
+ attendanceInfo.put("出勤天数_prd", UtilNumber.formatPrecisionValue(UtilMap.getFloat(attendanceInfo, "出勤天数") - leave_all));
|
|
|
+ attendanceInfos.add(attendanceInfo);
|
|
|
+ });
|
|
|
+ // prd 26-25周期非自然月逻辑 [获取出现最多次作为法定应出勤天数] 考勤应出勤天数和班组 + 人员挂钩, ppExt 排班天数钉钉查询没有接口
|
|
|
+ float workMin = (Float) UtilList.maxFrequencyObject(attendanceInfos.stream().map(item -> UtilMap.getFloat(item, "出勤天数")).collect(Collectors.toList())) * 60 * 8;
|
|
|
+
|
|
|
+ // prd 数据处理
|
|
|
+ int order = 0;
|
|
|
+ for (Map attendance : attendanceInfos) {
|
|
|
+ if (attendance.containsKey("总时长") && workMin > 0) {
|
|
|
+ attendance.put("勤勉度系数", UtilNumber.formatPrecisionValue(UtilMap.getFloat(attendance, "总时长") / workMin));
|
|
|
+ }
|
|
|
+ order++;
|
|
|
+ attendance.put("序号", String.valueOf(order));
|
|
|
+ // prd 月度汇总表和月度明细表是否可实现部分无需打卡的员工
|
|
|
+ if ("无需打卡".equals(attendance.get("考勤状态"))) {
|
|
|
+ attendance.put("旷工天数", 0);
|
|
|
+ attendance.put("缺卡次数", 0);
|
|
|
+ attendance.put("上班缺卡次数", 0);
|
|
|
+ attendance.put("下班缺卡次数", 0);
|
|
|
+ attendance.put("迟到次数", 0);
|
|
|
+ attendance.put("早退次数", 0);
|
|
|
+ if (!Objects.isNull(days)) {
|
|
|
+ for (Object key : attendance.keySet()) {
|
|
|
+ String prop = String.valueOf(key);
|
|
|
+ if (prop.contains("_") || prop.contains("-")) {
|
|
|
+ String val = String.valueOf(attendance.get(prop)).replace("旷工", "").replace("缺卡", "").replace("迟到", "").replace("早退", "");
|
|
|
+ // 忽略考勤异常 | 考勤静默用户
|
|
|
+ if (StringUtils.isBlank(val) || val.equals("/")) {
|
|
|
+ attendance.put(prop, "zc");
|
|
|
+ } else {
|
|
|
+ attendance.put(prop, val);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ List<String> vals = new ArrayList<>();
|
|
|
+ for (String cont : String.valueOf(attendance.get("考勤结果")).split("; ")) {
|
|
|
+ if (!cont.contains("旷工") && !cont.contains("缺卡") && !cont.contains("迟到") && !cont.contains("早退")) {
|
|
|
+ vals.add(cont);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ attendance.put("考勤结果", String.join("; ", vals));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // prd 异常与假期统计对应状态数据, 出勤天数就是实际到公司工作的天数[zc状态], 兼容3小时需求逻辑
|
|
|
+ AtomicReference<Float> days_rest = new AtomicReference<>(0f);
|
|
|
+ AtomicReference<Float> days_tiaoxiu = new AtomicReference<>(0f);
|
|
|
+ AtomicReference<Float> days_shijia = new AtomicReference<>(0f);
|
|
|
+ AtomicReference<Float> days_burujia = new AtomicReference<>(0f);
|
|
|
+ AtomicReference<Float> days_waichu = new AtomicReference<>(0f);
|
|
|
+ AtomicReference<Float> days_chidao = new AtomicReference<>(0f);
|
|
|
+ AtomicReference<Float> days_zaotui = new AtomicReference<>(0f);
|
|
|
+ AtomicReference<Float> days_kuangong = new AtomicReference<>(0f);
|
|
|
+ attendance.put("出勤天数_prd", attendance.keySet().stream().reduce(0f, (acc, cur) -> {
|
|
|
+ if (cur.toString().contains("_")) {
|
|
|
+ // 外出标注为zc?
|
|
|
+ if (attendance.get(cur).equals("外出")) {
|
|
|
+// attendance.put(cur, "zc");
|
|
|
+ days_waichu.updateAndGet(v -> new Float((float) (v + 0.5)));
|
|
|
+ }
|
|
|
+ // 累计汇总天数
|
|
|
+ if (attendance.get(cur).equals("zc")) {
|
|
|
+ return Float.valueOf(String.valueOf(acc)) + 0.5;
|
|
|
+ } else if (attendance.get(cur).equals("公假")) {
|
|
|
+ days_rest.updateAndGet(v -> new Float((float) (v + 0.5)));
|
|
|
+ } else if (attendance.get(cur).equals("调休")) {
|
|
|
+ days_tiaoxiu.updateAndGet(v -> new Float((float) (v + 0.5)));
|
|
|
+ } else if (attendance.get(cur).equals("事假")) {
|
|
|
+ days_shijia.updateAndGet(v -> new Float((float) (v + 0.5)));
|
|
|
+ } else if (attendance.get(cur).equals("哺乳假")) {
|
|
|
+ days_burujia.updateAndGet(v -> new Float((float) (v + 0.5)));
|
|
|
+ } else if (attendance.get(cur).equals("迟到")) {
|
|
|
+ days_chidao.updateAndGet(v -> new Float((float) (v + 0.5)));
|
|
|
+ } else if (attendance.get(cur).equals("早退")) {
|
|
|
+ days_zaotui.updateAndGet(v -> new Float((float) (v + 0.5)));
|
|
|
+ } else if (attendance.get(cur).equals("旷工")) {
|
|
|
+ days_kuangong.updateAndGet(v -> new Float((float) (v + 0.5)));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return acc;
|
|
|
+ }));
|
|
|
+ attendance.put("公假天数_prd", days_rest.get());
|
|
|
+ attendance.put("调休天数_prd", days_tiaoxiu.get());
|
|
|
+ attendance.put("事假天数_prd", days_shijia.get());
|
|
|
+ attendance.put("哺乳假天数_prd", days_burujia.get());
|
|
|
+ attendance.put("外出天数_prd", days_waichu.get());
|
|
|
+ attendance.put("迟到次数_prd", days_chidao.get());
|
|
|
+ attendance.put("早退次数_prd", days_zaotui.get());
|
|
|
+ attendance.put("旷工天数_prd", days_kuangong.get());
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 记录月度明细日期, 进行排序 [接口返回已排序]
|
|
|
+// if (UtilList.isNotEmpty(days)) {
|
|
|
+// Collections.sort(days, Comparator.comparingLong(o -> Long.valueOf(o.replace("-", ""))));
|
|
|
+// }
|
|
|
+ return UtilList.ignoreListMapZero(attendanceInfos);
|
|
|
+ }
|
|
|
}
|