# ShiftReportInfoServiceImpl.java

    package cloud.wconnect.frontoffice.service.endofshift.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.frontoffice.entity.endofshift.EosShiftConfig;
    import cloud.wconnect.frontoffice.entity.endofshift.ShiftReportInfo;
    import cloud.wconnect.frontoffice.mapper.endofshift.ShiftReportInfoMapper;
    import cloud.wconnect.frontoffice.pojo.dto.EosShiftDetailDTO;
    import cloud.wconnect.frontoffice.pojo.dto.endofshift.ShiftInfoDTO;
    import cloud.wconnect.frontoffice.pojo.dto.endofshift.report.HistoryAndForecastForEndOfShiftReportDTO;
    import cloud.wconnect.frontoffice.pojo.dto.endofshift.report.HouseStatusServiceDTO;
    import cloud.wconnect.frontoffice.service.endofshift.IEosShiftConfigService;
    import cloud.wconnect.frontoffice.service.endofshift.IShiftReportInfoService;
    import cloud.wconnect.frontoffice.service.endofshift.ShiftTimeUtil;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    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 lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.text.MessageFormat;
    import java.time.LocalDate;
    import java.time.LocalTime;
    import java.time.ZoneId;
    import java.util.*;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * 服务实现类: ShiftReportInfo
     * </p>
     *
     * @author zc
     * @since 2023-02-15
     */
    @Service
    @Slf4j
    @DS("ratelog_manage")
    public class ShiftReportInfoServiceImpl extends ServiceImpl<ShiftReportInfoMapper, ShiftReportInfo> implements IShiftReportInfoService {

        @Autowired
        @Lazy
        private IEosShiftConfigService shiftConfigService;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private ObjectMapper objectMapper;

        /**
         * 用History And Forecast报告数据更新资产指定日期的排班信息
         *
         * @param propertyDTO        资产DTO
         * @param historyAndForecast 报告数据
         * @return void
         * @author lichen
         * @date 2025/6/20
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void updateHistoryAndForecastData(PropertyDTO propertyDTO, HistoryAndForecastForEndOfShiftReportDTO historyAndForecast) {
            //获取当前日期
            LocalDate currentLocalDate = LocalDate.now(ZoneId.of(propertyDTO.getTimezone()));
            Integer targetDate = Integer.parseInt(DateTimeUtils.localDateToStr(currentLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD));

            //查询目标日期命中的排班配置
            EosShiftConfig eosShiftConfig = shiftConfigService.getTargetDateHitShiftConfig(propertyDTO.getId(), targetDate);
            if (Objects.isNull(eosShiftConfig)) {
                log.warn("The property:{} has no shift config on the target date:{}", propertyDTO.getId(), targetDate);
                return;
            }

            String calculationRule = propertyDTO.getInternationalization().getNumber().getCalculationRule();
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(calculationRule);

            //班次详情DTO集合
            List<EosShiftDetailDTO> shiftDetailDTOS = JsonUtils.convertStringToList(objectMapper, eosShiftConfig.getShiftConfig(), EosShiftDetailDTO.class);

            //获取当前时间
            LocalTime localTime = LocalTime.now(ZoneId.of(propertyDTO.getTimezone()));
            //当前时间的分钟数
            int nowMinutes = ShiftTimeUtil.timeToMinutes(localTime);

            //当前时间
            long nowTimestamp = DateTimeUtils.nowTimestamp();

            List<ShiftReportInfo> shiftReportInfos = shiftDetailDTOS.stream()
                    .filter(shiftDetailDTO -> {
                        // 跨天的排班，当History And Forecast报告过来时，都需要更新
                        if (ShiftTimeUtil.isCrossDay(shiftDetailDTO.getStartTime(), shiftDetailDTO.getEndTime())) {
                            return true;
                        } else {
                            // 当前时间小于班次的结束时间，表示当前班次未开始或进行中，需要更新
                            return nowMinutes <= ShiftTimeUtil.timeToMinutes(shiftDetailDTO.getEndTime());
                        }
                    })
                    .map(shiftDetailDTO -> {
                        ShiftReportInfo shiftReportInfo = packageInitShiftReportInfo(propertyDTO.getId(), targetDate, eosShiftConfig.getId(), shiftDetailDTO);
                        //设置History And Forecast报告中的字段
                        shiftReportInfo.setArrivals(historyAndForecast.getArrRooms());
                        shiftReportInfo.setDepartures(historyAndForecast.getDepRooms());
                        shiftReportInfo.setExpOcc(new BigDecimal(historyAndForecast.getOccPercent()).setScale(BaseConstant.DATA_PRECISION, roundingMode));
                        shiftReportInfo.setCompRooms(historyAndForecast.getCompRooms());
                        shiftReportInfo.setOooRooms(historyAndForecast.getOooRooms());
                        //设置操作人信息
                        shiftReportInfo.setOperateUid(0);
                        shiftReportInfo.setOperateTime(nowTimestamp);
                        return shiftReportInfo;
                    }).toList();
            if (CollectionUtils.isNotEmpty(shiftReportInfos)) {
                //根据唯一索引新增或修改数据
                baseMapper.insertOrUpdateHistoryAndForecastData(shiftReportInfos);
            }
        }

        /**
         * 包装初始化的排班基础信息（只处理非空字段）
         *
         * @param propertyId     资产id
         * @param targetDate     日期（yyyyMMdd）
         * @param shiftDetailDTO 班次详情DTO
         * @return java.lang.Object
         * @author lichen
         * @date 2025/6/20
         */
        private ShiftReportInfo packageInitShiftReportInfo(Integer propertyId, Integer targetDate, Integer shiftConfigId, EosShiftDetailDTO shiftDetailDTO) {
            // 组装参数
            ShiftReportInfo shiftReportInfo = new ShiftReportInfo();
            shiftReportInfo.setPropertyId(propertyId);
            shiftReportInfo.setTargetDate(targetDate);
            shiftReportInfo.setShiftConfigId(shiftConfigId);
            shiftReportInfo.setShift(shiftDetailDTO.getShift());
            shiftReportInfo.setShiftSign(ShiftReportInfo.generateShiftSign(targetDate, shiftDetailDTO.getStartTime()));
            shiftReportInfo.setGsr1("");
            shiftReportInfo.setGsr2("");
            shiftReportInfo.setGsr3("");
            shiftReportInfo.setMaintenance("");
            shiftReportInfo.setHouseman("");
            shiftReportInfo.setDriver1("");
            shiftReportInfo.setDriver2("");
            shiftReportInfo.setSecurity("");
            shiftReportInfo.setGatekeeper("");
            shiftReportInfo.setEnrollmentJson("");
            shiftReportInfo.setResumeJson("");
            shiftReportInfo.setPassOnJson("");
            shiftReportInfo.setIssuesJson("");
            shiftReportInfo.setSendBy(0);
            shiftReportInfo.setSendTime(0L);
            shiftReportInfo.setCanSend(true);
            shiftReportInfo.setOperateUid(0);
            shiftReportInfo.setOperateTime(0L);
            return shiftReportInfo;
        }

        /**
         * 更新排班可发送邮件状态
         *
         * @param shiftReportInfoId 排班id
         * @param canSend           是否可发送邮件
         * @return void
         * @author lichen
         * @date 2025/6/19
         */
        @Override
        public void updateCanSend(Integer shiftReportInfoId, boolean canSend) {
            //更新为可发送邮件
            ShiftReportInfo updateShiftInfo = new ShiftReportInfo();
            updateShiftInfo.setCanSend(canSend);
            updateShiftInfo.setId(shiftReportInfoId);
            updateById(updateShiftInfo);
        }

        /**
         * 根据id分页查询排班信息
         *
         * @param page 页码
         * @param size 每页大小
         * @param ids  排班id集合
         * @return com.baomidou.mybatisplus.extension.plugins.pagination.Page<cloud.wconnect.frontoffice.entity.endofshift.ShiftReportInfo>
         * @author lichen
         * @date 2025/6/20
         */
        @Override
        public Page<ShiftReportInfo> pageByIds(Integer page, Integer size, Collection<Integer> ids) {
            return page(new Page<>(page, size), new LambdaQueryWrapper<ShiftReportInfo>().in(ShiftReportInfo::getId, ids));
        }

        /**
         * 用House status/service报告数据更新资产指定日期的排班信息
         *
         * @param propertyDTO           资产DTO
         * @param houseStatusServiceDTO House status/service报告数据
         * @return void
         * @author lichen
         * @date 2025/6/20
         */
        @Override
        public void updateHouseStatusData(PropertyDTO propertyDTO, HouseStatusServiceDTO houseStatusServiceDTO) {
            //获取当前日期
            LocalDate currentLocalDate = LocalDate.now(ZoneId.of(propertyDTO.getTimezone()));
            Integer targetDate = Integer.parseInt(DateTimeUtils.localDateToStr(currentLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD));

            //查询目标日期命中的排班配置
            EosShiftConfig eosShiftConfig = shiftConfigService.getTargetDateHitShiftConfig(propertyDTO.getId(), targetDate);
            if (Objects.isNull(eosShiftConfig)) {
                log.warn("The property:{} has no shift config on the target date:{}", propertyDTO.getId(), targetDate);
                return;
            }
            //排班配置详情
            List<EosShiftDetailDTO> shiftDetailDTOS = JsonUtils.convertStringToList(objectMapper, eosShiftConfig.getShiftConfig(), EosShiftDetailDTO.class);

            //获取当前时间
            LocalTime localTime = LocalTime.now(ZoneId.of(propertyDTO.getTimezone()));
            //当前时间的分钟数
            int nowMinutes = ShiftTimeUtil.timeToMinutes(localTime);

            //当前时间
            long nowTimestamp = DateTimeUtils.nowTimestamp();

            List<ShiftReportInfo> shiftReportInfos = shiftDetailDTOS.stream()
                    .filter(shiftDetailDTO -> {
                        // 跨天的排班，当House Status 报告过来时，都需要更新
                        if (ShiftTimeUtil.isCrossDay(shiftDetailDTO.getStartTime(), shiftDetailDTO.getEndTime())) {
                            return true;
                        } else {
                            // 当前时间小于班次的结束时间，表示当前班次未开始或进行中，需要更新
                            return nowMinutes <= ShiftTimeUtil.timeToMinutes(shiftDetailDTO.getEndTime());
                        }
                    })
                    .map(shiftDetailDTO -> {
                        ShiftReportInfo shiftReportInfo = packageInitShiftReportInfo(propertyDTO.getId(), targetDate, eosShiftConfig.getId(), shiftDetailDTO);
                        //设置House status/service报告数据
                        shiftReportInfo.setVdRooms(houseStatusServiceDTO.getVdRooms());
                        shiftReportInfo.setOosRooms(houseStatusServiceDTO.getOosRooms());
                        //设置操作人信息
                        shiftReportInfo.setOperateUid(0);
                        shiftReportInfo.setOperateTime(nowTimestamp);
                        return shiftReportInfo;
                    }).toList();

            if (CollectionUtils.isNotEmpty(shiftReportInfos)) {
                //根据唯一索引新增或修改数据
                baseMapper.insertOrUpdateHistoryAndForecastData(shiftReportInfos);
            }
        }

        /**
         * 查询指定资产指定日期的排班信息集合
         *
         * @param propertyId  资产id
         * @param targetDate  目标日期（yyyyMMdd）
         * @param shiftConfig 目标日期的排班配置
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.dto.endofshift.ShiftInfoDTO>
         * @author lichen
         * @date 2025/6/23
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public List<ShiftInfoDTO> listByTargetDate(int propertyId, Integer targetDate, EosShiftConfig shiftConfig) {
            //查询指定日期的排班集合
            List<ShiftReportInfo> shiftReportInfoList = list(new LambdaQueryWrapper<ShiftReportInfo>().eq(ShiftReportInfo::getPropertyId, propertyId)
                    .eq(ShiftReportInfo::getTargetDate, targetDate));

            //存在的班次id集合
            List<Integer> existingShifts = shiftReportInfoList.stream().map(ShiftReportInfo::getShift).toList();

            //班次信息集合
            List<EosShiftDetailDTO> shiftDetailDTOS = JsonUtils.convertStringToList(objectMapper, shiftConfig.getShiftConfig(), EosShiftDetailDTO.class);
            Map<Integer, EosShiftDetailDTO> shift2ShiftDetailDTO = shiftDetailDTOS.stream().collect(
                    Collectors.toMap(EosShiftDetailDTO::getShift, Function.identity()));

            //初始化排班数据
            List<ShiftReportInfo> initList = shiftDetailDTOS.stream().filter(shiftDetailDTO -> !existingShifts.contains(shiftDetailDTO.getShift()))
                    .map(shiftDetailDTO -> packageInitShiftReportInfo(propertyId, targetDate, shiftConfig.getId(), shiftDetailDTO))
                    .toList();
            if (CollectionUtils.isNotEmpty(initList)) {
                saveBatch(initList);
            }

            //汇总指定日期的排班数据
            ArrayList<ShiftReportInfo> allShiftReportInfoList = Lists.newArrayListWithCapacity(shiftDetailDTOS.size());
            if (CollectionUtils.isNotEmpty(initList)) {
                allShiftReportInfoList.addAll(initList);
            }
            if (CollectionUtils.isNotEmpty(shiftReportInfoList)) {
                allShiftReportInfoList.addAll(shiftReportInfoList);
            }

            return beanMapper.mapList(allShiftReportInfoList, ShiftInfoDTO.class, (entity, dto) -> {
                EosShiftDetailDTO shiftDetailDTO = shift2ShiftDetailDTO.get(entity.getShift());
                dto.setShiftName(shiftDetailDTO.getShiftName());
                dto.setStartTime(shiftDetailDTO.getStartTime());
                dto.setEndTime(shiftDetailDTO.getEndTime());
            }).stream().sorted(Comparator.comparing(dto -> {
                //开始时间转换成LocalTime
                return LocalTime.parse(dto.getStartTime());
            })).toList();
        }

        /**
         * 查询大于等于指定日期的排班信息
         *
         * @param propertyId 资产id
         * @param targetDate 指定日期（yyyyMMdd）
         * @return java.util.List<cloud.wconnect.frontoffice.entity.endofshift.ShiftReportInfo>
         * @author lichen
         * @date 2025/6/24
         */
        @Override
        public List<ShiftReportInfo> listByGeTargetDate(Integer propertyId, Integer targetDate) {
            return list(new LambdaQueryWrapper<ShiftReportInfo>().eq(ShiftReportInfo::getPropertyId, propertyId)
                    .ge(ShiftReportInfo::getTargetDate, targetDate));
        }

        /**
         * 初始化多个日期的排班信息
         *
         * @param propertyId      资产id
         * @param targetDateList  日期集合
         * @param shiftConfigId   排班配置id
         * @param shiftDetailDTOS 班次详情DTO集合
         * @return java.util.List<cloud.wconnect.frontoffice.entity.endofshift.ShiftReportInfo>
         * @author lichen
         * @date 2025/6/24
         */
        @Override
        public List<ShiftReportInfo> initMultiTargetDateShiftReportInfo(Integer propertyId, Collection<Integer> targetDateList,
                                                                        Integer shiftConfigId, List<EosShiftDetailDTO> shiftDetailDTOS) {
            ArrayList<ShiftReportInfo> insertBatch = Lists.newArrayListWithCapacity(targetDateList.size() * shiftDetailDTOS.size());
            for (Integer targetDate : targetDateList) {
                for (EosShiftDetailDTO shiftConfigDetailDTO : shiftDetailDTOS) {
                    insertBatch.add(packageInitShiftReportInfo(propertyId, targetDate, shiftConfigId, shiftConfigDetailDTO));
                }
            }
            saveBatch(insertBatch);
            return insertBatch;
        }

        /**
         * 校验指定排班信息id的排班数据是否存在
         *
         * @param shiftReprortInfoId 排班信息id
         * @return cloud.wconnect.frontoffice.entity.endofshift.ShiftReportInfo
         * @author lichen
         * @date 2025/6/24
         */
        @Override
        public ShiftReportInfo checkExist(Integer shiftReprortInfoId) {
            ShiftReportInfo shiftReportInfo = getById(shiftReprortInfoId);
            if (Objects.isNull(shiftReportInfo)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "The End Of Shift does not exist");
            }
            return shiftReportInfo;
        }

        /**
         * 校验是否归属于当前资产
         *
         * @param propertyId      资产id
         * @param shiftReportInfo 排班信息
         * @return void
         * @author lichen
         * @date 2025/6/24
         */
        @Override
        public void checkBelong(Integer propertyId, ShiftReportInfo shiftReportInfo) {
            if (!propertyId.equals(shiftReportInfo.getPropertyId())) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        MessageFormat.format("End of Shift shift report info not belong property, shiftReportInfoId:{0}, belongPropertyId:{1}, propertyId:{2}",
                                shiftReportInfo.getId(), shiftReportInfo.getPropertyId(), propertyId));
            }
        }

    }