# MorOvertimeDailyServiceImpl.java

    package cloud.wconnect.workperformance.service.mor.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.MoudleEventSubjectEnum;
    import cloud.wconnect.commonlib.pojo.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.TransactionUtils;
    import cloud.wconnect.sns.http.processor.Sender;
    import cloud.wconnect.workperformance.entity.mor.MorDepartment;
    import cloud.wconnect.workperformance.entity.mor.MorOvertimeDaily;
    import cloud.wconnect.workperformance.entity.mor.Overtime;
    import cloud.wconnect.workperformance.entity.mor.OvertimeSum;
    import cloud.wconnect.workperformance.mapper.mor.MorOvertimeDailyMapper;
    import cloud.wconnect.workperformance.pojo.bo.modelevent.overtime.MorOvertimeEventBO;
    import cloud.wconnect.workperformance.pojo.bo.modelevent.overtime.MorOvertimeEventValueBO;
    import cloud.wconnect.workperformance.pojo.bo.mor.overtime.OvertimeDailyBO;
    import cloud.wconnect.workperformance.pojo.dto.mor.MorDepartmentDTO;
    import cloud.wconnect.workperformance.pojo.dto.mor.MorDepartmentSummaryDTO;
    import cloud.wconnect.workperformance.pojo.dto.mor.MorDepartmentSummaryDetailDTO;
    import cloud.wconnect.workperformance.pojo.dto.mor.overtime.*;
    import cloud.wconnect.workperformance.pojo.query.mor.overtime.MorDepartmentSummaryQuery;
    import cloud.wconnect.workperformance.pojo.query.mor.overtime.OvertimeDailyQuery;
    import cloud.wconnect.workperformance.pojo.query.mor.overtime.OvertimeDailySummaryQuery;
    import cloud.wconnect.workperformance.service.mor.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import jakarta.annotation.Resource;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.math.BigDecimal;
    import java.time.LocalDate;
    import java.time.ZoneId;
    import java.time.temporal.TemporalAdjusters;
    import java.util.*;
    import java.util.concurrent.Executor;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * Monthly OT Report - Overtime Daily 服务实现类
     * </p>
     *
     * @author zc
     * @since 2025-03-14
     */
    @Service
    @DS("work_performance")
    public class MorOvertimeDailyServiceImpl extends ServiceImpl<MorOvertimeDailyMapper, MorOvertimeDaily> implements IMorOvertimeDailyService {

        @Resource
        private IOvertimeService overtimeService;
        @Resource
        private IOvertimeSumService overtimeSumService;
        @Resource
        private IMorDepartmentService morDepartmentService;
        @Resource
        private BeanMapper beanMapper;
        @Resource
        private ObjectMapper objectMapper;
        @Resource
        private Executor executor;
        @Resource
        @Qualifier("moduleEventSender")
        private Sender moduleEventSender;
        @Resource
        private IOvertimeDataPermissionConfigService dataPermissionConfigService;

        /**
         * 根据日期范围查询 Overtime Daily 数据
         *
         * @param propertyId 资产 id
         * @param startDate  开始日期（yyyyMMdd）
         * @param endDate    结束日期（yyyyMMdd）
         * @return MorOvertimeDaily 集合
         * @author zc
         * @date 2025/3/17
         */
        private List<MorOvertimeDaily> listByDateRange(int propertyId, long startDate, long endDate) {
            return list(new LambdaQueryWrapper<MorOvertimeDaily>()
                    .eq(MorOvertimeDaily::getPropertyId, propertyId)
                    .between(MorOvertimeDaily::getTheDate, startDate, endDate));
        }

        /**
         * [For Update] 根据日期范围加锁 Overtime Daily 数据
         *
         * @param propertyId 资产 id
         * @param startDate  开始日期（yyyyMMdd）
         * @param endDate    结束日期（yyyyMMdd）
         * @author zc
         * @date 2025/3/18
         */
        private void listByDateRangeForUpdate(int propertyId, long startDate, long endDate) {
            list(new LambdaQueryWrapper<MorOvertimeDaily>()
                    .eq(MorOvertimeDaily::getPropertyId, propertyId)
                    .between(MorOvertimeDaily::getTheDate, startDate, endDate)
                    .last("for update"));
        }

        /**
         * 处理 Overtime 报告数据
         *
         * @param propertyList         资产
         * @param overtimeDailyBOList  报告文件数据 BO
         * @param operatorId           操作人 id（0：系统）
         * @param propertyId2DateRange 报告数据日期范围(yyyyMMdd)
         * @author zc
         * @date 2025/3/17
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleOvertimeWorkHour(List<PropertyDTO> propertyList, List<OvertimeDailyBO> overtimeDailyBOList,
                                           int operatorId, Map<Integer, List<Long>> propertyId2DateRange) {
            // 格式化 BO 数据
            Map<Integer, List<MorOvertimeDaily>> propertyId2OvertimeDailyList = formatBoList(propertyList, overtimeDailyBOList).stream()
                    .collect(Collectors.groupingBy(MorOvertimeDaily::getPropertyId));

            for (PropertyDTO propertyDTO : propertyList) {
                List<MorOvertimeDaily> overtimeDailyList = propertyId2OvertimeDailyList.getOrDefault(propertyDTO.getId(),
                        Collections.emptyList());
                List<Long> dateRange = propertyId2DateRange.get(propertyDTO.getId());
                long startDate = dateRange.get(0);
                long endDate = dateRange.get(1);
                // 加锁
                listByDateRangeForUpdate(propertyDTO.getId(), startDate, endDate);
                // 保存 Overtime Daily 数据
                saveOvertimeDaily(propertyDTO, overtimeDailyList, operatorId, startDate, endDate);
                // 保存 Overtime Month 数据
                saveOvertime(propertyDTO, operatorId, startDate, endDate);
                // 保存 Overtime Sum 数据
                saveOvertimeSum(propertyDTO, startDate, endDate);
            }
            // 更新部门数据
            morDepartmentService.updateDepartment(propertyList, overtimeDailyBOList);

            // 指定日期范围内数据发生变化时，发送 SNS 事件
            TransactionUtils.afterCommitAsyncExecute(executor, () ->
                    sendModuleEvent(propertyList, propertyId2DateRange)
            );
        }

        /**
         * 格式化 BO 数据
         *
         * @param propertyList        资产集合
         * @param overtimeDailyBOList 报告文件数据 BO
         * @return Overtime 集合
         * @author zc
         * @date 2023/10/23
         */
        private List<MorOvertimeDaily> formatBoList(List<PropertyDTO> propertyList, List<OvertimeDailyBO> overtimeDailyBOList) {
            Map<String, PropertyDTO> abbr2Property = propertyList.stream()
                    .collect(Collectors.toMap(PropertyDTO::getAbbr, v -> v));
            return beanMapper.mapList(overtimeDailyBOList, MorOvertimeDaily.class, (bo, item) -> {
                PropertyDTO propertyDTO = abbr2Property.get(bo.getProperty());
                item.setPropertyId(propertyDTO.getId());
                item.setTheDate(Long.parseLong(DateTimeUtils.localDateToStr(DateTimeUtils.strToLocalDate(bo.getTheDateStr(),
                        BaseConstant.DATE_FORMAT_YYYY_MM_DD), BaseConstant.DATE_FORMAT_YYYYMMDD)));
                item.setTimeIn(DateTimeUtils.localDateTimeToTimestamp(DateTimeUtils.strToLocalDateTime(bo.getTimeInStr(),
                        BaseConstant.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MM), propertyDTO.getTimezone()));
                item.setTimeOut(DateTimeUtils.localDateTimeToTimestamp(DateTimeUtils.strToLocalDateTime(bo.getTimeOutStr(),
                        BaseConstant.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MM), propertyDTO.getTimezone()));
            });
        }

        /**
         * 保存 Overtime Daily 数据
         *
         * @param propertyDTO       资产
         * @param overtimeDailyList 报告数据
         * @param operatorId        操作人 id
         * @param startDate         报告数据开始日期(yyyyMMdd)
         * @param endDate           报告数据结束日期(yyyyMMdd)
         * @author zc
         * @date 2025/3/17
         */
        @Transactional(rollbackFor = Exception.class)
        public void saveOvertimeDaily(PropertyDTO propertyDTO, List<MorOvertimeDaily> overtimeDailyList, int operatorId,
                                      long startDate, long endDate) {
            // 删除指定日期范围的 OvertimeDaily 数据
            remove(new LambdaQueryWrapper<MorOvertimeDaily>()
                    .eq(MorOvertimeDaily::getPropertyId, propertyDTO.getId())
                    .between(MorOvertimeDaily::getTheDate, startDate, endDate));

            // 保存 OvertimeDaily 数据
            long nowTimestamp = DateTimeUtils.nowTimestamp();
            overtimeDailyList.forEach(item -> {
                item.setPropertyId(propertyDTO.getId());
                item.setCreatedBy(operatorId);
                item.setCreatedTime(nowTimestamp);
            });
            saveBatch(overtimeDailyList);
        }

        /**
         * 保存 Overtime Month 数据
         *
         * @param propertyDTO 资产
         * @param operatorId  操作人 id
         * @param startDate   报告数据开始日期(yyyyMMdd)
         * @param endDate     报告数据结束日期(yyyyMMdd)
         * @author zc
         * @date 2025/3/17
         */
        private void saveOvertime(PropertyDTO propertyDTO, int operatorId, long startDate, long endDate) {
            ZoneId zoneId = ZoneId.of(propertyDTO.getTimezone());
            // 需要取整月数据来计算
            LocalDate startLocalDate = DateTimeUtils.strToLocalDate(String.valueOf(startDate),
                    BaseConstant.DATE_FORMAT_YYYYMMDD).withDayOfMonth(1);
            LocalDate endLocalDate = DateTimeUtils.strToLocalDate(String.valueOf(endDate),
                    BaseConstant.DATE_FORMAT_YYYYMMDD).with(TemporalAdjusters.lastDayOfMonth());

            // 删除指定日期范围的 Overtime 数据
            overtimeService.deleteByDateRange(propertyDTO.getId(), startLocalDate.atStartOfDay(zoneId).toEpochSecond(),
                    endLocalDate.atStartOfDay(zoneId).toEpochSecond());

            long startOfMonthDate = Long.parseLong(DateTimeUtils.localDateToStr(startLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
            long endOfMonthDate = Long.parseLong(DateTimeUtils.localDateToStr(endLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
            Map<Long, Map<String, List<MorOvertimeDaily>>> overtimeDailyMap = listByDateRange(propertyDTO.getId(),
                    startOfMonthDate, endOfMonthDate).stream().collect(Collectors.groupingBy(item ->
                            DateTimeUtils.strToLocalDate(String.valueOf(item.getTheDate()),
                                    BaseConstant.DATE_FORMAT_YYYYMMDD).withDayOfMonth(1).atStartOfDay(zoneId).toEpochSecond(),
                    Collectors.groupingBy(item -> item.getPositionId() + "&" + item.getWorkDepartmentCode() + "&" + item.getPayRate())));
            long nowTimestamp = DateTimeUtils.nowTimestamp();
            List<Overtime> overtimeList = Lists.newArrayList();
            overtimeDailyMap.forEach((theMonthTimestamp, key2DailyList) ->
                    key2DailyList.forEach((key, dailyGroup) -> {
                        MorOvertimeDaily firstRecord = dailyGroup.get(0);
                        Overtime overtime = new Overtime();
                        overtime.setPropertyId(propertyDTO.getId());
                        overtime.setStartOfMonth(theMonthTimestamp);
                        overtime.setLastName(firstRecord.getLastName());
                        overtime.setFirstName(firstRecord.getFirstName());
                        overtime.setPositionId(firstRecord.getPositionId());
                        overtime.setWorkDepartmentCode(firstRecord.getWorkDepartmentCode());
                        overtime.setPayRate(firstRecord.getPayRate());
                        overtime.setHours(dailyGroup.stream().map(MorOvertimeDaily::getHours)
                                .reduce(BigDecimal.ZERO, BigDecimal::add));
                        overtime.setAmount(dailyGroup.stream().map(MorOvertimeDaily::getAmount)
                                .reduce(BigDecimal.ZERO, BigDecimal::add));
                        overtime.setCreatedBy(operatorId);
                        overtime.setCreatedTime(nowTimestamp);

                        overtimeList.add(overtime);
                    })
            );
            // 保存 Overtime 数据
            overtimeService.saveBatch(overtimeList);
        }

        /**
         * 保存 Overtime Sum 数据
         *
         * @param propertyDTO 资产
         * @param startDate   开始日期(yyyyMMdd)
         * @param endDate     结束日期(yyyyMMdd)
         * @author zc
         * @date 2025/3/17
         */
        private void saveOvertimeSum(PropertyDTO propertyDTO, long startDate, long endDate) {
            String timezone = propertyDTO.getTimezone();
            ZoneId zoneId = ZoneId.of(timezone);
            // 需要取整年数据来计算
            LocalDate startLocalDate = DateTimeUtils.strToLocalDate(String.valueOf(startDate),
                    BaseConstant.DATE_FORMAT_YYYYMMDD);
            LocalDate endLocalDate = DateTimeUtils.strToLocalDate(String.valueOf(endDate),
                    BaseConstant.DATE_FORMAT_YYYYMMDD);
            // 删除指定日期范围的 OvertimeSum 数据
            overtimeSumService.deleteByDateRange(propertyDTO.getId(), startLocalDate.getYear(), endLocalDate.getYear());

            long startTimestamp = startLocalDate.withDayOfYear(1).atStartOfDay(zoneId).toEpochSecond();
            long endTimestamp = endLocalDate.with(TemporalAdjusters.lastDayOfYear()).atStartOfDay(zoneId).toEpochSecond();
            Map<Integer, Map<Long, Map<String, List<Overtime>>>> overtimeMap = overtimeService.listByDateRange(propertyDTO.getId(),
                    startTimestamp, endTimestamp).stream().collect(Collectors.groupingBy(item ->
                            DateTimeUtils.toZonedDateTime(item.getStartOfMonth(), timezone).getYear(),
                    Collectors.groupingBy(Overtime::getStartOfMonth,
                            Collectors.groupingBy(Overtime::getWorkDepartmentCode))));

            List<OvertimeSum> yearList = Lists.newArrayList();
            overtimeMap.forEach((year, month2OvertimeList) -> {
                List<OvertimeSumForMonthDTO> monthList = Lists.newArrayList();
                month2OvertimeList.forEach((month, depCode2OvertimeList) -> {
                    List<OvertimeSumForMonthDepartmentDTO> departmentList = Lists.newArrayList();
                    depCode2OvertimeList.forEach((depCode, overtimeList) ->
                            departmentList.add(OvertimeSumForMonthDepartmentDTO.builder()
                                    .departmentCode(depCode)
                                    .departmentTotal(overtimeList.stream().map(Overtime::getAmount)
                                            .filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO))
                                    .build())
                    );
                    BigDecimal monthTotal = departmentList.stream().map(OvertimeSumForMonthDepartmentDTO::getDepartmentTotal)
                            .filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
                    monthList.add(OvertimeSumForMonthDTO.builder()
                            .month(month)
                            .monthTotal(monthTotal)
                            .departmentList(departmentList)
                            .build());
                });
                BigDecimal yearTotal = monthList.stream().map(OvertimeSumForMonthDTO::getMonthTotal)
                        .filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
                OvertimeSumForYearDTO overtimeSumForYearDTO = OvertimeSumForYearDTO.builder()
                        .year(year)
                        .yearTotal(yearTotal)
                        .monthList(monthList)
                        .build();

                yearList.add(OvertimeSum.builder()
                        .propertyId(propertyDTO.getId())
                        .type(OvertimeSum.TYPE_OVERTIME)
                        .theYear(year)
                        .detail(JsonUtils.convertObjToString(objectMapper, overtimeSumForYearDTO))
                        .build());
            });
            // 保存 OvertimeSum 数据
            overtimeSumService.saveBatch(yearList);
        }

        /**
         * 发送数据变更事件
         *
         * @param propertyList            资产集合
         * @param propertyId2DateRangeMap 报告数据日期范围(yyyyMMdd)
         * @author zc
         * @date 2025/3/17
         */
        private void sendModuleEvent(List<PropertyDTO> propertyList, Map<Integer, List<Long>> propertyId2DateRangeMap) {
            List<MorOvertimeEventBO> overtimeBOList = Lists.newArrayList();
            for (PropertyDTO propertyDTO : propertyList) {
                List<Long> dateRange = propertyId2DateRangeMap.get(propertyDTO.getId());
                long startDate = dateRange.get(0);
                long endDate = dateRange.get(1);

                List<MorOvertimeEventValueBO> list = listByDateRange(propertyDTO.getId(), startDate, endDate).stream()
                        .collect(Collectors.groupingBy(MorOvertimeDaily::getTheDate, Collectors.mapping(MorOvertimeDaily::getAmount,
                                Collectors.reducing(BigDecimal.ZERO, BigDecimal::add)))).entrySet().stream().map(entry ->
                                MorOvertimeEventValueBO.builder()
                                        .theDate(entry.getKey()) // 设置 theDate
                                        .value(entry.getValue()) // 设置累加后的 value
                                        .build()
                        ).toList();
                overtimeBOList.add(MorOvertimeEventBO.builder()
                        .propertyId(propertyDTO.getId())
                        .startDate(startDate)
                        .endDate(endDate)
                        .list(list)
                        .build());
            }
            String messages = JsonUtils.convertObjListToString(objectMapper, overtimeBOList);
            moduleEventSender.publish(MoudleEventSubjectEnum.MOR_OVERTIME_DETAILS.getSubject(), messages);
        }

        /**
         * 获取指定日期范围的Overtime Daily数据
         *
         * @param tenantId    租户 id
         * @param propertyId  资产 id
         * @param loginUserId 用户 id
         * @param query       查询条件
         * @return ListPagedDTO<OvertimeDateRangeDTO>
         * @author lichen
         * @date 2025/10/21
         */
        @Override
        public ListPagedDTO<OvertimeDailyDTO> pageListDateRange(int tenantId, int propertyId, int loginUserId, OvertimeDailyQuery query) {
            if (Objects.isNull(query.getSortOrder())) {
                query.setSortOrder(OvertimeDailyQuery.DEFAULT_SORT_ORDER);
            }
            query.setStartDateInt(Integer.parseInt(query.getStartDate()));
            query.setEndDateInt(Integer.parseInt(query.getEndDate()));

            //Date + Position ID + Work Department Code + Pay Rate分组后再分页
            Page<MorOvertimeDaily> page = new Page<>(query.getPage(), query.getSize());
            List<MorOvertimeDaily> morOvertimeDailyList = baseMapper.groupAndThenPage(page, propertyId, query);

            long total = page.getTotal();
            if (total == 0L) return new ListPagedDTO<>();

            //获取Position ID集合
            Set<String> positionIds = morOvertimeDailyList.stream().map(MorOvertimeDaily::getPositionId).collect(Collectors.toSet());
            //Position ID + first name + last name 分组后再查询
            Map<String, MorOvertimeDaily> positionId2Name = baseMapper.groupPositionIdAndNameAndThenQuery(propertyId, positionIds)
                    .stream().collect(Collectors.toMap(MorOvertimeDaily::getPositionId, Function.identity(), (v1, v2) -> v1));

            // 查询 department desc
            List<String> departmentCodes = morOvertimeDailyList.stream().map(MorOvertimeDaily::getWorkDepartmentCode).distinct().collect(Collectors.toList());
            Map<String, MorDepartment> code2Department = morDepartmentService.list(new QueryWrapper<MorDepartment>().lambda()
                            .eq(MorDepartment::getPropertyId, propertyId)
                            .in(CollectionUtils.isNotEmpty(departmentCodes), MorDepartment::getCode, departmentCodes))
                    .stream().collect(Collectors.toMap(MorDepartment::getCode, v -> v));

            // 校验当前登录用户是否拥有数据权限
            boolean hasDataPermissionAccess = dataPermissionConfigService.verifyDataPermissionAccess(tenantId, propertyId, loginUserId);

            //转化为DTO
            List<OvertimeDailyDTO> overtimeDTOList = beanMapper.mapList(morOvertimeDailyList, OvertimeDailyDTO.class,
                    (item, dto) -> {
                        dto.setDate(item.getTheDate());

                        MorDepartmentDTO morDepartmentDTO = new MorDepartmentDTO();
                        morDepartmentDTO.setCode(item.getWorkDepartmentCode());

                        MorDepartment morDepartment = code2Department.get(item.getWorkDepartmentCode());
                        if (Objects.nonNull(morDepartment)) {
                            morDepartmentDTO.setId(morDepartment.getId());
                            morDepartmentDTO.setDesc(morDepartment.getDesc());
                            morDepartmentDTO.setDescByReport(morDepartment.getDescByReport());
                        }
                        dto.setWorkDepartment(morDepartmentDTO);
                        if (!hasDataPermissionAccess) {
                            dto.setPayRate(null);
                            dto.setAmount(null);
                        }
                        MorOvertimeDaily morOvertimeDaily = positionId2Name.get(item.getPositionId());
                        if (Objects.nonNull(morOvertimeDaily)) {
                            dto.setFirstName(morOvertimeDaily.getFirstName());
                            dto.setLastName(morOvertimeDaily.getLastName());
                        }
                    });
            return new ListPagedDTO<>(total, overtimeDTOList);
        }

        /**
         * 获取指定日期范围的Overtime Daily汇总数据
         *
         * @param tenantId   租户 id
         * @param propertyId 资产 id
         * @param query      筛选条件
         * @return ListPagedDTO<OvertimeDailyDTO>
         * @author lichen
         * @date 2025/10/21
         */
        @Override
        public Map<String, BigDecimal> dailySummary(int tenantId, int propertyId, OvertimeDailySummaryQuery query) {
            Integer startDateInt = Integer.valueOf(query.getStartDate());
            Integer endDateInt = Integer.valueOf(query.getEndDate());

            List<MorOvertimeDaily> morOvertimeDailyList = list(new LambdaQueryWrapper<MorOvertimeDaily>()
                    .eq(MorOvertimeDaily::getPropertyId, propertyId)
                    .between(MorOvertimeDaily::getTheDate, startDateInt, endDateInt)
                    .in(CollectionUtils.isNotEmpty(query.getWorkDepartmentCode()), MorOvertimeDaily::getWorkDepartmentCode, query.getWorkDepartmentCode())
                    .eq(StringUtils.isNotBlank(query.getPositionId()), MorOvertimeDaily::getPositionId, query.getPositionId())
                    .and(StringUtils.isNotBlank(query.getName()), qw -> qw.like(MorOvertimeDaily::getFirstName, query.getName())
                            .or()
                            .like(MorOvertimeDaily::getLastName, query.getName())));

            HashMap<String, BigDecimal> resultMap = new HashMap<>();
            resultMap.put("totalHours", null);
            resultMap.put("totalAmount", null);

            if (CollectionUtils.isEmpty(morOvertimeDailyList)) {
                return resultMap;
            }

            BigDecimal totalHours = morOvertimeDailyList.stream().map(MorOvertimeDaily::getHours).filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            resultMap.put("totalHours", totalHours);
            BigDecimal totalAmount = morOvertimeDailyList.stream().map(MorOvertimeDaily::getAmount).filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            resultMap.put("totalAmount", totalAmount);
            return resultMap;
        }

        /**
         * 获取指定日期范围的Overtime Daily部门汇总数据
         *
         * @param propertyId 资产 id
         * @param query      筛选条件
         * @return cloud.wconnect.workperformance.pojo.dto.mor.MorDepartmentSummaryDTO
         * @author lichen
         * @date 2025/10/22
         */
        @Override
        public MorDepartmentSummaryDTO listDepartmentSummary(int propertyId, MorDepartmentSummaryQuery query) {
            Integer startDateInt = Integer.valueOf(query.getStartDate());
            Integer endDateInt = Integer.valueOf(query.getEndDate());

            List<MorOvertimeDaily> morOvertimeDailyList = list(new LambdaQueryWrapper<MorOvertimeDaily>()
                    .eq(MorOvertimeDaily::getPropertyId, propertyId)
                    .between(MorOvertimeDaily::getTheDate, startDateInt, endDateInt)
                    .in(CollectionUtils.isNotEmpty(query.getWorkDepartmentCode()), MorOvertimeDaily::getWorkDepartmentCode, query.getWorkDepartmentCode()));
            if (CollectionUtils.isEmpty(morOvertimeDailyList)) {
                return new MorDepartmentSummaryDTO(null, Collections.emptyList());
            }

            //所有部门加班总金额
            BigDecimal allDepartmentTotalAmount = morOvertimeDailyList.stream().map(MorOvertimeDaily::getAmount)
                    .filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);

            //按照部门进行分组
            Map<String, List<MorOvertimeDaily>> workDepartmentCode2List = morOvertimeDailyList.stream().collect(Collectors.groupingBy(MorOvertimeDaily::getWorkDepartmentCode));

            //查询部门信息
            List<String> departmentCodes = morOvertimeDailyList.stream().map(MorOvertimeDaily::getWorkDepartmentCode).distinct().collect(Collectors.toList());
            Map<String, MorDepartment> code2Department = morDepartmentService.list(new QueryWrapper<MorDepartment>().lambda()
                            .eq(MorDepartment::getPropertyId, propertyId)
                            .in(CollectionUtils.isNotEmpty(departmentCodes), MorDepartment::getCode, departmentCodes))
                    .stream().collect(Collectors.toMap(MorDepartment::getCode, v -> v));

            ArrayList<MorDepartmentSummaryDetailDTO> list = Lists.newArrayListWithCapacity(departmentCodes.size());

            for (String departmentCode : departmentCodes) {
                MorDepartment morDepartment = code2Department.get(departmentCode);
                if (Objects.nonNull(morDepartment)) {
                    MorDepartmentSummaryDetailDTO dto = beanMapper.map(morDepartment, MorDepartmentSummaryDetailDTO.class);

                    //汇总部门加班总金额
                    BigDecimal totalAmount = workDepartmentCode2List.getOrDefault(departmentCode, Collections.emptyList())
                            .stream().map(MorOvertimeDaily::getAmount).filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
                    dto.setTotalAmount(totalAmount);

                    list.add(dto);
                }
            }
            return new MorDepartmentSummaryDTO(allDepartmentTotalAmount, list);
        }

    }