# IDbPmsMarketCodeStatisticsParseService.java

    package cloud.wconnect.workperformance.service.database.marketcode;

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.exception.ParseReportException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.workperformance.entity.database.DbReportImportLog;
    import cloud.wconnect.workperformance.pojo.bo.database.managerflash.DbPmsManagerFlashItemBO;
    import cloud.wconnect.workperformance.pojo.bo.database.marketcode.DbPmsMarketCodeStatisticsBO;
    import cloud.wconnect.workperformance.pojo.bo.database.marketcode.DbPmsMarketCodeStatisticsDataBO;
    import cloud.wconnect.workperformance.pojo.bo.database.marketcode.DbPmsMarketCodeStatisticsParentGroupBO;
    import cloud.wconnect.workperformance.pojo.bo.database.marketcode.DbPmsMarketCodeStatisticsSubGroupBO;
    import org.springframework.web.multipart.MultipartFile;

    import java.io.InputStream;
    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.util.List;
    import java.util.Objects;

    /**
     * 解析 Market Code Statistics/Service 报告
     *
     * @author zc
     * @date 2025/12/12
     */
    public interface IDbPmsMarketCodeStatisticsParseService {
        /**
         * 支持的报告来源
         *
         * @param source 报告来源
         * @return boolean
         * @author zc
         * @date 2025/12/12
         */
        boolean supports(String source);

        /**
         * 校验文件内容是否合法
         *
         * @param propertyDTO 资产
         * @param file        文件
         * @author zc
         * @date 2025/12/12
         */
        void checkFile(PropertyDTO propertyDTO, MultipartFile file);

        /**
         * 处理数据
         *
         * @param propertyDTO 资产
         * @param file        文件
         * @param importLog   日志
         * @param operatorId  操作人 id
         * @author zc
         * @date 2025/12/12
         */
        void handleData(PropertyDTO propertyDTO, MultipartFile file, DbReportImportLog importLog, int operatorId);

        /**
         * 解析报告
         *
         * @param propertyDTO 资产
         * @param in          报告文件流
         * @param dateRange   数据日期范围（yyyyMMdd）
         * @return DbPmsMarketCodeStatisticsBO
         * @author zc
         * @date 2025/12/12
         */
        DbPmsMarketCodeStatisticsBO parse(PropertyDTO propertyDTO, InputStream in, List<Long> dateRange) throws ParseReportException;

        /**
         * 计算每个父级的小计
         *
         * @param parentGroup  父级组列表
         * @param scRoomsBO    SC房间数BO
         * @param roundingMode 舍入模式
         * @author zc
         * @date 2025/12/15
         */
        static void calculateParentSubtotals(List<DbPmsMarketCodeStatisticsParentGroupBO> parentGroup,
                                             DbPmsManagerFlashItemBO scRoomsBO, RoundingMode roundingMode) {
            BigDecimal scRoomsDay = scRoomsBO.getDay();
            BigDecimal scRoomsMtd = scRoomsBO.getMtd();
            BigDecimal scRoomsYtd = scRoomsBO.getYtd();
            for (DbPmsMarketCodeStatisticsParentGroupBO parentGroupBO : parentGroup) {
                List<DbPmsMarketCodeStatisticsSubGroupBO> subGroup = parentGroupBO.getRateCode();

                parentGroupBO.setRmsDay(subGroup.stream().map(DbPmsMarketCodeStatisticsSubGroupBO::getRmsDay)
                        .filter(Objects::nonNull).reduce(Integer::sum).orElse(0));
                parentGroupBO.setRmsMtd(subGroup.stream().map(DbPmsMarketCodeStatisticsSubGroupBO::getRmsMtd)
                        .filter(Objects::nonNull).reduce(Integer::sum).orElse(0));
                parentGroupBO.setRmsYtd(subGroup.stream().map(DbPmsMarketCodeStatisticsSubGroupBO::getRmsYtd)
                        .filter(Objects::nonNull).reduce(Integer::sum).orElse(0));

                parentGroupBO.setRmRevDay(subGroup.stream().map(DbPmsMarketCodeStatisticsSubGroupBO::getRmRevDay)
                        .filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO));
                parentGroupBO.setRmRevMtd(subGroup.stream().map(DbPmsMarketCodeStatisticsSubGroupBO::getRmRevMtd)
                        .filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO));
                parentGroupBO.setRmRevYtd(subGroup.stream().map(DbPmsMarketCodeStatisticsSubGroupBO::getRmRevYtd)
                        .filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO));

                parentGroupBO.setAdrDay(calculateAdr(parentGroupBO.getRmRevDay(),
                        parentGroupBO.getRmsDay(), roundingMode));
                parentGroupBO.setAdrMtd(calculateAdr(parentGroupBO.getRmRevMtd(),
                        parentGroupBO.getRmsMtd(), roundingMode));
                parentGroupBO.setAdrYtd(calculateAdr(parentGroupBO.getRmRevYtd(),
                        parentGroupBO.getRmsYtd(), roundingMode));

                parentGroupBO.setOccDay(calculateOccPercent(parentGroupBO.getRmsDay(),
                        scRoomsDay, roundingMode));
                parentGroupBO.setOccMtd(calculateOccPercent(parentGroupBO.getRmsMtd(),
                        scRoomsMtd, roundingMode));
                parentGroupBO.setOccYtd(calculateOccPercent(parentGroupBO.getRmsYtd(),
                        scRoomsYtd, roundingMode));

                parentGroupBO.setGuestDay(subGroup.stream().map(DbPmsMarketCodeStatisticsSubGroupBO::getGuestDay)
                        .filter(Objects::nonNull).reduce(Integer::sum).orElse(0));
                parentGroupBO.setGuestMtd(subGroup.stream().map(DbPmsMarketCodeStatisticsSubGroupBO::getGuestMtd)
                        .filter(Objects::nonNull).reduce(Integer::sum).orElse(0));
                parentGroupBO.setGuestYtd(subGroup.stream().map(DbPmsMarketCodeStatisticsSubGroupBO::getGuestYtd)
                        .filter(Objects::nonNull).reduce(Integer::sum).orElse(0));
            }
        }

        /**
         * 计算总计
         *
         * @param parentGroup  父级组列表
         * @param scRoomsBO    SC房间数BO
         * @param roundingMode 舍入模式
         * @return 包含总计数据的BO对象
         * @author zc
         * @date 2025/12/15
         */
        static DbPmsMarketCodeStatisticsDataBO calculateTotals(List<DbPmsMarketCodeStatisticsParentGroupBO> parentGroup,
                                                               DbPmsManagerFlashItemBO scRoomsBO, RoundingMode roundingMode) {
            BigDecimal scRoomsDay = scRoomsBO.getDay();
            BigDecimal scRoomsMtd = scRoomsBO.getMtd();
            BigDecimal scRoomsYtd = scRoomsBO.getYtd();

            DbPmsMarketCodeStatisticsDataBO bo = new DbPmsMarketCodeStatisticsDataBO();
            bo.setRmsDay(parentGroup.stream().map(DbPmsMarketCodeStatisticsParentGroupBO::getRmsDay)
                    .filter(Objects::nonNull).reduce(Integer::sum).orElse(0));
            bo.setRmsMtd(parentGroup.stream().map(DbPmsMarketCodeStatisticsParentGroupBO::getRmsMtd)
                    .filter(Objects::nonNull).reduce(Integer::sum).orElse(0));
            bo.setRmsYtd(parentGroup.stream().map(DbPmsMarketCodeStatisticsParentGroupBO::getRmsYtd)
                    .filter(Objects::nonNull).reduce(Integer::sum).orElse(0));

            bo.setRmRevDay(parentGroup.stream().map(DbPmsMarketCodeStatisticsParentGroupBO::getRmRevDay)
                    .filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO));
            bo.setRmRevMtd(parentGroup.stream().map(DbPmsMarketCodeStatisticsParentGroupBO::getRmRevMtd)
                    .filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO));
            bo.setRmRevYtd(parentGroup.stream().map(DbPmsMarketCodeStatisticsParentGroupBO::getRmRevYtd)
                    .filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO));

            bo.setAdrDay(calculateAdr(bo.getRmRevDay(), bo.getRmsDay(), roundingMode));
            bo.setAdrMtd(calculateAdr(bo.getRmRevMtd(), bo.getRmsMtd(), roundingMode));
            bo.setAdrYtd(calculateAdr(bo.getRmRevYtd(), bo.getRmsYtd(), roundingMode));

            bo.setOccDay(calculateOccPercent(bo.getRmsDay(), scRoomsDay, roundingMode));
            bo.setOccMtd(calculateOccPercent(bo.getRmsMtd(), scRoomsMtd, roundingMode));
            bo.setOccYtd(calculateOccPercent(bo.getRmsYtd(), scRoomsYtd, roundingMode));

            bo.setGuestDay(parentGroup.stream().map(DbPmsMarketCodeStatisticsParentGroupBO::getGuestDay)
                    .filter(Objects::nonNull).reduce(Integer::sum).orElse(0));
            bo.setGuestMtd(parentGroup.stream().map(DbPmsMarketCodeStatisticsParentGroupBO::getGuestMtd)
                    .filter(Objects::nonNull).reduce(Integer::sum).orElse(0));
            bo.setGuestYtd(parentGroup.stream().map(DbPmsMarketCodeStatisticsParentGroupBO::getGuestYtd)
                    .filter(Objects::nonNull).reduce(Integer::sum).orElse(0));

            return bo;
        }

        /**
         * 计算 Parent Subtotals Occ.%
         *
         * @param parentGroupBOList 父级组列表
         * @param scRoomsBO         SC房间数BO
         * @param roundingMode      舍入模式
         * @author zc
         * @date 2026/1/13
         */
        static void calculateParentSubtotalsOcc(List<DbPmsMarketCodeStatisticsParentGroupBO> parentGroupBOList,
                                                DbPmsManagerFlashItemBO scRoomsBO, RoundingMode roundingMode) {
            BigDecimal scRoomsDay = scRoomsBO.getDay();
            BigDecimal scRoomsMtd = scRoomsBO.getMtd();
            BigDecimal scRoomsYtd = scRoomsBO.getYtd();
            for (DbPmsMarketCodeStatisticsParentGroupBO parentGroupBO : parentGroupBOList) {
                parentGroupBO.setOccDay(calculateOccPercent(parentGroupBO.getRmsDay(),
                        scRoomsDay, roundingMode));
                parentGroupBO.setOccMtd(calculateOccPercent(parentGroupBO.getRmsMtd(),
                        scRoomsMtd, roundingMode));
                parentGroupBO.setOccYtd(calculateOccPercent(parentGroupBO.getRmsYtd(),
                        scRoomsYtd, roundingMode));
            }
        }

        /**
         * 计算 Total Occ.%
         *
         * @param dataBO       Total BO
         * @param scRoomsBO    SC房间数BO
         * @param roundingMode 舍入模式
         * @author zc
         * @date 2026/1/13
         */
        static void calculateTotalsOcc(DbPmsMarketCodeStatisticsDataBO dataBO,
                                       DbPmsManagerFlashItemBO scRoomsBO, RoundingMode roundingMode) {
            dataBO.setOccDay(calculateOccPercent(dataBO.getRmsDay(), scRoomsBO.getDay(), roundingMode));
            dataBO.setOccMtd(calculateOccPercent(dataBO.getRmsMtd(), scRoomsBO.getMtd(), roundingMode));
            dataBO.setOccYtd(calculateOccPercent(dataBO.getRmsYtd(), scRoomsBO.getYtd(), roundingMode));
        }

        /**
         * Calculate ADR
         *
         * @param roomRevenue  room revenue
         * @param rooms        occupied rooms
         * @param roundingMode rounding mode
         * @return java.math.BigDecimal
         * @author zc
         * @date 2025/12/15
         */
        private static BigDecimal calculateAdr(BigDecimal roomRevenue, Integer rooms, RoundingMode roundingMode) {
            if (rooms == 0) {
                return BigDecimal.ZERO;
            }
            return roomRevenue.divide(new BigDecimal(rooms.toString()), BaseConstant.DATA_PRECISION, roundingMode);
        }

        /**
         * Calculate occupancy percent
         *
         * @param rooms        occupied rooms
         * @param totalScRooms total SC rooms
         * @param roundingMode rounding mode
         * @return java.math.BigDecimal
         * @author zc
         * @date 2025/12/15
         */
        private static BigDecimal calculateOccPercent(Integer rooms, BigDecimal totalScRooms, RoundingMode roundingMode) {
            if (totalScRooms == null || totalScRooms.equals(BigDecimal.ZERO)) {
                return BigDecimal.ZERO;
            }
            return new BigDecimal(rooms.toString()).multiply(new BigDecimal("100"))
                    .divide(totalScRooms, BaseConstant.DATA_PRECISION, roundingMode);
        }
    }