|
@@ -1,10 +1,38 @@
|
|
|
package com.ylx.massage.service.impl;
|
|
package com.ylx.massage.service.impl;
|
|
|
|
|
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
|
|
+import com.ylx.attendanceconfig.domain.AttendanceDeductionRule;
|
|
|
|
|
+import com.ylx.attendanceconfig.domain.AttendanceRule;
|
|
|
|
|
+import com.ylx.attendanceconfig.mapper.AttendanceDeductionRuleMapper;
|
|
|
|
|
+import com.ylx.attendanceconfig.mapper.AttendanceRuleMapper;
|
|
|
|
|
+import com.ylx.common.exception.ServiceException;
|
|
|
|
|
+import com.ylx.common.utils.DateUtils;
|
|
|
|
|
+import com.ylx.common.utils.StringUtils;
|
|
|
import com.ylx.massage.domain.MerchantDailyAttendance;
|
|
import com.ylx.massage.domain.MerchantDailyAttendance;
|
|
|
|
|
+import com.ylx.massage.domain.dto.MerchantDailyAttendanceQueryDTO;
|
|
|
import com.ylx.massage.mapper.MerchantDailyAttendanceMapper;
|
|
import com.ylx.massage.mapper.MerchantDailyAttendanceMapper;
|
|
|
import com.ylx.massage.service.MerchantDailyAttendanceService;
|
|
import com.ylx.massage.service.MerchantDailyAttendanceService;
|
|
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
+
|
|
|
|
|
+import javax.annotation.Resource;
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
|
|
+import java.math.RoundingMode;
|
|
|
|
|
+import java.time.LocalDate;
|
|
|
|
|
+import java.time.ZoneId;
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.Calendar;
|
|
|
|
|
+import java.util.Comparator;
|
|
|
|
|
+import java.util.Date;
|
|
|
|
|
+import java.util.LinkedHashMap;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.Objects;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 商户每日考勤统计表(MerchantDailyAttendance)表服务实现类
|
|
* 商户每日考勤统计表(MerchantDailyAttendance)表服务实现类
|
|
@@ -15,5 +43,292 @@ import org.springframework.stereotype.Service;
|
|
|
@Service("merchantDailyAttendanceService")
|
|
@Service("merchantDailyAttendanceService")
|
|
|
public class MerchantDailyAttendanceServiceImpl extends ServiceImpl<MerchantDailyAttendanceMapper, MerchantDailyAttendance> implements MerchantDailyAttendanceService {
|
|
public class MerchantDailyAttendanceServiceImpl extends ServiceImpl<MerchantDailyAttendanceMapper, MerchantDailyAttendance> implements MerchantDailyAttendanceService {
|
|
|
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ private static final int NOT_DELETE = 0;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 正常考勤状态
|
|
|
|
|
+ */
|
|
|
|
|
+ private static final int ATTENDANCE_NORMAL = 1;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 异常考勤状态
|
|
|
|
|
+ */
|
|
|
|
|
+ private static final int ATTENDANCE_ABNORMAL = 2;
|
|
|
|
|
+ private static final int ENABLED = 1;
|
|
|
|
|
+ private static final int EARLY_LEAVE_DEDUCTION = 1;
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private AttendanceRuleMapper attendanceRuleMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private AttendanceDeductionRuleMapper attendanceDeductionRuleMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Page<MerchantDailyAttendance> queryDailyAttendance(Page<MerchantDailyAttendance> page, MerchantDailyAttendanceQueryDTO query) {
|
|
|
|
|
+ MerchantDailyAttendanceQueryDTO condition = query == null ? new MerchantDailyAttendanceQueryDTO() : query;
|
|
|
|
|
+ List<MerchantDailyAttendance> attendanceList = list(buildBaseQuery(condition));
|
|
|
|
|
+ AttendanceRule enabledRule = getEnabledAttendanceRule();
|
|
|
|
|
+ List<AttendanceDeductionRule> deductionRules = getDeductionRules(enabledRule);
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建每日考勤统计列表
|
|
|
|
|
+ */
|
|
|
|
|
+ List<MerchantDailyAttendance> summaryList = buildDailySummaryList(attendanceList, enabledRule, deductionRules);
|
|
|
|
|
+ if (condition.getAttendanceStatus() != null) {
|
|
|
|
|
+ summaryList = summaryList.stream()
|
|
|
|
|
+ .filter(item -> condition.getAttendanceStatus().equals(item.getAttendanceStatus()))
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ long current = page.getCurrent() <= 0 ? 1 : page.getCurrent();
|
|
|
|
|
+ long size = page.getSize() <= 0 ? 10 : page.getSize();
|
|
|
|
|
+ int fromIndex = (int) Math.min((current - 1) * size, summaryList.size());
|
|
|
|
|
+ int toIndex = (int) Math.min(fromIndex + size, summaryList.size());
|
|
|
|
|
+
|
|
|
|
|
+ Page<MerchantDailyAttendance> resultPage = new Page<>(current, size);
|
|
|
|
|
+ resultPage.setTotal(summaryList.size());
|
|
|
|
|
+ resultPage.setRecords(summaryList.subList(fromIndex, toIndex));
|
|
|
|
|
+ return resultPage;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<MerchantDailyAttendance> queryDailyWorkTime(Integer merchantId, String attendanceDate) {
|
|
|
|
|
+ if (merchantId == null) {
|
|
|
|
|
+ throw new ServiceException("商户ID不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ Date dayStart = parseDateStart(attendanceDate, "考勤日期不能为空");
|
|
|
|
|
+ Date nextDayStart = addDays(dayStart, 1);
|
|
|
|
|
+
|
|
|
|
|
+ List<MerchantDailyAttendance> records = list(new LambdaQueryWrapper<MerchantDailyAttendance>()
|
|
|
|
|
+ .eq(MerchantDailyAttendance::getMerchantId, merchantId)
|
|
|
|
|
+ .ge(MerchantDailyAttendance::getAttendanceDate, dayStart)
|
|
|
|
|
+ .lt(MerchantDailyAttendance::getAttendanceDate, nextDayStart)
|
|
|
|
|
+ .orderByAsc(MerchantDailyAttendance::getAttendanceStartTime));
|
|
|
|
|
+
|
|
|
|
|
+ records.forEach(item -> item.setTotalWorkDurationText(formatDuration(defaultMinutes(item.getTotalWorkMinutes()))));
|
|
|
|
|
+ return records;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public void refreshYesterdayDeductionAmount() {
|
|
|
|
|
+ Date yesterday = addDays(DateUtils.getNowDate(), -1);
|
|
|
|
|
+ refreshDeductionAmountByDate(yesterday);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建基础查询条件。
|
|
|
|
|
+ * @param query 查询实体
|
|
|
|
|
+ * @return LambdaQueryWrapper<MerchantDailyAttendance> 基础查询条件
|
|
|
|
|
+ */
|
|
|
|
|
+ private LambdaQueryWrapper<MerchantDailyAttendance> buildBaseQuery(MerchantDailyAttendanceQueryDTO query) {
|
|
|
|
|
+ LambdaQueryWrapper<MerchantDailyAttendance> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ wrapper.like(StringUtils.isNotBlank(query.getMerchantName()), MerchantDailyAttendance::getMerchantName, query.getMerchantName())
|
|
|
|
|
+ .orderByDesc(MerchantDailyAttendance::getAttendanceDate);
|
|
|
|
|
+ //考勤状态
|
|
|
|
|
+ /*if (query.getAttendanceStatus() != null) {
|
|
|
|
|
+ wrapper.eq(MerchantDailyAttendance::getAttendanceStatus, query.getAttendanceStatus());
|
|
|
|
|
+ }*/
|
|
|
|
|
+ //开始日期
|
|
|
|
|
+ if (StringUtils.isNotBlank(query.getBeginDate())) {
|
|
|
|
|
+ wrapper.ge(MerchantDailyAttendance::getAttendanceDate, parseDateStart(query.getBeginDate(), "开始日期格式不正确"));
|
|
|
|
|
+ }
|
|
|
|
|
+ //结束日期
|
|
|
|
|
+ if (StringUtils.isNotBlank(query.getEndDate())) {
|
|
|
|
|
+ wrapper.lt(MerchantDailyAttendance::getAttendanceDate, addDays(parseDateStart(query.getEndDate(), "结束日期格式不正确"), 1));
|
|
|
|
|
+ }
|
|
|
|
|
+ return wrapper;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建每日考勤统计列表
|
|
|
|
|
+ * @param attendanceList 考勤记录列表
|
|
|
|
|
+ * @param enabledRule 有效考勤规则
|
|
|
|
|
+ * @param deductionRules 早退扣款规则列表
|
|
|
|
|
+ * @return List<MerchantDailyAttendance>
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<MerchantDailyAttendance> buildDailySummaryList(List<MerchantDailyAttendance> attendanceList, AttendanceRule enabledRule, List<AttendanceDeductionRule> deductionRules) {
|
|
|
|
|
+ Map<String, List<MerchantDailyAttendance>> groupMap = attendanceList.stream()
|
|
|
|
|
+ .filter(item -> item.getMerchantId() != null && item.getAttendanceDate() != null)
|
|
|
|
|
+ .collect(Collectors.groupingBy(this::buildDailyGroupKey, LinkedHashMap::new, Collectors.toList()));
|
|
|
|
|
|
|
|
|
|
+ List<MerchantDailyAttendance> summaryList = new ArrayList<>();
|
|
|
|
|
+ for (List<MerchantDailyAttendance> groupRecords : groupMap.values()) {
|
|
|
|
|
+ MerchantDailyAttendance first = groupRecords.get(0);
|
|
|
|
|
+ int totalMinutes = groupRecords.stream()
|
|
|
|
|
+ .map(MerchantDailyAttendance::getTotalWorkMinutes)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .mapToInt(Integer::intValue)
|
|
|
|
|
+ .sum();
|
|
|
|
|
+
|
|
|
|
|
+ MerchantDailyAttendance summary = new MerchantDailyAttendance();
|
|
|
|
|
+ BeanUtils.copyProperties(first, summary);
|
|
|
|
|
+ summary.setTotalWorkMinutes(totalMinutes);
|
|
|
|
|
+ summary.setTotalWorkDurationText(formatDuration(totalMinutes));
|
|
|
|
|
+ summary.setAttendanceStatus(calculateAttendanceStatus(totalMinutes, enabledRule, first.getAttendanceStatus()));
|
|
|
|
|
+ summary.setDeductionAmount(calculateDeductionAmount(totalMinutes, enabledRule, deductionRules).doubleValue());
|
|
|
|
|
+ summaryList.add(summary);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ summaryList.sort(Comparator
|
|
|
|
|
+ .comparing(MerchantDailyAttendance::getAttendanceDate, Comparator.nullsLast(Date::compareTo)).reversed()
|
|
|
|
|
+ .thenComparing(MerchantDailyAttendance::getMerchantId, Comparator.nullsLast(Integer::compareTo)));
|
|
|
|
|
+ return summaryList;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void refreshDeductionAmountByDate(Date attendanceDate) {
|
|
|
|
|
+ Date dayStart = startOfDay(attendanceDate);
|
|
|
|
|
+ Date nextDayStart = addDays(dayStart, 1);
|
|
|
|
|
+ List<MerchantDailyAttendance> records = list(new LambdaQueryWrapper<MerchantDailyAttendance>()
|
|
|
|
|
+ .ge(MerchantDailyAttendance::getAttendanceDate, dayStart)
|
|
|
|
|
+ .lt(MerchantDailyAttendance::getAttendanceDate, nextDayStart)
|
|
|
|
|
+ .eq(MerchantDailyAttendance::getIsDelete, NOT_DELETE));
|
|
|
|
|
+ if (records.isEmpty()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ AttendanceRule enabledRule = getEnabledAttendanceRule();
|
|
|
|
|
+ List<AttendanceDeductionRule> deductionRules = getDeductionRules(enabledRule);
|
|
|
|
|
+ List<MerchantDailyAttendance> summaryList = buildDailySummaryList(records, enabledRule, deductionRules);
|
|
|
|
|
+ for (MerchantDailyAttendance summary : summaryList) {
|
|
|
|
|
+ update(null, new LambdaUpdateWrapper<MerchantDailyAttendance>()
|
|
|
|
|
+ .eq(MerchantDailyAttendance::getMerchantId, summary.getMerchantId())
|
|
|
|
|
+ .ge(MerchantDailyAttendance::getAttendanceDate, startOfDay(summary.getAttendanceDate()))
|
|
|
|
|
+ .lt(MerchantDailyAttendance::getAttendanceDate, addDays(startOfDay(summary.getAttendanceDate()), 1))
|
|
|
|
|
+ .eq(MerchantDailyAttendance::getIsDelete, NOT_DELETE)
|
|
|
|
|
+ .set(MerchantDailyAttendance::getAttendanceStatus, summary.getAttendanceStatus())
|
|
|
|
|
+ .set(MerchantDailyAttendance::getDeductionAmount, summary.getDeductionAmount())
|
|
|
|
|
+ .set(MerchantDailyAttendance::getUpdateTime, java.time.LocalDateTime.now()));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取当前生效的考勤规则
|
|
|
|
|
+ * @return AttendanceRule
|
|
|
|
|
+ */
|
|
|
|
|
+ private AttendanceRule getEnabledAttendanceRule() {
|
|
|
|
|
+ return attendanceRuleMapper.selectOne(new LambdaQueryWrapper<AttendanceRule>()
|
|
|
|
|
+ .eq(AttendanceRule::getStatus, ENABLED)
|
|
|
|
|
+ .eq(AttendanceRule::getWorkDurationRuleEnabled, ENABLED)
|
|
|
|
|
+ .orderByDesc(AttendanceRule::getCreateTime)
|
|
|
|
|
+ .last("LIMIT 1"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取早退扣款规则
|
|
|
|
|
+ * @param rule
|
|
|
|
|
+ * @return List<AttendanceDeductionRule>
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<AttendanceDeductionRule> getDeductionRules(AttendanceRule rule) {
|
|
|
|
|
+ if (rule == null || rule.getId() == null) {
|
|
|
|
|
+ return new ArrayList<>();
|
|
|
|
|
+ }
|
|
|
|
|
+ return attendanceDeductionRuleMapper.selectList(new LambdaQueryWrapper<AttendanceDeductionRule>()
|
|
|
|
|
+ .eq(AttendanceDeductionRule::getRuleId, rule.getId())
|
|
|
|
|
+ .eq(AttendanceDeductionRule::getRuleType, EARLY_LEAVE_DEDUCTION)
|
|
|
|
|
+ .orderByAsc(AttendanceDeductionRule::getCreateTime));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 计算考勤状态
|
|
|
|
|
+ * @param totalMinutes 工作时长,单位:分钟
|
|
|
|
|
+ * @param rule 考勤规则
|
|
|
|
|
+ * @param fallbackStatus 回退状态
|
|
|
|
|
+ * @return 考勤状态
|
|
|
|
|
+ */
|
|
|
|
|
+ private Integer calculateAttendanceStatus(int totalMinutes, AttendanceRule rule, Integer fallbackStatus) {
|
|
|
|
|
+ if (rule == null || rule.getBasicWorkHours() == null) {
|
|
|
|
|
+ return fallbackStatus == null ? ATTENDANCE_NORMAL : fallbackStatus;
|
|
|
|
|
+ }
|
|
|
|
|
+ return totalMinutes >= getBasicWorkMinutes(rule) ? ATTENDANCE_NORMAL : ATTENDANCE_ABNORMAL;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 计算扣款金额
|
|
|
|
|
+ * @param totalMinutes
|
|
|
|
|
+ * @param rule
|
|
|
|
|
+ * @param deductionRules
|
|
|
|
|
+ * @return BigDecimal 扣款金额,单位:元
|
|
|
|
|
+ */
|
|
|
|
|
+ private BigDecimal calculateDeductionAmount(int totalMinutes, AttendanceRule rule, List<AttendanceDeductionRule> deductionRules) {
|
|
|
|
|
+ if (rule == null || rule.getBasicWorkHours() == null || deductionRules == null || deductionRules.isEmpty()) {
|
|
|
|
|
+ return BigDecimal.ZERO;
|
|
|
|
|
+ }
|
|
|
|
|
+ int lackMinutes = getBasicWorkMinutes(rule) - totalMinutes;
|
|
|
|
|
+ if (lackMinutes <= 0) {
|
|
|
|
|
+ return BigDecimal.ZERO;
|
|
|
|
|
+ }
|
|
|
|
|
+ return deductionRules.stream()
|
|
|
|
|
+ .filter(ruleItem -> ruleItem.getStartMinutes() != null && ruleItem.getEndMinutes() != null)
|
|
|
|
|
+ .filter(ruleItem -> lackMinutes >= ruleItem.getStartMinutes() && lackMinutes <= ruleItem.getEndMinutes())
|
|
|
|
|
+ .map(AttendanceDeductionRule::getDeductAmount)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .findFirst()
|
|
|
|
|
+ .orElse(BigDecimal.ZERO);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取基本工作时长
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param rule 考勤规则
|
|
|
|
|
+ * @return 基本工作时长,单位:分钟
|
|
|
|
|
+ */
|
|
|
|
|
+ private int getBasicWorkMinutes(AttendanceRule rule) {
|
|
|
|
|
+ return rule.getBasicWorkHours()
|
|
|
|
|
+ .multiply(BigDecimal.valueOf(60))
|
|
|
|
|
+ .setScale(0, RoundingMode.HALF_UP)
|
|
|
|
|
+ .intValue();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建每日考勤统计分组键
|
|
|
|
|
+ * @param attendance 考勤记录
|
|
|
|
|
+ * @return String 分组键
|
|
|
|
|
+ */
|
|
|
|
|
+ private String buildDailyGroupKey(MerchantDailyAttendance attendance) {
|
|
|
|
|
+ return attendance.getMerchantId() + "_" + DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, attendance.getAttendanceDate());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 格式化工作时长
|
|
|
|
|
+ * @param minutes 工作时长,单位:分钟
|
|
|
|
|
+ * @return 格式化后的字符串时长
|
|
|
|
|
+ */
|
|
|
|
|
+ private String formatDuration(Integer minutes) {
|
|
|
|
|
+ int totalMinutes = defaultMinutes(minutes);
|
|
|
|
|
+ return totalMinutes / 60 + "小时" + String.format("%02d", totalMinutes % 60) + "分钟";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int defaultMinutes(Integer minutes) {
|
|
|
|
|
+ return minutes == null ? 0 : minutes;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析日期字符串为日期对象
|
|
|
|
|
+ * @param date
|
|
|
|
|
+ * @param errorMessage
|
|
|
|
|
+ * @return
|
|
|
|
|
+ */
|
|
|
|
|
+ private Date parseDateStart(String date, String errorMessage) {
|
|
|
|
|
+ if (StringUtils.isBlank(date)) {
|
|
|
|
|
+ throw new ServiceException(errorMessage);
|
|
|
|
|
+ }
|
|
|
|
|
+ Date parsedDate = DateUtils.parseDate(date.trim());
|
|
|
|
|
+ if (parsedDate == null) {
|
|
|
|
|
+ throw new ServiceException(errorMessage);
|
|
|
|
|
+ }
|
|
|
|
|
+ return startOfDay(parsedDate);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Date startOfDay(Date date) {
|
|
|
|
|
+ LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
|
|
|
|
+ return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Date addDays(Date date, int amount) {
|
|
|
|
|
+ Calendar calendar = Calendar.getInstance();
|
|
|
|
|
+ calendar.setTime(date);
|
|
|
|
|
+ calendar.add(Calendar.DAY_OF_MONTH, amount);
|
|
|
|
|
+ return calendar.getTime();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|