# ClcArrivalDetailedServiceImpl.java

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

    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.ReportImportResultCons;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.service.PropertyInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.frontoffice.entity.clc.ClcArrivalDetailed;
    import cloud.wconnect.frontoffice.entity.clc.ClcMissingReport;
    import cloud.wconnect.frontoffice.entity.clc.ClcSiteData;
    import cloud.wconnect.frontoffice.mapper.clc.ClcArrivalDetailedMapper;
    import cloud.wconnect.frontoffice.pojo.bo.clc.ArrivalBO;
    import cloud.wconnect.frontoffice.pojo.bo.clc.DataBO;
    import cloud.wconnect.frontoffice.pojo.dto.clc.ArrivalCountDTO;
    import cloud.wconnect.frontoffice.pojo.dto.clc.UpdateArrivalStatusDTO;
    import cloud.wconnect.frontoffice.pojo.query.clc.ArrivalQuery;
    import cloud.wconnect.frontoffice.service.clc.IClcArrivalDetailedService;
    import cloud.wconnect.frontoffice.service.clc.IClcMissingReportService;
    import cloud.wconnect.frontoffice.service.clc.IClcSiteDataService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    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.google.common.collect.Lists;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import jakarta.annotation.Resource;
    import java.time.LocalDate;
    import java.time.ZoneId;
    import java.time.format.DateTimeFormatter;
    import java.util.*;
    import java.util.stream.Collectors;

    import static java.util.stream.Collectors.toList;

    /**
     * <p>
     * 服务实现类
     * </p>
     *
     * @author yyt
     * @since 2023-07-24
     */
    @Service
    @DS("logbook")
    public class ClcArrivalDetailedServiceImpl extends ServiceImpl<ClcArrivalDetailedMapper, ClcArrivalDetailed> implements IClcArrivalDetailedService {

        @Resource
        private ClcArrivalDetailedMapper clcArrivalDetailedMapper;
        @Autowired
        private IClcSiteDataService clcSiteDataService;
        @Autowired
        private IClcMissingReportService clcMissingReportService;
        @Autowired
        private PropertyInternalService propertyInternalService;

        /**
         * 统计 arrival 各状态数据
         *
         * @param propertyId 资产标识
         * @param query      查询条件
         * @return arrival 各状态数据
         * @author yyt
         * @date 2023/7/24
         */
        @Override
        public List<ArrivalCountDTO> countArrival(Integer propertyId, ArrivalQuery query) {
            return clcArrivalDetailedMapper.countArrival(propertyId, query);
        }

        /**
         * 校验存在
         *
         * @param id arrival id
         * @return arrival
         * @author yyt
         * @date 2023/7/24
         */
        @Override
        public ClcArrivalDetailed checkExist(long id) {
            ClcArrivalDetailed arrivalDetailed = getById(id);
            if (Objects.isNull(arrivalDetailed)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the clcArrivalDetailed is not found;id" + id);
            }
            return arrivalDetailed;
        }

        /**
         * 处理 入住信息数据
         *
         * @param property 资产
         * @param operator 操作人
         * @param dataBO   入住信息
         * @author yyt
         * @date 2023/7/27
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleArrivalData(PropertyDTO property, int operator, DataBO<ArrivalBO> dataBO) {
            // 插入数据
            insertData(property, operator, dataBO);

            // 处理匹配逻辑
            matchClcRecord(property.getId(), dataBO.getDate());
        }

        /**
         * 批量插入数据
         *
         * @param property 资产
         * @param operator 操作人
         * @param dataBO   入住信息
         * @author yyt
         * @date 2023/7/27
         */
        private void insertData(PropertyDTO property, int operator, DataBO<ArrivalBO> dataBO) {
            long nowTime = DateTimeUtils.nowTimestamp();
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.ENGLISH);
            Integer propertyId = property.getId();
            ZoneId zoneId = ZoneId.of(property.getTimezone());

            List<ClcArrivalDetailed> arrivalDetails = dataBO.getData().stream().map(item -> {
                ClcArrivalDetailed arrivalDetailed = new ClcArrivalDetailed();
                arrivalDetailed.setPropertyId(propertyId);
                arrivalDetailed.setArrivDate(LocalDate.parse(item.getArrivDate(), dateTimeFormatter).atStartOfDay(zoneId).toEpochSecond());
                arrivalDetailed.setDepartDate(LocalDate.parse(item.getDepartDate(), dateTimeFormatter).atStartOfDay(zoneId).toEpochSecond());
                arrivalDetailed.setRoomNumber(item.getRoomNumber());
                arrivalDetailed.setConfNumber(item.getConfNumber());
                arrivalDetailed.setGuestName(item.getGuestName());
                arrivalDetailed.setCompany(item.getCompany());
                arrivalDetailed.setMatchResult(ClcArrivalDetailed.MATCH_RESULT_MISSED);
                arrivalDetailed.setRoomType(item.getRoomType());
                arrivalDetailed.setCreatedBy(operator);
                arrivalDetailed.setCreatedTime(nowTime);
                return arrivalDetailed;
            }).collect(toList());

            Map<String, ClcArrivalDetailed> existArrivals = baseMapper.listData(arrivalDetails).stream()
                    .collect(Collectors.toMap(ClcArrivalDetailed::getUniKey, v -> v));
            List<ClcArrivalDetailed> insertOrUpdateData = arrivalDetails.stream().filter(item -> {
                ClcArrivalDetailed existArrival = existArrivals.get(item.getUniKey());
                // 过滤出未处理过的数据
                return Objects.isNull(existArrival) || existArrival.getOperatedBy() == 0;
            }).collect(toList());
            if (!insertOrUpdateData.isEmpty()) {
                // 根据唯一索引条件排序
                insertOrUpdateData.sort(Comparator.comparing(ClcArrivalDetailed::getArrivDate).thenComparing(ClcArrivalDetailed::getRoomNumber));

                baseMapper.saveOrUpdateBatch(insertOrUpdateData);
            }
        }

        /**
         * 匹配 clc 信息
         *
         * @param propertyId 资产 id
         * @param date       日期
         * @author yyt
         * @date 2023/7/27
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void matchClcRecord(Integer propertyId, String date) {
            // 获取目前所有未匹配到CLC数据的Arrivals Detailed集合
            List<ClcArrivalDetailed> missedArrivlDetailedList = list(new QueryWrapper<ClcArrivalDetailed>()
                    .lambda().eq(ClcArrivalDetailed::getPropertyId, propertyId)
                    .in(ClcArrivalDetailed::getMatchResult, Lists.newArrayList(ClcArrivalDetailed.MATCH_RESULT_MISSED,
                            ClcArrivalDetailed.MATCH_RESULT_CLC_ERR)));
            if (missedArrivlDetailedList.isEmpty()) {
                return;
            }

            // 获取目前所有未匹配到CLC数据的Arrivals Detailed记录中的入住日期集合
            List<Long> checkInDates = missedArrivlDetailedList.stream().map(ClcArrivalDetailed::getArrivDate).collect(toList());
            // 获取指定资产下在指定多个入住日期内每天的CLC数据集合
            Map<Long, List<ClcSiteData>> clcDatasByCheckInDate = clcSiteDataService.list(new QueryWrapper<ClcSiteData>()
                            .lambda().eq(ClcSiteData::getPropertyId, propertyId).in(ClcSiteData::getCheckInDate, checkInDates))
                    .stream().collect(Collectors.groupingBy(ClcSiteData::getCheckInDate));

            // 判断最近一次的CLC数据是否同步成功
            ClcMissingReport missingReport = clcMissingReportService.getOne(new QueryWrapper<ClcMissingReport>()
                    .lambda().eq(ClcMissingReport::getPropertyId, propertyId)
                    .eq(ClcMissingReport::getReportType, ClcMissingReport.REPORT_TYPE_CLC)
                    .orderByDesc(ClcMissingReport::getTheDate).last("limit 1"));
            boolean hasSyncedClcDatas = missingReport == null || missingReport.getStatus() == ReportImportResultCons.IMPORT_RESULT_SUCCESS;

            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.ENGLISH);
            String timeZone = propertyInternalService.getOne(propertyId).getTimezone();
            long targetDate = LocalDate.parse(date, dateTimeFormatter).atStartOfDay(ZoneId.of(timeZone)).toEpochSecond();

            List<ClcArrivalDetailed> updateResults = Lists.newArrayListWithExpectedSize(missedArrivlDetailedList.size());
            for (ClcArrivalDetailed arrivalDetailed : missedArrivlDetailedList) {
                Long arrivalDetailedId = arrivalDetailed.getId();
                long arrivDate = arrivalDetailed.getArrivDate();
                List<ClcSiteData> clcSiteDatas = clcDatasByCheckInDate.get(arrivDate);
                // 根据同一入住日期内的Arrival Detailed数据和CLC数据，计算得出匹配结果
                boolean matched = Objects.nonNull(clcSiteDataService.getMatchRecord(arrivalDetailed, clcSiteDatas, true));

                if (matched) {
                    // 匹配到CLC数据
                    ClcArrivalDetailed updateContent = new ClcArrivalDetailed();
                    updateContent.setId(arrivalDetailedId);
                    updateContent.setMatchResult(ClcArrivalDetailed.MATCH_RESULT_MATCHED);
                    updateResults.add(updateContent);
                } else {
                    if (hasSyncedClcDatas) {
                        // 当前天的CLC数据同步成功，但仍未匹配成功
                        int matchedResult = arrivalDetailed.getMatchResult();
                        if (matchedResult == ClcArrivalDetailed.MATCH_RESULT_CLC_ERR) {
                            // 所有之前匹配结果为CLC Error的数据，匹配结果改为Missed
                            ClcArrivalDetailed updateContent = new ClcArrivalDetailed();
                            updateContent.setId(arrivalDetailedId);
                            updateContent.setMatchResult(ClcArrivalDetailed.MATCH_RESULT_MISSED);
                            updateResults.add(updateContent);
                        }
                    } else {
                        // 当前天的CLC数据同步失败导致未匹配成功
                        if (arrivDate == targetDate) {
                            // 当前天内的数据匹配结果为CLC Error，之前的匹配结果保持不变
                            ClcArrivalDetailed updateContent = new ClcArrivalDetailed();
                            updateContent.setId(arrivalDetailedId);
                            updateContent.setMatchResult(ClcArrivalDetailed.MATCH_RESULT_CLC_ERR);
                            updateResults.add(updateContent);
                        }
                    }
                }
            }

            // 批量更新匹配结果
            updateBatchById(updateResults);
        }

        /**
         * 分页查询数据
         *
         * @param propertyId 资产 id
         * @param query      查询条件
         * @return arrival 数据
         * @author yyt
         * @date 2024/11/19
         **/
        @Override
        public Page<ClcArrivalDetailed> listPage(Integer propertyId, ArrivalQuery query) {
            return page(new Page<>(query.getPage(), query.getSize()), new QueryWrapper<ClcArrivalDetailed>()
                    .lambda().eq(ClcArrivalDetailed::getPropertyId, propertyId)
                    .between(Objects.nonNull(query.getFromDate()) && Objects.nonNull(query.getToDate()),
                            ClcArrivalDetailed::getArrivDate, query.getFromDate(), query.getToDate())
                    .like(StringUtils.isNotEmpty(query.getRoomNumber()), ClcArrivalDetailed::getRoomNumber, query.getRoomNumber())
                    .in(CollectionUtils.isNotEmpty(query.getMatchResults()), ClcArrivalDetailed::getMatchResult, query.getMatchResults())
                    .orderByDesc(ClcArrivalDetailed::getId));
        }

        /**
         * 查询数据
         *
         * @param propertyId 资产 id
         * @param fromDate   开始日期
         * @param toDate     结束日期
         * @param status     状态
         * @return arrival 数据
         * @author yyt
         * @date 2024/11/19
         **/
        @Override
        public List<ClcArrivalDetailed> list(Integer propertyId, long fromDate, long toDate, int status) {
            return list(new QueryWrapper<ClcArrivalDetailed>().lambda()
                    .eq(ClcArrivalDetailed::getPropertyId, propertyId)
                    .between(ClcArrivalDetailed::getArrivDate, fromDate, toDate)
                    .eq(ClcArrivalDetailed::getMatchResult, status)
                    .orderByDesc(ClcArrivalDetailed::getId));
        }

        /**
         * 更新入住信息状态
         *
         * @param id                     入住信息 id
         * @param updateArrivalStatusDTO 状态信息
         * @param userId                 操作人
         * @author yyt
         * @date 2026/1/19
         **/
        @Override
        public void update(long id, UpdateArrivalStatusDTO updateArrivalStatusDTO, int userId) {
            ClcArrivalDetailed updateContent = new ClcArrivalDetailed();
            updateContent.setOperatedBy(userId);
            updateContent.setOperatedTime(DateTimeUtils.nowTimestamp());
            updateContent.setMatchResult(updateArrivalStatusDTO.getResult());
            updateContent.setOperatedRemark(updateArrivalStatusDTO.getRemark());
            updateContent.setId(id);
            updateById(updateContent);
        }

    }