# GmwrSelloutEfficiencyServiceImpl.java

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

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.RoomsConfigDTO;
    import cloud.wconnect.commonlib.service.RoomsConfigInternalService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.workperformance.cons.gmwr.GmwrCons;
    import cloud.wconnect.workperformance.cons.gmwr.selloutefficiency.GmwrSelloutEfficiencyCons;
    import cloud.wconnect.workperformance.cons.gmwr.selloutefficiency.GmwrSelloutEfficiencyEnum;
    import cloud.wconnect.workperformance.entity.gmwr.GmwrDailySummaryOcc;
    import cloud.wconnect.workperformance.entity.gmwr.GmwrSelloutEfficiency;
    import cloud.wconnect.workperformance.entity.gmwr.GmwrSelloutEfficiencyNote;
    import cloud.wconnect.workperformance.entity.gmwr.GmwrWeeks;
    import cloud.wconnect.workperformance.mapper.gmwr.GmwrSelloutEfficiencyMapper;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.note.GmwrNoteDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.selloutefficiency.GmwrOccSelloutConfigDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.selloutefficiency.GmwrSelloutEfficiencyDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.selloutefficiency.GmwrSelloutEfficiencyPackageDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.selloutefficiency.GmwrSelloutEfficiencyValueDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.selloutefficiency.basic.GmwrSelloutEfficiencyBasicDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.selloutefficiency.basic.GmwrSelloutEfficiencyBasicValueDTO;
    import cloud.wconnect.workperformance.pojo.dto.internal.OooRoomCountInternalDTO;
    import cloud.wconnect.workperformance.service.OooRoomInternalService;
    import cloud.wconnect.workperformance.service.gmwr.IGmwrDailySummaryOccService;
    import cloud.wconnect.workperformance.service.gmwr.IGmwrOccSelloutConfigService;
    import cloud.wconnect.workperformance.service.gmwr.IGmwrSelloutEfficiencyNoteService;
    import cloud.wconnect.workperformance.service.gmwr.IGmwrSelloutEfficiencyService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Maps;
    import jakarta.annotation.Resource;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.time.LocalDate;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.Set;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * GM Weekly Report Sellout Efficiency 服务实现类
     * </p>
     *
     * @author zc
     * @since 2024-10-12
     */
    @Service
    @DS("work_performance")
    public class GmwrSelloutEfficiencyServiceImpl extends ServiceImpl<GmwrSelloutEfficiencyMapper, GmwrSelloutEfficiency> implements IGmwrSelloutEfficiencyService {
        @Resource
        private RoomsConfigInternalService roomsConfigInternalService;
        @Resource
        private OooRoomInternalService oooRoomInternalService;
        @Resource
        private IGmwrOccSelloutConfigService occSelloutConfigService;
        @Resource
        private IGmwrDailySummaryOccService dailySummaryOccService;
        @Resource
        private IGmwrSelloutEfficiencyNoteService selloutEfficiencyNoteService;
        @Resource
        private UserInternalService userInternalService;
        @Resource
        private ObjectMapper objectMapper;
        @Resource
        private BeanMapper beanMapper;

        /**
         * 生成 Sellout Efficiency 数据
         *
         * @param propertyDTO 资产
         * @param gmwrWeeks   周报数据
         * @author zc
         * @date 2024/10/20
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void generate(PropertyDTO propertyDTO, GmwrWeeks gmwrWeeks) {
            // 获取房间数配置
            RoomsConfigDTO roomsConfigDTO = roomsConfigInternalService.getRoomsConfig(propertyDTO.getId());
            Integer totalRooms = Objects.isNull(roomsConfigDTO) ? null : roomsConfigDTO.getRooms();

            // 由于生成的是前一周的数据，因此需要获取前一周的开始和结束日期
            LocalDate startLocalDate = DateTimeUtils.strToLocalDate(gmwrWeeks.getWeek().toString(),
                    BaseConstant.DATE_FORMAT_YYYYMMDD).minusDays(7);
            long startDate = Long.parseLong(DateTimeUtils.localDateToStr(startLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
            LocalDate endLocalDate = startLocalDate.plusDays(6);
            long endDate = Long.parseLong(DateTimeUtils.localDateToStr(endLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD));

            // 获取资产配置的 OCC 时刻
            GmwrOccSelloutConfigDTO configDTO = occSelloutConfigService.getByPropertyId(propertyDTO.getId());
            // 获取 Daily Summary OCC
            List<Integer> hours = List.of(configDTO.getOccHour(), GmwrSelloutEfficiencyCons.FINAL_OCC_HOUR);
            List<GmwrDailySummaryOcc> dailySummaryOccList = dailySummaryOccService.list(propertyDTO.getId(), startDate, endDate, hours);

            // 获取指定日期未完成的 OOO 房间数
            List<OooRoomCountInternalDTO> oooRoomDTOList = oooRoomInternalService.listCount(propertyDTO.getId(), startDate, endDate);

            // 生成 Basic DTO 数据
            List<GmwrSelloutEfficiencyBasicDTO> basicDTOList = generateBasicDTOList(configDTO, totalRooms,
                    startLocalDate, endLocalDate, dailySummaryOccList, oooRoomDTOList);

            GmwrSelloutEfficiency selloutEfficiency = new GmwrSelloutEfficiency();
            selloutEfficiency.setPropertyId(propertyDTO.getId());
            selloutEfficiency.setReportId(gmwrWeeks.getId());
            selloutEfficiency.setTotalRooms(totalRooms);
            selloutEfficiency.setDetail(JsonUtils.convertObjListToString(objectMapper, basicDTOList));
            selloutEfficiency.setConfigDetail(JsonUtils.convertObjToString(objectMapper, configDTO));

            baseMapper.insertOrUpdateByUnique(selloutEfficiency);
        }

        /**
         * 生成 Basic DTO 数据
         *
         * @param configDTO           OCC 配置
         * @param totalRooms          总房间数量
         * @param startLocalDate      开始日期
         * @param endLocalDate        结束日期
         * @param dailySummaryOccList Daily Summary OCC 数据
         * @param oooRoomDTOList      OOO Room Count 数据
         * @return GmwrSelloutEfficiencyBasicDTO 集合
         * @author zc
         * @date 2024/10/21
         */
        private List<GmwrSelloutEfficiencyBasicDTO> generateBasicDTOList(GmwrOccSelloutConfigDTO configDTO, Integer totalRooms,
                                                                         LocalDate startLocalDate, LocalDate endLocalDate,
                                                                         List<GmwrDailySummaryOcc> dailySummaryOccList,
                                                                         List<OooRoomCountInternalDTO> oooRoomDTOList) {
            Map<Long, Map<Integer, GmwrDailySummaryOcc>> hour2OccByDate = dailySummaryOccList.stream()
                    .collect(Collectors.groupingBy(GmwrDailySummaryOcc::getDate,
                            Collectors.toMap(GmwrDailySummaryOcc::getHour, Function.identity())));

            Map<Long, OooRoomCountInternalDTO> date2OooCountDTO = oooRoomDTOList.stream()
                    .collect(Collectors.toMap(OooRoomCountInternalDTO::getDate, Function.identity()));

            List<GmwrSelloutEfficiencyBasicValueDTO> occList = Lists.newArrayList();
            List<GmwrSelloutEfficiencyBasicValueDTO> oooRoomsList = Lists.newArrayList();
            List<GmwrSelloutEfficiencyBasicValueDTO> roomsSoldList = Lists.newArrayList();
            List<GmwrSelloutEfficiencyBasicValueDTO> roomsUnsoldList = Lists.newArrayList();
            List<GmwrSelloutEfficiencyBasicValueDTO> roomTotalList = Lists.newArrayList();
            List<GmwrSelloutEfficiencyBasicValueDTO> occFinalList = Lists.newArrayList();
            List<GmwrSelloutEfficiencyBasicValueDTO> perfectSelloutList = Lists.newArrayList();
            for (LocalDate indexDate = startLocalDate; !indexDate.isAfter(endLocalDate); indexDate = indexDate.plusDays(1)) {
                long date = Long.parseLong(DateTimeUtils.localDateToStr(indexDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
                Map<Integer, GmwrDailySummaryOcc> hour2Occ = hour2OccByDate.getOrDefault(date, Maps.newHashMap());
                GmwrDailySummaryOcc finalOcc = hour2Occ.getOrDefault(GmwrSelloutEfficiencyCons.FINAL_OCC_HOUR, new GmwrDailySummaryOcc());
                GmwrDailySummaryOcc occ = hour2Occ.getOrDefault(configDTO.getOccHour(), new GmwrDailySummaryOcc());
                occList.add(GmwrSelloutEfficiencyBasicValueDTO.builder()
                        .date(date)
                        .value(occ.getOccPercent())
                        .build());

                OooRoomCountInternalDTO oooRoomCount = date2OooCountDTO.getOrDefault(date, new OooRoomCountInternalDTO());
                Integer oooRoomsCount = oooRoomCount.getOooRoomsCount();
                oooRoomsList.add(GmwrSelloutEfficiencyBasicValueDTO.builder()
                        .date(date)
                        .value(Objects.isNull(oooRoomsCount) ? null : new BigDecimal(oooRoomsCount.toString()))
                        .build());

                Integer roomsSold = finalOcc.getRoomsSold();
                roomsSoldList.add(GmwrSelloutEfficiencyBasicValueDTO.builder()
                        .date(date)
                        .value(Objects.isNull(roomsSold) ? null : new BigDecimal(roomsSold.toString()))
                        .build());

                Integer roomsUnSold = (Objects.isNull(totalRooms) || Objects.isNull(oooRoomsCount) || Objects.isNull(roomsSold)) ?
                        null : totalRooms - oooRoomsCount - roomsSold;
                roomsUnsoldList.add(GmwrSelloutEfficiencyBasicValueDTO.builder()
                        .date(date)
                        .value(Objects.isNull(roomsUnSold) ? null : new BigDecimal(roomsUnSold.toString()))
                        .build());

                roomTotalList.add(GmwrSelloutEfficiencyBasicValueDTO.builder()
                        .date(date)
                        .value(Objects.isNull(totalRooms) ? null : new BigDecimal(totalRooms.toString()))
                        .build());

                occFinalList.add(GmwrSelloutEfficiencyBasicValueDTO.builder()
                        .date(date)
                        .value(finalOcc.getOccPercent())
                        .build());

                perfectSelloutList.add(GmwrSelloutEfficiencyBasicValueDTO.builder()
                        .date(date)
                        .build());
            }
            return Lists.newArrayList(new GmwrSelloutEfficiencyBasicDTO(GmwrSelloutEfficiencyEnum.OCC.getCategory(), occList),
                    new GmwrSelloutEfficiencyBasicDTO(GmwrSelloutEfficiencyEnum.OOO_ROOMS.getCategory(), oooRoomsList),
                    new GmwrSelloutEfficiencyBasicDTO(GmwrSelloutEfficiencyEnum.ROOM_SOLD.getCategory(), roomsSoldList),
                    new GmwrSelloutEfficiencyBasicDTO(GmwrSelloutEfficiencyEnum.ROOM_UNSOLD.getCategory(), roomsUnsoldList),
                    new GmwrSelloutEfficiencyBasicDTO(GmwrSelloutEfficiencyEnum.ROOM_TOTAL.getCategory(), roomTotalList),
                    new GmwrSelloutEfficiencyBasicDTO(GmwrSelloutEfficiencyEnum.OCC_FINAL.getCategory(), occFinalList),
                    new GmwrSelloutEfficiencyBasicDTO(GmwrSelloutEfficiencyEnum.PERFECT_SELLOUT.getCategory(), perfectSelloutList),
                    new GmwrSelloutEfficiencyBasicDTO(GmwrSelloutEfficiencyEnum.WEEKLY_SELLOUT_EFFICIENCY.getCategory(),
                            List.of(new GmwrSelloutEfficiencyBasicValueDTO())));
        }

        /**
         * 生成空集合
         *
         * @param startLocalDate 开始日期
         * @param endLocalDate   结束日期
         * @return GmwrSelloutEfficiencyBasicDTO 集合
         * @author zc
         * @date 2025/3/3
         */
        private List<GmwrSelloutEfficiencyBasicDTO> generateEmptyBasicDTOList(LocalDate startLocalDate, LocalDate endLocalDate) {
            List<GmwrSelloutEfficiencyBasicDTO> basicDTOList = Lists.newArrayList();
            Lists.newArrayList(GmwrSelloutEfficiencyEnum.OCC.getCategory(), GmwrSelloutEfficiencyEnum.OOO_ROOMS.getCategory(),
                    GmwrSelloutEfficiencyEnum.ROOM_SOLD.getCategory(), GmwrSelloutEfficiencyEnum.ROOM_UNSOLD.getCategory(),
                    GmwrSelloutEfficiencyEnum.ROOM_TOTAL.getCategory(), GmwrSelloutEfficiencyEnum.OCC_FINAL.getCategory(),
                    GmwrSelloutEfficiencyEnum.PERFECT_SELLOUT.getCategory()).forEach(category -> {
                List<GmwrSelloutEfficiencyBasicValueDTO> basicValueDTOList = Lists.newArrayList();
                for (LocalDate indexDate = startLocalDate; !indexDate.isAfter(endLocalDate); indexDate = indexDate.plusDays(1)) {
                    long date = Long.parseLong(DateTimeUtils.localDateToStr(indexDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
                    basicValueDTOList.add(GmwrSelloutEfficiencyBasicValueDTO.builder()
                            .date(date).build());
                }
                basicDTOList.add(GmwrSelloutEfficiencyBasicDTO.builder()
                        .category(category).list(basicValueDTOList)
                        .build());
            });
            basicDTOList.add(GmwrSelloutEfficiencyBasicDTO.builder()
                    .category(GmwrSelloutEfficiencyEnum.WEEKLY_SELLOUT_EFFICIENCY.getCategory())
                    .list(Lists.newArrayList(new GmwrSelloutEfficiencyBasicValueDTO()))
                    .build());
            return basicDTOList;
        }

        /**
         * 获取 Sellout Efficiency
         *
         * @param propertyDTO  资产
         * @param configDTO    配置规则
         * @param occList      OCC
         * @param occFinalList OCC Final
         * @return Sellout Efficiency
         * @author zc
         * @date 2025/6/13
         */
        private BigDecimal getWeeklySelloutEfficiency(PropertyDTO propertyDTO, GmwrOccSelloutConfigDTO configDTO,
                                                      List<GmwrSelloutEfficiencyValueDTO> occList,
                                                      List<GmwrSelloutEfficiencyValueDTO> occFinalList) {
            if (CollectionUtils.isEmpty(occList) || CollectionUtils.isEmpty(occFinalList)) {
                return null;
            }

            Map<Long, GmwrSelloutEfficiencyValueDTO> date2ValueDTO = occList.stream()
                    .collect(Collectors.toMap(GmwrSelloutEfficiencyValueDTO::getDate, Function.identity()));

            /*
             * Perfect Sell = OCC Final >= 100%; Technical sellout = OCC >= 95%;
             * If reached perfect sell however OCC < 95%, still consider a Technical sellout;
             * Weekly Sellout Efficiency % = Perfect Sell / Technical sellout; 0 night / 2 night = 0%
             */
            long perfectSell = 0;
            long technicalSellout = 0;

            for (GmwrSelloutEfficiencyValueDTO item : occFinalList) {
                BigDecimal occFinal = item.getValue();
                BigDecimal occ = date2ValueDTO.getOrDefault(item.getDate(), new GmwrSelloutEfficiencyValueDTO()).getValue();
                if (Objects.isNull(occFinal)) {
                    continue;
                }
                // 若 OCC Final >= 100%，则 perfectSell++, technicalSellout++
                if (occFinal.compareTo(GmwrSelloutEfficiencyCons.PERFECT_SELL) >= 0) {
                    perfectSell++;
                    technicalSellout++;
                } else if (Objects.nonNull(occ) && occ.compareTo(configDTO.getOccThreshold()) >= 0) {
                    technicalSellout++;
                }
            }

            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(propertyDTO
                    .getInternationalization().getNumber().getCalculationRule());
            return (technicalSellout == 0) ? null : new BigDecimal(perfectSell).multiply(new BigDecimal(100))
                    .divide(new BigDecimal(technicalSellout), BaseConstant.DATA_PRECISION, roundingMode);
        }

        /**
         * 获取 OOO Rooms
         *
         * @param propertyDTO 资产
         * @param gmwrWeeks   周报
         * @return GmwrSelloutEfficiencyPackageDTO
         * @author zc
         * @date 2024/10/21
         */
        @Override
        public GmwrSelloutEfficiencyPackageDTO getDetail(PropertyDTO propertyDTO, GmwrWeeks gmwrWeeks) {
            List<GmwrSelloutEfficiencyBasicDTO> basicDTOList;
            GmwrSelloutEfficiency selloutEfficiency = getByReportId(gmwrWeeks.getId());

            GmwrOccSelloutConfigDTO currentConfig;
            if (Objects.isNull(selloutEfficiency)) {
                // 由于获取的是前一周的数据，因此需要获取前一周的开始和结束日期
                LocalDate startLocalDate = DateTimeUtils.strToLocalDate(gmwrWeeks.getWeek().toString(),
                        BaseConstant.DATE_FORMAT_YYYYMMDD).minusDays(7);
                LocalDate endLocalDate = startLocalDate.plusDays(6);
                basicDTOList = generateEmptyBasicDTOList(startLocalDate, endLocalDate);

                currentConfig = occSelloutConfigService.getByPropertyId(propertyDTO.getId());
            } else {
                basicDTOList = JsonUtils.convertStringToList(objectMapper, selloutEfficiency.getDetail(), GmwrSelloutEfficiencyBasicDTO.class);

                currentConfig = JsonUtils.convertStringToObject(objectMapper, selloutEfficiency.getConfigDetail(), GmwrOccSelloutConfigDTO.class);
            }

            // 获取备注数据
            List<GmwrSelloutEfficiencyNote> noteList = selloutEfficiencyNoteService.listByReportId(gmwrWeeks.getId());
            Map<Integer, GmwrSelloutEfficiencyNote> category2Note = noteList.stream()
                    .collect(Collectors.toMap(GmwrSelloutEfficiencyNote::getCategory, Function.identity()));
            Set<Integer> userIds = noteList.stream().map(GmwrSelloutEfficiencyNote::getUpdatedBy).collect(Collectors.toSet());
            Map<Integer, String> userId2Name = userInternalService.getNameMapByIds(propertyDTO.getTenantId(), userIds);

            List<GmwrSelloutEfficiencyDTO> selloutEfficiencyDTOList = beanMapper.mapList(basicDTOList,
                    GmwrSelloutEfficiencyDTO.class, (basicDTO, dto) -> {
                        dto.setCategoryName(GmwrSelloutEfficiencyEnum.getCategoryName(basicDTO.getCategory()));
                        GmwrSelloutEfficiencyNote note = category2Note.get(dto.getCategory());
                        if (Objects.nonNull(note)) {
                            GmwrNoteDTO noteDTO = beanMapper.map(note, GmwrNoteDTO.class, noteDto ->
                                    noteDto.setUpdatedName(userId2Name.get(noteDto.getUpdatedBy())));
                            dto.setNote(noteDTO);
                        }
                    });

            // 组装 Perfect Sellout 结果参数
            packageParams(propertyDTO, currentConfig, selloutEfficiencyDTOList);

            return new GmwrSelloutEfficiencyPackageDTO(selloutEfficiencyDTOList, currentConfig);
        }

        /**
         * 根据周报 id 获取 oooRooms
         *
         * @param reportId 周报 id
         * @return GmwrOooRooms
         * @author zc
         * @date 2024/11/6
         */
        @Override
        public GmwrSelloutEfficiency getByReportId(int reportId) {
            return getOne(new LambdaQueryWrapper<GmwrSelloutEfficiency>()
                    .eq(GmwrSelloutEfficiency::getReportId, reportId));
        }

        /**
         * 组装 Sellout Efficiency 结果参数
         *
         * @param configDTO                OCC 阈值配置
         * @param selloutEfficiencyDTOList DTO数据集合
         * @author zc
         * @date 2024/10/21
         */
        private void packageParams(PropertyDTO propertyDTO, GmwrOccSelloutConfigDTO configDTO, List<GmwrSelloutEfficiencyDTO> selloutEfficiencyDTOList) {
            Map<Integer, GmwrSelloutEfficiencyDTO> category2DTO = selloutEfficiencyDTOList.stream()
                    .collect(Collectors.toMap(GmwrSelloutEfficiencyDTO::getCategory, Function.identity()));

            List<GmwrSelloutEfficiencyValueDTO> occList = category2DTO.getOrDefault(GmwrSelloutEfficiencyEnum.OCC.getCategory(),
                    new GmwrSelloutEfficiencyDTO()).getList();
            Map<Long, GmwrSelloutEfficiencyValueDTO> date2Occ = occList.stream()
                    .collect(Collectors.toMap(GmwrSelloutEfficiencyValueDTO::getDate, Function.identity()));

            List<GmwrSelloutEfficiencyValueDTO> occFinalList = category2DTO.getOrDefault(GmwrSelloutEfficiencyEnum.OCC_FINAL.getCategory(),
                    new GmwrSelloutEfficiencyDTO()).getList();
            Map<Long, GmwrSelloutEfficiencyValueDTO> date2OccFinal = occFinalList.stream()
                    .collect(Collectors.toMap(GmwrSelloutEfficiencyValueDTO::getDate, Function.identity()));

            // 计算 Weekly Sellout Efficiency
            List<GmwrSelloutEfficiencyValueDTO> weeklySellEfficiencyList = category2DTO.getOrDefault(GmwrSelloutEfficiencyEnum.WEEKLY_SELLOUT_EFFICIENCY.getCategory(),
                    new GmwrSelloutEfficiencyDTO()).getList();
            for (GmwrSelloutEfficiencyValueDTO valueDTO : weeklySellEfficiencyList) {
                valueDTO.setValue(getWeeklySelloutEfficiency(propertyDTO, configDTO, occList, occFinalList));
            }

            // 获取阈值
            BigDecimal occThreshold = configDTO.getOccThreshold();

            // 设置 Perfect Sellout
            List<GmwrSelloutEfficiencyValueDTO> perfectSelloutList = category2DTO.getOrDefault(GmwrSelloutEfficiencyEnum.PERFECT_SELLOUT.getCategory(),
                    new GmwrSelloutEfficiencyDTO()).getList();
            for (GmwrSelloutEfficiencyValueDTO valueDTO : perfectSelloutList) {
                BigDecimal occFinal = date2OccFinal.getOrDefault(valueDTO.getDate(), new GmwrSelloutEfficiencyValueDTO()).getValue();
                BigDecimal occ = date2Occ.getOrDefault(valueDTO.getDate(), new GmwrSelloutEfficiencyValueDTO()).getValue();
                // 设置比较结果
                valueDTO.setJudgment(getJudgment(occ, occFinal, occThreshold));
                // 设置是否 Perfect
                valueDTO.setPerfect(getPerfect(occ, occFinal, occThreshold));
            }
        }

        /**
         * 获取比较结果
         * 例：假设阈值=95%
         * 1. OCC Final = null → null（数据缺失）
         * 2. OCC Final ≥ 100 → JUDGMENT_GOOD（Perfect Sell，绿色）
         * 3. OCC Final < 100 且 OCC = null → null（OCC 数据缺失）
         * 4. OCC Final < 100 且 OCC < 95 → null（未达技术售罄标准）
         * 5. OCC Final < 100 且 OCC ≥ 95 且 OCC Final ≥ OCC → JUDGMENT_CURE（有改善，黄色）
         * 6. OCC Final < 100 且 OCC ≥ 95 且 OCC Final < OCC → JUDGMENT_BAD（恶化，红色）
         *
         * @param occ          OCC
         * @param occFinal     OCC Final
         * @param occThreshold OCC Threshold
         * @return java.lang.String
         * @author zc
         * @date 2026/3/14
         */
        private String getJudgment(BigDecimal occ, BigDecimal occFinal, BigDecimal occThreshold) {
            if (Objects.isNull(occFinal)) {
                return null;
            }

            if (occFinal.compareTo(GmwrSelloutEfficiencyCons.PERFECT_SELL) >= 0) {
                return GmwrCons.JUDGMENT_GOOD;
            }

            if (Objects.isNull(occ)) {
                return null;
            }

            if (occ.compareTo(occThreshold) < 0) {
                return null;
            }

            if (occFinal.compareTo(occ) >= 0) {
                return GmwrCons.JUDGMENT_CURE;
            }

            return GmwrCons.JUDGMENT_BAD;
        }

        /**
         * 获取 Perfect Sellout 数值
         * 例：假设阈值=95%
         * 1. OCC Final = null → null（数据缺失）
         * 2. OCC Final ≥ 100 → true（Perfect Sell）
         * 3. OCC Final < 100 且 OCC = null → null（OCC 数据缺失）
         * 4. OCC Final < 100 且 OCC < 95 → null（未达技术售罄标准）
         * 5. OCC Final < 100 且 OCC ≥ 95 且 OCC Final < OCC → false（未达 Perfect）
         * 6. OCC Final < 100 且 OCC ≥ 95 且 OCC Final ≥ OCC → false（未达 Perfect）
         *
         * @param occ      Occ
         * @param occFinal Occ Final
         * @return BigDecimal
         * @author zc
         * @date 2026/3/14
         */
        private Boolean getPerfect(BigDecimal occ, BigDecimal occFinal, BigDecimal occThreshold) {
            if (Objects.isNull(occFinal)) {
                return null;
            }

            if (occFinal.compareTo(GmwrSelloutEfficiencyCons.PERFECT_SELL) >= 0) {
                return Boolean.TRUE;
            }

            if (Objects.isNull(occ)) {
                return null;
            }

            if (occ.compareTo(occThreshold) < 0) {
                return null;
            }

            return Boolean.FALSE;
        }
    }