考勤逻辑重构代码
This commit is contained in:
parent
908dd61ff7
commit
c0332b2548
|
|
@ -0,0 +1,50 @@
|
|||
package com.bonus.system.att.service;
|
||||
|
||||
import com.bonus.system.api.domain.MapVo;
|
||||
import com.bonus.system.att.entity.OrgChangeBean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 考勤计算
|
||||
* @author fly
|
||||
*/
|
||||
public interface AttCalService {
|
||||
void getAttendanceData(String pushDate, int pushType);
|
||||
|
||||
/**
|
||||
* 插入模板数据
|
||||
* @param pushDate
|
||||
* @param pushType
|
||||
*/
|
||||
void insertAttTempData(String pushDate, int pushType);
|
||||
|
||||
/**
|
||||
* 待考勤人员列表
|
||||
* @param pushDate
|
||||
*/
|
||||
void insertAttDateHistory(String pushDate);
|
||||
|
||||
/**
|
||||
* 考勤数据应用(步骤一:考勤数据更新)
|
||||
* 建议一小时一次
|
||||
* @param pushDate 时间
|
||||
* @param pushType 1:自动推送(当天) 2:手动(历史)
|
||||
*/
|
||||
void updateAttData(String pushDate, int pushType);
|
||||
|
||||
/**
|
||||
* 考勤数据应用(步骤二:旷工状态更新)
|
||||
* 每天12点将当天未打卡考勤人员上班置为旷工
|
||||
* 每天晚上22点,将下班未打卡置为旷工
|
||||
* @param pushDate 时间
|
||||
* @param pushType 1:自动推送(当天) 2:手动(历史)
|
||||
*/
|
||||
void updateAbsenteeismData(String pushDate, int pushType);
|
||||
/**
|
||||
* 请假数据应用(步骤一:法假数据更新)
|
||||
* 法定节假日不会随时间变化,一天执行一次就可以了
|
||||
* @param pushDate 时间
|
||||
*/
|
||||
void updateLegalHolidayData(String pushDate);
|
||||
}
|
||||
|
|
@ -0,0 +1,798 @@
|
|||
package com.bonus.system.att.service;
|
||||
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.bonus.common.core.utils.DateTimeHelper;
|
||||
import com.bonus.common.core.utils.DateUtils;
|
||||
import com.bonus.common.core.utils.StringUtils;
|
||||
import com.bonus.common.security.utils.SecurityUtils;
|
||||
import com.bonus.system.api.domain.MapVo;
|
||||
import com.bonus.system.api.domain.SysUser;
|
||||
import com.bonus.system.att.dao.AttGroupDao;
|
||||
import com.bonus.system.att.dao.AttSourceDataDao;
|
||||
import com.bonus.system.att.dao.OrgChangeDao;
|
||||
import com.bonus.system.att.entity.*;
|
||||
import com.bonus.system.att.utils.AddressCoordinateFormatUtil;
|
||||
import com.bonus.system.att.utils.AttTimeUtil;
|
||||
import com.bonus.system.att.utils.WorkdayCalculator;
|
||||
import com.bonus.system.dept.dao.ProDeptRoleDao;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.beanutils.BeanUtils;
|
||||
import org.mybatis.spring.SqlSessionTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 组织架构-业务层
|
||||
*
|
||||
* @author zys
|
||||
*/
|
||||
@Slf4j
|
||||
@Service("AttCalService")
|
||||
public class AttCalServiceImpl implements AttCalService {
|
||||
|
||||
@Resource(name = "attSourceDataDao")
|
||||
private AttSourceDataDao attSourceDataDao;
|
||||
|
||||
@Resource(name = "attGroupDao")
|
||||
private AttGroupDao attGroupDao;
|
||||
|
||||
@Resource(name = "sqlSessionTemplate")
|
||||
private SqlSessionTemplate sqlSessionTemplate;
|
||||
|
||||
/**
|
||||
* 待考勤人员列表
|
||||
* 在模版数据之前生成
|
||||
*
|
||||
* @param pushDate 时间(可为空,为空则推历史60天内没生成的数据,不为空则推指定日期)
|
||||
*/
|
||||
@Override
|
||||
public void insertAttDateHistory(String pushDate) {
|
||||
List<String> dateList = new ArrayList<>();
|
||||
if (StringUtils.isEmpty(pushDate)) {
|
||||
String startDate = DateUtil.today();
|
||||
// 解析 startDate 字符串到 LocalDate 对象
|
||||
LocalDate date = LocalDate.parse(startDate, DateTimeFormatter.ISO_LOCAL_DATE);
|
||||
// 向前推n天
|
||||
int n = 60;
|
||||
LocalDate newDate = date.minusDays(n);
|
||||
// 如果需要将结果格式化为特定格式的字符串,可以使用以下代码
|
||||
String formattedNewDate = newDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
|
||||
//向前推1个月的时间检索,缺少哪一天推送哪一天
|
||||
List<String> dateListOne = attSourceDataDao.getAttSettingDate(startDate, n);
|
||||
dateList = AttTimeUtil.getStrDateListBetween(formattedNewDate, startDate);
|
||||
// 从 dateList 中移除所有在 dateListOne 中存在的元素
|
||||
dateListOne.removeIf(startDate::contains);
|
||||
dateList.removeIf(dateListOne::contains);
|
||||
} else {
|
||||
dateList.add(pushDate);
|
||||
}
|
||||
for (String currentDay : dateList) {
|
||||
List<AttGroupBean> attList = attSourceDataDao.getAttSettingHistoryDate(currentDay);
|
||||
if (!attList.isEmpty()) {
|
||||
attSourceDataDao.insertAttSettingHistoryData(attList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 考勤数据应用(步骤一:考勤数据更新)
|
||||
* 建议一小时一次
|
||||
*
|
||||
* @param pushDate 时间
|
||||
* @param pushType 1:自动推送(当天) 2:手动(历史)
|
||||
*/
|
||||
@Override
|
||||
public void updateAttData(String pushDate, int pushType) {
|
||||
//查出人员考勤记录
|
||||
AtomicReference<List<AttSourceDataBean>> sourceDataList = new AtomicReference<>(attSourceDataDao.getSourceAttData(pushDate, pushType));
|
||||
//查出考勤组
|
||||
List<AttGroupBean> groupList = getGroupData(pushDate);
|
||||
//
|
||||
groupList.forEach(c -> {
|
||||
List<AttSourceDataBean> list = Collections.emptyList();
|
||||
//考勤规则里面的attType 1 固定打卡 2 自由打卡
|
||||
if ("1".equals(c.getAttType())) {
|
||||
list = groupFixedData(sourceDataList.get().stream()
|
||||
.filter(a -> "1".equals(a.getGroupType()) &&
|
||||
a.getGroupId() == c.getGroupId())
|
||||
.collect(Collectors.toList()), c);
|
||||
} else if ("2".equals(c.getAttType())) {
|
||||
list = groupFreeData(sourceDataList.get().stream()
|
||||
.filter(a -> "2".equals(a.getGroupType()) &&
|
||||
a.getGroupId() == c.getGroupId())
|
||||
.collect(Collectors.toList()), c);
|
||||
}
|
||||
//修改考勤记录 att_data
|
||||
if (!list.isEmpty()) {
|
||||
if ("1".equals(c.getAttType())) {
|
||||
//固定打卡
|
||||
// 定义时间格式
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
// 移除移除 attType 为 2 且 attCurrentTime 在上午 5 点到 12 点之间的元素
|
||||
list.removeIf(record -> {
|
||||
String attType = record.getAttType();
|
||||
String attCurrentTime = record.getAttCurrentTime();
|
||||
if (!"".equals(attCurrentTime)) {
|
||||
if (attCurrentTime.contains(" ")) {
|
||||
attCurrentTime = attCurrentTime.split(" ")[0];
|
||||
}
|
||||
LocalDateTime time = LocalDateTime.parse(attCurrentTime, formatter);
|
||||
// 提取时间部分
|
||||
LocalTime localTime = time.toLocalTime();
|
||||
boolean condition1 = "2".equals(attType) && localTime.isAfter(LocalTime.of(5, 0)) && localTime.isBefore(LocalTime.of(12, 0));
|
||||
boolean condition2 = "1".equals(attType) && localTime.isAfter(LocalTime.of(12, 0)) && localTime.isBefore(LocalTime.of(23, 59));
|
||||
return condition1 || condition2;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// 插入剩余的元素
|
||||
attSourceDataDao.updateAttStatusData(list);
|
||||
} else if ("2".equals(c.getAttType())) {
|
||||
//自由打卡
|
||||
attSourceDataDao.updateAttStatusData(list);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 考勤数据应用(步骤二:旷工状态更新)
|
||||
* 每天12点将当天未打卡考勤人员上班置为旷工
|
||||
* 每天晚上22点,将下班未打卡置为旷工
|
||||
* @param pushDate 时间
|
||||
* @param pushType 1:自动推送(当天) 2:手动(历史)
|
||||
*/
|
||||
@Override
|
||||
public void updateAbsenteeismData(String pushDate, int pushType) {
|
||||
List<AttDataBean> list = new ArrayList<>();
|
||||
List<AttDataBean> listUpdate = new ArrayList<>();
|
||||
if(pushType == 1){
|
||||
//判断是否大于12点大于则更新所有attType = 1的未打卡状态为旷工
|
||||
//判断是否大于22点大于则更新所有未打卡状态为旷工
|
||||
boolean pm = AttTimeUtil.timeCheck(21, 59);
|
||||
if(pm){
|
||||
list = attSourceDataDao.getAttDataByStatus(pushDate,"");
|
||||
listUpdate = attSourceDataDao.getAttDataUpdateByStatus(pushDate,"");
|
||||
}else{
|
||||
boolean am = AttTimeUtil.timeCheck(11, 59);
|
||||
if(am){
|
||||
list = attSourceDataDao.getAttDataByStatus(pushDate,"1");
|
||||
listUpdate = attSourceDataDao.getAttDataUpdateByStatus(pushDate,"1");
|
||||
}
|
||||
}
|
||||
|
||||
}else if(pushType == 2){
|
||||
list = attSourceDataDao.getAttDataByStatus(pushDate,"");
|
||||
listUpdate = attSourceDataDao.getAttDataUpdateByStatus(pushDate,"");
|
||||
}
|
||||
if(list != null && !list.isEmpty()){
|
||||
list.forEach(c -> c.setAttStatus("3"));
|
||||
attSourceDataDao.updateAttDataList(list);
|
||||
}
|
||||
if(listUpdate != null && !listUpdate.isEmpty()){
|
||||
listUpdate.forEach(c -> c.setAttStatus("3"));
|
||||
attSourceDataDao.updateAttDataUpdateList(listUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLegalHolidayData(String pushDate) {
|
||||
//查询当日是否是法假
|
||||
List<Holiday> holidays = attSourceDataDao.selectHolidayByDay(pushDate);
|
||||
if(!holidays.isEmpty()){
|
||||
//当日为节假日
|
||||
List<AttGroupBean> groupList = Optional.ofNullable(attGroupDao.selectAttGroupList(new AttGroupBean())).
|
||||
orElseGet(ArrayList::new);
|
||||
List<AttGroupBean> attList = attSourceDataDao.getAttSettingHistoryDate(pushDate);
|
||||
groupList.forEach(c -> {
|
||||
if(c.getIsHaveHoliday() == 1){
|
||||
List<AttGroupBean> collect = attList.stream().filter(a -> Objects.equals(a.getGroupId(), c.getGroupId())).collect(Collectors.toList());
|
||||
if(!collect.isEmpty()){
|
||||
// 创建一个新的 List<AttDataBean>
|
||||
List<AttDataBean> listUpdate = new ArrayList<>();
|
||||
collect.forEach(v -> {
|
||||
AttDataBean dataBean = new AttDataBean();
|
||||
dataBean.setUserId(v.getUserId());
|
||||
dataBean.setAttCurrentDay(v.getCurrentDay());
|
||||
dataBean.setAttStatus("11");
|
||||
listUpdate.add(dataBean);
|
||||
});
|
||||
attSourceDataDao.updateAttDataList(listUpdate);
|
||||
attSourceDataDao.updateAttDataUpdateList(listUpdate);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入模板数据
|
||||
*
|
||||
* @param pushDate
|
||||
* @param pushType 1定时 2手动
|
||||
*/
|
||||
@Override
|
||||
public void insertAttTempData(String pushDate, int pushType) {
|
||||
//查询当天应考勤人员列表
|
||||
List<AttDataBean> listPerson = attSourceDataDao.getAttPerson(pushDate);
|
||||
if (pushType == 1) {
|
||||
//只会在凌晨执行一次
|
||||
attSourceDataDao.insertAttDataList(listPerson);
|
||||
} else if (pushType == 2) {
|
||||
//1.查询出历史当天已插入模版
|
||||
//2.查询出历史当天考勤列表
|
||||
//3.对比增量更新
|
||||
List<Long> personId = attSourceDataDao.getAttDataPerson(pushDate);
|
||||
listPerson.removeIf(c -> personId.contains(c.getUserId()));
|
||||
attSourceDataDao.insertAttDataList(listPerson);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取考勤数据(数据拉取)
|
||||
*
|
||||
* @param pushDate 日期
|
||||
* @param pushType 1:自动推送(当天) 2:手动(历史)
|
||||
*/
|
||||
@Override
|
||||
public void getAttendanceData(String pushDate, int pushType) {
|
||||
List<AttSourceDataBean> qsyAttData = getQsyAttendanceData(pushDate, pushType);
|
||||
List<AttSourceDataBean> machineAttSourceList = getMachineAttendanceData(pushDate, pushType);
|
||||
//新增考勤到考勤来源表(数据过多时分批次插入)
|
||||
machineAttSourceList.addAll(qsyAttData);
|
||||
insertAttSourceData(machineAttSourceList);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取黔送云的考勤数据
|
||||
*/
|
||||
private List<AttSourceDataBean> getQsyAttendanceData(String pushDate, int pushType) {
|
||||
List<AttSourceDataBean> attSourceList = Optional.ofNullable(attSourceDataDao.getQsyAttendances(pushDate, pushType)).
|
||||
orElseGet(ArrayList::new);
|
||||
if (!attSourceList.isEmpty()) {
|
||||
attSourceList.forEach(c -> {
|
||||
String address;
|
||||
String province;
|
||||
try {
|
||||
JSONObject result = AddressCoordinateFormatUtil.
|
||||
coordinateToAddress2(c.getAttLon(), c.getAttLat());
|
||||
address = result.getString("formatted_address");
|
||||
province = result.getJSONObject("addressComponent").getString("province");
|
||||
} catch (Exception e) {
|
||||
address = "地址未转化成功";
|
||||
province = "地址未转化成功";
|
||||
}
|
||||
c.setAttAddress(address);
|
||||
c.setProvince(province);
|
||||
}
|
||||
);
|
||||
}
|
||||
return attSourceList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取考勤机的考勤数据
|
||||
*/
|
||||
private List<AttSourceDataBean> getMachineAttendanceData(String pushDate, int pushType) {
|
||||
return Optional.ofNullable(attSourceDataDao.getMachineAttendances(pushDate, pushType)).
|
||||
orElseGet(ArrayList::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增考勤到数据库(考勤来源表)
|
||||
* 处理过后的数据新增到考勤来源表
|
||||
*
|
||||
* @param attSourceList 考勤数据
|
||||
*/
|
||||
public void insertAttSourceData(List<AttSourceDataBean> attSourceList) {
|
||||
if (attSourceList == null || attSourceList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
attSourceList.forEach(c -> {
|
||||
if ("0".equals(c.getAttType())) {
|
||||
//判断打卡类型
|
||||
int i = AttTimeUtil.getAttTypeByTime(c.getAttCurrentTime());
|
||||
c.setAttType(String.valueOf(i));
|
||||
}
|
||||
//判断打卡时间归属于当天还是昨天
|
||||
AttTimeUtil.changeAttCurrentDay(c);
|
||||
});
|
||||
int batchSize = 3000;
|
||||
int totalBatches = (int) Math.ceil((double) attSourceList.size() / batchSize);
|
||||
try {
|
||||
for (int i = 0; i < totalBatches; i++) {
|
||||
int fromIndex = i * batchSize;
|
||||
int toIndex = Math.min(fromIndex + batchSize, attSourceList.size());
|
||||
List<AttSourceDataBean> batch = attSourceList.subList(fromIndex, toIndex);
|
||||
attSourceDataDao.insertAttSourceData(batch);
|
||||
if ((i + 1) % 100 == 0 || i + 1 == totalBatches) {
|
||||
sqlSessionTemplate.flushStatements(); // 提交事务
|
||||
sqlSessionTemplate.clearCache(); // 清理缓存
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("考勤数据抓取到数据库出错啦", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查出每一个考勤组的应出勤天数以及考勤组的规则
|
||||
*
|
||||
* @return 考勤组集合
|
||||
*/
|
||||
public List<AttGroupBean> getGroupData(String pushDate) {
|
||||
//查出考勤组的数据
|
||||
List<AttGroupBean> groupList = Optional.ofNullable(attGroupDao.selectAttGroupList(new AttGroupBean())).
|
||||
orElseGet(ArrayList::new);
|
||||
if (groupList.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
//查询当月否有节假日或补班
|
||||
List<Holiday> holidays = attSourceDataDao.selectHolidayByMonth(pushDate);
|
||||
groupList.forEach(c -> {
|
||||
//应考勤天
|
||||
List<String> attDayList = WorkdayCalculator.getWorkDay(c.getAttDay(),
|
||||
Integer.parseInt(c.getAttType()), holidays, pushDate);
|
||||
c.setAttWorkDayBean(new PersonAttWorkDayBean(c.getGroupId(), attDayList.size(), attDayList));
|
||||
});
|
||||
return groupList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 固定考勤数据计算
|
||||
*
|
||||
* @param list 考勤来源数据
|
||||
* @param attGroupBean 考勤组数据
|
||||
* @return 考勤数据
|
||||
*/
|
||||
private List<AttSourceDataBean> groupFixedData(List<AttSourceDataBean> list,
|
||||
AttGroupBean attGroupBean) {
|
||||
List<AttSourceDataBean> newList = new ArrayList<>();
|
||||
//分组排序(将每个人每天在自己考勤组的数据分组)
|
||||
Map<String, List<AttSourceDataBean>> groupedItems = list.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
c -> c.getUserId() + "|" + c.getAttCurrentDay(),
|
||||
Collectors.collectingAndThen(
|
||||
Collectors.toList(),
|
||||
v -> {
|
||||
v.sort(Comparator.comparing(AttSourceDataBean::getAttCurrentTime));
|
||||
return v;
|
||||
}
|
||||
)
|
||||
));
|
||||
//固定打卡,以最靠近上班打卡时间前的出闸机的时间为基准,去除掉之前的所有打卡数据
|
||||
AttTimeUtil.processGroupedItems(groupedItems, attGroupBean);
|
||||
groupedItems.forEach((c, v) -> {
|
||||
//第一次上班时间
|
||||
AttSourceDataBean frontToWorkBean = v.stream()
|
||||
.filter(a -> a.getAttType().equals("1"))
|
||||
.collect(Collectors.toList())
|
||||
.stream().min(Comparator.comparing(AttSourceDataBean::getAttCurrentTime)).orElse(null);
|
||||
//最新一次下班时间
|
||||
AttSourceDataBean backOffWorkBean = v.stream()
|
||||
.filter(a -> a.getAttType().equals("2"))
|
||||
.collect(Collectors.toList())
|
||||
.stream().max(Comparator.comparing(AttSourceDataBean::getAttCurrentTime)).orElse(null);
|
||||
|
||||
if (backOffWorkBean != null) {
|
||||
//最新一次下班时间插入list
|
||||
newList.add(backOffWorkBean);
|
||||
}
|
||||
//没有下班卡则添加第一次打卡数据
|
||||
newList.add(frontToWorkBean);
|
||||
//获取工作时间外出记录并存储(支线,不影响)
|
||||
addWorkTimeOutRecord(v, attGroupBean);
|
||||
});
|
||||
Iterator<AttSourceDataBean> iterator = newList.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
if (iterator.next() == null) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
processAttendanceStatus(newList, attGroupBean.getToWorkTime(), attGroupBean.getOffWorkTime(), attGroupBean.getLateMinute(),
|
||||
attGroupBean.getAbsenteeismLateMinute(), attGroupBean.getLeaveMinute(), attGroupBean.getAbsenteeismLeaveMinute());
|
||||
return newList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自由考勤数据计算
|
||||
*
|
||||
* @param list 考勤来源数据
|
||||
* @param attGroupBean 考勤组数据
|
||||
* @return 考勤数据
|
||||
*/
|
||||
private List<AttSourceDataBean> groupFreeData(List<AttSourceDataBean> list, AttGroupBean attGroupBean) {
|
||||
List<AttSourceDataBean> newList = new ArrayList<>();
|
||||
//分组排序(将每个人每天在自己考勤组的数据分组)
|
||||
Map<String, List<AttSourceDataBean>> groupedItems = list.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
c -> c.getUserId() + "|" + c.getAttCurrentDay(),
|
||||
Collectors.collectingAndThen(
|
||||
Collectors.toList(),
|
||||
v -> {
|
||||
v.sort(Comparator.comparing(AttSourceDataBean::getAttCurrentTime));
|
||||
return v;
|
||||
}
|
||||
)
|
||||
));
|
||||
|
||||
groupedItems.forEach((c, v) -> {
|
||||
//第一次上班时间
|
||||
AttSourceDataBean frontToWorkBean = v.stream()
|
||||
.filter(a -> a.getAttType().equals("1"))
|
||||
.collect(Collectors.toList())
|
||||
.stream().min(Comparator.comparing(AttSourceDataBean::getAttCurrentTime)).orElse(null);
|
||||
//最新一次下班时间
|
||||
AttSourceDataBean backOffWorkBean = v.stream()
|
||||
.filter(a -> a.getAttType().equals("2"))
|
||||
.collect(Collectors.toList())
|
||||
.stream().max(Comparator.comparing(AttSourceDataBean::getAttCurrentTime)).orElse(null);
|
||||
//自由打卡不需要去除数据
|
||||
getFreeAttData(newList, frontToWorkBean, backOffWorkBean, attGroupBean);
|
||||
});
|
||||
return newList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上下班考勤计算
|
||||
*
|
||||
* @param list 考勤来源数据
|
||||
* @param attGroupBean 考勤组数据
|
||||
* @param tf 固定|自由
|
||||
* @return 考勤数据
|
||||
*/
|
||||
private List<AttSourceDataBean> getDataList(List<AttSourceDataBean> list,
|
||||
AttGroupBean attGroupBean,
|
||||
Boolean tf) {
|
||||
List<AttSourceDataBean> newList = new ArrayList<>();
|
||||
//分组排序(将每个人每天在自己考勤组的数据分组)
|
||||
Map<String, List<AttSourceDataBean>> groupedItems = list.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
c -> c.getUserId() + "|" + c.getAttCurrentDay(),
|
||||
Collectors.collectingAndThen(
|
||||
Collectors.toList(),
|
||||
v -> {
|
||||
v.sort(Comparator.comparing(AttSourceDataBean::getAttCurrentTime));
|
||||
return v;
|
||||
}
|
||||
)
|
||||
));
|
||||
if (!tf) {
|
||||
//固定打卡,以最靠近上班打卡时间前的出闸机的时间为基准,去除掉之前的所有打卡数据
|
||||
AttTimeUtil.processGroupedItems(groupedItems, attGroupBean);
|
||||
}
|
||||
groupedItems.forEach((c, v) -> {
|
||||
//第一次上班时间
|
||||
AttSourceDataBean frontToWorkBean = v.stream()
|
||||
.filter(a -> a.getAttType().equals("1"))
|
||||
.collect(Collectors.toList())
|
||||
.stream().min(Comparator.comparing(AttSourceDataBean::getAttCurrentTime)).orElse(null);
|
||||
//最新一次下班时间
|
||||
AttSourceDataBean backOffWorkBean = v.stream()
|
||||
.filter(a -> a.getAttType().equals("2"))
|
||||
.collect(Collectors.toList())
|
||||
.stream().max(Comparator.comparing(AttSourceDataBean::getAttCurrentTime)).orElse(null);
|
||||
if (tf) {
|
||||
// if(!v.isEmpty() && v.get(0).getName().equals("任荣辉")){
|
||||
// System.out.println("11111111111");
|
||||
// }
|
||||
//自由打卡不需要去除数据
|
||||
getFreeAttData(newList, frontToWorkBean, backOffWorkBean, attGroupBean);
|
||||
} else {
|
||||
if (backOffWorkBean != null) {
|
||||
//最新一次下班时间插入list
|
||||
newList.add(backOffWorkBean);
|
||||
}
|
||||
//没有下班卡则添加第一次打卡数据
|
||||
newList.add(frontToWorkBean);
|
||||
//获取工作异常
|
||||
LocalTime lastOutTime = null;
|
||||
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
try {
|
||||
//存储工作异常的list
|
||||
List<AttSourceDataBean> longBreakRecords = new ArrayList<>();
|
||||
for (int i = 0; i < v.size(); i++) {
|
||||
AttSourceDataBean record = v.get(i);
|
||||
// 如果是“出”的记录,保存时间
|
||||
if ("2".equals(record.getAttType())) {
|
||||
lastOutTime = LocalDateTime.parse(record.getAttCurrentTime(), dateTimeFormatter).toLocalTime();
|
||||
if (lastOutTime.isAfter(LocalTime.parse(attGroupBean.getOffWorkTime(), timeFormatter))) {
|
||||
//进在下班之后,不考虑
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 如果是“进”的记录并且之前有“出”的记录
|
||||
else if ("1".equals(record.getAttType()) && lastOutTime != null) {
|
||||
LocalTime inTime = LocalDateTime.parse(record.getAttCurrentTime(), dateTimeFormatter).toLocalTime();
|
||||
// 计算实际工作时间(不包括午休时间)
|
||||
// if(record.getName().equals("何波")){
|
||||
// System.out.println("11111111111");
|
||||
// }
|
||||
// 出-》进 的时间在上班时间前,去除
|
||||
if (inTime.isBefore(LocalTime.parse(attGroupBean.getToWorkTime(), timeFormatter))) {
|
||||
//进在上班之后,不考虑
|
||||
break;
|
||||
}
|
||||
Duration workDuration = AttTimeUtil.calculateWorkDuration(lastOutTime, inTime, attGroupBean);
|
||||
// 如果工作时间外出超过了规定时间,记录下这对“出”和“进”
|
||||
if (workDuration != null && !workDuration.isNegative() && workDuration.toMinutes() > attGroupBean.getWorkAbnormalMinute()) {
|
||||
AttSourceDataBean longBreakRecord = new AttSourceDataBean();
|
||||
longBreakRecord.setUserId(record.getUserId());
|
||||
longBreakRecord.setName(record.getName());
|
||||
longBreakRecord.setOrgId(attGroupBean.getOrgId());
|
||||
longBreakRecord.setAttCurrentDay(record.getAttCurrentDay());
|
||||
longBreakRecord.setAttCurrentTime(v.get(i - 1).getAttCurrentTime() + " " + record.getAttCurrentTime());
|
||||
longBreakRecord.setAttAddress(v.get(i - 1).getAttAddress() + " " + record.getAttAddress());
|
||||
longBreakRecords.add(longBreakRecord);
|
||||
}
|
||||
// 更新lastOutTime为null,因为已经处理了这一对
|
||||
lastOutTime = null;
|
||||
}
|
||||
}
|
||||
//判断有没有临时外出请假等等
|
||||
if (!longBreakRecords.isEmpty()) {
|
||||
int x = attSourceDataDao.getLeaveDataByUserId(longBreakRecords.get(0));
|
||||
if (x == 0) {
|
||||
attSourceDataDao.insertWorkAbnormal(longBreakRecords);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("工作异常处理报错");
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
return newList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理打卡状态
|
||||
*
|
||||
* @param newList 考勤数据
|
||||
* @param toWorkTime 上班时间
|
||||
* @param offWorkTime 下班时间
|
||||
* @param lateMinute 迟到时间
|
||||
* @param absenteeismLateMinute 旷工迟到分钟
|
||||
* @param leaveMinute 早退分钟
|
||||
* @param absenteeismLeaveMinute 旷工早退分钟
|
||||
*/
|
||||
private void processAttendanceStatus(List<AttSourceDataBean> newList, String toWorkTime,
|
||||
String offWorkTime, long lateMinute,
|
||||
long absenteeismLateMinute, long leaveMinute,
|
||||
long absenteeismLeaveMinute) {
|
||||
newList.forEach(c -> {
|
||||
// if(c.getName().equals("肖阳")){
|
||||
// System.out.println("c.getAttCurrentTime() = " + c.getAttCurrentTime());
|
||||
// }
|
||||
System.out.println(c.getName());
|
||||
int attStatus = calculateStatus(
|
||||
c.getAttType(),
|
||||
c.getAttCurrentTime(),
|
||||
"1".equals(c.getAttType()) ? toWorkTime : offWorkTime,
|
||||
"1".equals(c.getAttType()) ? lateMinute : leaveMinute,
|
||||
"1".equals(c.getAttType()) ? absenteeismLateMinute : absenteeismLeaveMinute
|
||||
);
|
||||
System.out.println(1111);
|
||||
// 如打卡状态为正常,检查是否有异常出入时间
|
||||
if (attStatus == 1 && c.getAttStatus() != null && c.getAttStatus() == 8) {
|
||||
attStatus = 8; // 异常
|
||||
boolean b = DateTimeHelper.compareTime2(c.getAttCurrentTime(), c.getAbnormalAttTime());
|
||||
String time = "";
|
||||
String address = "";
|
||||
if (b) {
|
||||
time = c.getAttCurrentTime() + " " + c.getAbnormalAttTime();
|
||||
address = c.getAttAddress() + " " + c.getAbnormalAttAddress();
|
||||
} else {
|
||||
time = c.getAbnormalAttTime() + " " + c.getAttCurrentTime();
|
||||
address = c.getAbnormalAttAddress() + " " + c.getAttAddress();
|
||||
}
|
||||
c.setAttCurrentTime(time);
|
||||
c.setAttAddress(address);
|
||||
}
|
||||
System.out.println(2222);
|
||||
c.setAttStatus(attStatus);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自由考勤数据
|
||||
*
|
||||
* @param newList 考勤数据
|
||||
* @param frontToWorkBean 第一次上班考勤数据
|
||||
* @param backOffWorkBean 最后一次下班考勤数据
|
||||
* @param attGroupBean 考勤组数据
|
||||
*/
|
||||
private void getFreeAttData(List<AttSourceDataBean> newList,
|
||||
AttSourceDataBean frontToWorkBean,
|
||||
AttSourceDataBean backOffWorkBean,
|
||||
AttGroupBean attGroupBean) {
|
||||
// todayClockNum 自由打卡每天打卡次数
|
||||
if (attGroupBean.getTodayClockNum() == 2) {
|
||||
if (frontToWorkBean != null) {
|
||||
frontToWorkBean.setAttStatus(1);
|
||||
newList.add(frontToWorkBean);
|
||||
}
|
||||
if (backOffWorkBean != null) {
|
||||
if (frontToWorkBean != null) {
|
||||
long minutesDiff = DateUtil.between(
|
||||
DateUtils.parseDate(frontToWorkBean.getAttCurrentTime()),
|
||||
DateUtils.parseDate(backOffWorkBean.getAttCurrentTime()),
|
||||
DateUnit.MINUTE
|
||||
);
|
||||
if (minutesDiff >= attGroupBean.getAttendanceDuration()) {
|
||||
backOffWorkBean.setAttStatus(1);
|
||||
} else {
|
||||
if (minutesDiff > attGroupBean.getAbsenteeismLateMinute()) {
|
||||
backOffWorkBean.setAttStatus(1);
|
||||
} else if (minutesDiff > attGroupBean.getAbsenteeismLeaveMinute()) {
|
||||
backOffWorkBean.setAttStatus(4);
|
||||
} else {
|
||||
backOffWorkBean.setAttStatus(3);
|
||||
}
|
||||
}
|
||||
newList.add(backOffWorkBean);
|
||||
} else {
|
||||
backOffWorkBean.setAttStatus(1);
|
||||
newList.add(backOffWorkBean);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (frontToWorkBean != null) {
|
||||
// if(frontToWorkBean.getName().equals("位兆虎")){
|
||||
// System.out.println(1);
|
||||
// }
|
||||
frontToWorkBean.setAttStatus(1);
|
||||
AttSourceDataBean bean;
|
||||
try {
|
||||
bean = (AttSourceDataBean) BeanUtils.cloneBean(frontToWorkBean);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
bean.setAttCurrentTime("");
|
||||
bean.setAttType("2");
|
||||
newList.add(bean);
|
||||
newList.add(frontToWorkBean);
|
||||
} else if (backOffWorkBean != null) {
|
||||
backOffWorkBean.setAttStatus(1);
|
||||
AttSourceDataBean bean;
|
||||
try {
|
||||
bean = (AttSourceDataBean) BeanUtils.cloneBean(backOffWorkBean);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
bean.setAttCurrentTime("");
|
||||
bean.setAttType("1");
|
||||
newList.add(bean);
|
||||
newList.add(backOffWorkBean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算时间差并返回状态
|
||||
*
|
||||
* @param attType 考勤类型
|
||||
* @param attTime 考勤时间
|
||||
* @param standardTime 上、下班时间
|
||||
* @param lateThreshold 迟到、早退时间
|
||||
* @param absenteeismThreshold 旷工时间
|
||||
* @return 考勤状态
|
||||
*/
|
||||
private int calculateStatus(String attType, String attTime, String standardTime,
|
||||
long lateThreshold, long absenteeismThreshold) {
|
||||
int status = 1;
|
||||
// 创建两个时间点
|
||||
Date date1 = DateUtils.parseDate(DateUtils.getYyyyMmDd(attTime) + " " + standardTime);
|
||||
Date date2 = DateUtils.parseDate(attTime);
|
||||
if ("2".equals(attType)) {
|
||||
String startTime = DateUtils.getYyyyMmDd(attTime) + " 00:00:00";
|
||||
String endTime = DateUtils.getYyyyMmDd(attTime) + " 04:59:59";
|
||||
if (DateUtils.getTimeIsRange(attTime, startTime, endTime)) {
|
||||
date1 = DateUtils.parseDate(DateUtils.getAfterDate(DateUtils.getYyyyMmDd(attTime)) + " " + standardTime);
|
||||
}
|
||||
}
|
||||
if ("1".equals(attType)) { // 上班打卡
|
||||
// 计算时间差
|
||||
// 减 1 原因:30上班,30:59不算迟到,类似于默认可以迟到一分钟
|
||||
double difference = (date2.getTime() - date1.getTime()) / 60000.0 - 1;
|
||||
if (difference > 0) {
|
||||
if (difference > absenteeismThreshold) {
|
||||
status = 3; // 旷工
|
||||
} else if (difference > lateThreshold) {
|
||||
status = 2; // 迟到
|
||||
}
|
||||
}
|
||||
} else if ("2".equals(attType)) { // 下班打卡
|
||||
// 计算时间差
|
||||
double difference = (date2.getTime() - date1.getTime()) / 60000.0;
|
||||
if (difference < 0) {
|
||||
if (Math.abs(difference) > lateThreshold) {
|
||||
status = 4; // 早退
|
||||
}
|
||||
if (Math.abs(difference) > absenteeismThreshold) {
|
||||
status = 3; // 旷工
|
||||
}
|
||||
}
|
||||
}
|
||||
return status; // 正常
|
||||
}
|
||||
|
||||
private void addWorkTimeOutRecord(List<AttSourceDataBean> v, AttGroupBean attGroupBean) {
|
||||
LocalTime lastOutTime = null;
|
||||
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
try {
|
||||
//存储工作异常的list
|
||||
List<AttSourceDataBean> longBreakRecords = new ArrayList<>();
|
||||
for (int i = 0; i < v.size(); i++) {
|
||||
AttSourceDataBean record = v.get(i);
|
||||
// 如果是“出”的记录,保存时间
|
||||
if ("2".equals(record.getAttType())) {
|
||||
lastOutTime = LocalDateTime.parse(record.getAttCurrentTime(), dateTimeFormatter).toLocalTime();
|
||||
if (lastOutTime.isAfter(LocalTime.parse(attGroupBean.getOffWorkTime(), timeFormatter))) {
|
||||
//进在下班之后,不考虑
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 如果是“进”的记录并且之前有“出”的记录
|
||||
else if ("1".equals(record.getAttType()) && lastOutTime != null) {
|
||||
LocalTime inTime = LocalDateTime.parse(record.getAttCurrentTime(), dateTimeFormatter).toLocalTime();
|
||||
// 计算实际工作时间(不包括午休时间)
|
||||
// if(record.getName().equals("何波")){
|
||||
// System.out.println("11111111111");
|
||||
// }
|
||||
// 出-》进 的时间在上班时间前,去除
|
||||
if (inTime.isBefore(LocalTime.parse(attGroupBean.getToWorkTime(), timeFormatter))) {
|
||||
//进在上班之后,不考虑
|
||||
break;
|
||||
}
|
||||
Duration workDuration = AttTimeUtil.calculateWorkDuration(lastOutTime, inTime, attGroupBean);
|
||||
// 如果工作时间外出超过了规定时间,记录下这对“出”和“进”
|
||||
if (workDuration != null && !workDuration.isNegative() && workDuration.toMinutes() > attGroupBean.getWorkAbnormalMinute()) {
|
||||
AttSourceDataBean longBreakRecord = new AttSourceDataBean();
|
||||
longBreakRecord.setUserId(record.getUserId());
|
||||
longBreakRecord.setName(record.getName());
|
||||
longBreakRecord.setOrgId(attGroupBean.getOrgId());
|
||||
longBreakRecord.setAttCurrentDay(record.getAttCurrentDay());
|
||||
longBreakRecord.setAttCurrentTime(v.get(i - 1).getAttCurrentTime() + " " + record.getAttCurrentTime());
|
||||
longBreakRecord.setAttAddress(v.get(i - 1).getAttAddress() + " " + record.getAttAddress());
|
||||
longBreakRecords.add(longBreakRecord);
|
||||
}
|
||||
// 更新lastOutTime为null,因为已经处理了这一对
|
||||
lastOutTime = null;
|
||||
}
|
||||
}
|
||||
//判断有没有临时外出请假等等
|
||||
if (!longBreakRecords.isEmpty()) {
|
||||
int x = attSourceDataDao.getLeaveDataByUserId(longBreakRecords.get(0));
|
||||
if (x == 0) {
|
||||
attSourceDataDao.insertWorkAbnormal(longBreakRecords);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("工作异常处理报错");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package com.bonus.system.att.tasks;
|
||||
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.bonus.common.core.utils.DateTimeHelper;
|
||||
import com.bonus.common.core.utils.DateUtils;
|
||||
import com.bonus.system.att.dao.AttGroupDao;
|
||||
import com.bonus.system.att.dao.AttSourceDataDao;
|
||||
import com.bonus.system.att.entity.*;
|
||||
import com.bonus.system.att.service.AttCalService;
|
||||
import com.bonus.system.att.utils.AddressCoordinateFormatUtil;
|
||||
import com.bonus.system.att.utils.AttTimeUtil;
|
||||
import com.bonus.system.att.utils.WorkdayCalculator;
|
||||
import com.bonus.system.holiday.dao.HolidayDao;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.beanutils.BeanUtils;
|
||||
import org.mybatis.spring.SqlSessionTemplate;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.*;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author zys
|
||||
* 考勤定时器
|
||||
*/
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
@Slf4j
|
||||
@EnableAsync
|
||||
public class NewAttTask {
|
||||
|
||||
|
||||
@Resource(name = "attSourceDataDao")
|
||||
private AttSourceDataDao attSourceDataDao;
|
||||
|
||||
@Resource(name = "sqlSessionTemplate")
|
||||
private SqlSessionTemplate sqlSessionTemplate;
|
||||
|
||||
@Resource(name = "AttCalService")
|
||||
private AttCalService attCalService;
|
||||
|
||||
|
||||
/**
|
||||
* 待考勤人员列表
|
||||
* 在人员考勤模版数据之前生成
|
||||
* @param pushDate 时间(可为空,为空则推历史60天内没生成的数据,不为空则推指定日期)
|
||||
*/
|
||||
private void insertAttDateHistory(String pushDate) {
|
||||
//待考勤人员列表生成
|
||||
attCalService.insertAttDateHistory(pushDate);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 人员考勤模版数据(默认是未打卡)
|
||||
* 每天早上0点30执行一次
|
||||
* @param pushDate 时间
|
||||
* @param pushType 1:自动推送(当天) 2:手动(历史)
|
||||
*/
|
||||
private void insertAttTempData(String pushDate, int pushType) {
|
||||
//获取考勤数据
|
||||
attCalService.insertAttTempData(pushDate, pushType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取其他系统考勤数据
|
||||
* 建议一小时一次
|
||||
* @param pushDate 时间
|
||||
* @param pushType 1:自动推送(当天) 2:手动(历史)
|
||||
*/
|
||||
private void getAttendanceData(String pushDate, int pushType) {
|
||||
//获取考勤数据
|
||||
attCalService.getAttendanceData(pushDate, pushType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 考勤数据应用(步骤一:考勤数据更新)
|
||||
* 建议一小时一次
|
||||
* @param pushDate 时间
|
||||
* @param pushType 1:自动推送(当天) 2:手动(历史)
|
||||
*/
|
||||
private void updateAttData(String pushDate, int pushType) {
|
||||
//获取考勤数据
|
||||
attCalService.updateAttData(pushDate, pushType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 考勤数据应用(步骤二:旷工状态更新)
|
||||
* 每天12点将当天未打卡考勤人员上班置为旷工
|
||||
* 每天晚上22点,将下班未打卡置为旷工
|
||||
* @param pushDate 时间
|
||||
* @param pushType 1:自动推送(当天) 2:手动(历史)
|
||||
*/
|
||||
private void updateAbsenteeismData(String pushDate, int pushType) {
|
||||
//旷工状态更新
|
||||
attCalService.updateAbsenteeismData(pushDate, pushType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请假数据应用(步骤一:法假数据更新)
|
||||
* 法定节假日不会随时间变化,一天执行一次就可以了
|
||||
* @param pushDate 时间
|
||||
*/
|
||||
private void updateLegalHolidayData(String pushDate) {
|
||||
//旷工状态更新
|
||||
attCalService.updateLegalHolidayData(pushDate);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue