# BudgetDatabaseTotalServiceImpl.java

    package cloud.wconnect.rateshop.service.budgetdatabase.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.UserDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.YearMonthUtils;
    import cloud.wconnect.rateshop.cons.budgetdatabase.*;
    import cloud.wconnect.rateshop.entity.budgetdatabase.*;
    import cloud.wconnect.rateshop.pojo.bo.budgetdatabase.OneYearRevenueBO;
    import cloud.wconnect.rateshop.pojo.dto.budgetdatabase.*;
    import cloud.wconnect.rateshop.service.budgetdatabase.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.google.common.collect.Lists;
    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 java.math.BigDecimal;
    import java.time.Month;
    import java.time.YearMonth;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * @author: yyt
     * 2025-08-14
     */
    @Service
    @DS("rate_shop")
    public class BudgetDatabaseTotalServiceImpl implements IBudgetDatabaseTotalService {

        @Lazy
        @Autowired
        private IBudgetDatabaseService budgetDatabaseService;
        @Autowired
        private IBudgetDatabaseStatusLogService budgetDatabaseStatusLogService;
        @Lazy
        @Autowired
        private IBudgetDatabaseConfigRoomTabService budgetDatabaseConfigRoomTabService;
        @Lazy
        @Autowired
        private IBudgetDatabaseRoomService budgetDatabaseRoomService;
        @Lazy
        @Autowired
        private IBudgetDatabaseRoomOtherService budgetDatabaseRoomOtherService;
        @Autowired
        private IBudgetDatabaseConfigFbTabService budgetDatabaseConfigFbTabService;
        @Lazy
        @Autowired
        private IBudgetDatabaseConfigFbTabItemService budgetDatabaseConfigFbTabItemService;
        @Autowired
        private IBudgetDatabaseConfigOtherTabService budgetDatabaseConfigOtherTabService;
        @Lazy
        @Autowired
        private IBudgetDatabaseConfigOtherTabItemService budgetDatabaseConfigOtherTabItemService;
        @Lazy
        @Autowired
        private IBudgetDatabaseFbRevenueService budgetDatabaseFbRevenueService;
        @Lazy
        @Autowired
        private IBudgetDatabaseOtherRevenueService budgetDatabaseOtherRevenueService;
        @Autowired
        private IBudgetDatabaseCalculateService budgetDatabaseCalculateService;
        @Lazy
        @Autowired
        private IBudgetDatabaseConfigRoomTabItemService budgetDatabaseConfigRoomTabItemService;
        @Autowired
        private IBudgetDatabaseDataLogRoomMonthStatisticsService budgetDatabaseDataLogRoomMonthStatisticsService;
        @Autowired
        private UserInternalService userInternalService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private BeanMapper beanMapper;

        /**
         * 获取 Total
         *
         * @param propertyId 资产 id
         * @param year       年
         * @return Total
         * @author yyt
         **/
        @Override
        public TotalDTO listTotal(int propertyId, int year) {
            // 根据配置返回内容，若无配置，则返回空
            boolean hasConfig = budgetDatabaseService.checkHasConfig(propertyId, year);
            if (!hasConfig) {
                return new TotalDTO();
            }

            TotalDTO totalDTO = new TotalDTO();
            StatusInfoDTO statusInfoDTO = new StatusInfoDTO();

            // 返回状态（默认 draft）
            BudgetDatabase budgetDatabase = budgetDatabaseService.get(propertyId, year);
            statusInfoDTO.setStatus(Objects.isNull(budgetDatabase) ? BudgetDatabaseStatusEnum.DRAFT.getCode() : budgetDatabase.getStatus());

            // 最近一次 complete/reopen 更新人，更新时间
            BudgetDatabaseStatusLog statusLog = budgetDatabaseStatusLogService.lastLog(propertyId, year);
            if (Objects.nonNull(statusLog)) {
                PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
                UserDTO user = userInternalService.getOne(propertyDTO.getTenantId(), statusLog.getUpdatedBy());
                statusInfoDTO.setUpdatedBy(statusLog.getUpdatedBy());
                statusInfoDTO.setUpdatedName(user.getName());
                statusInfoDTO.setUpdatedTime(statusLog.getUpdatedTime());
            }
            totalDTO.setStatusInfo(statusInfoDTO);

            // 返回统计内容
            totalDTO.setTotalInfos(packageTotalInfo(propertyId, year));

            return totalDTO;
        }

        /**
         * 返回统计内容
         *
         * @param propertyId 资产 id
         * @param year       年
         * @return 统计内容
         * @author yyt
         * @date 2025/8/14
         **/
        private List<TotalDetailDTO> packageTotalInfo(int propertyId, int year) {
            List<TotalDetailDTO> totalDetailDTOS = Lists.newArrayListWithExpectedSize(4);
            // room + other
            List<Integer> tabIds = Collections.emptyList();
            List<BudgetDatabaseConfigRoomTab> tabs = budgetDatabaseConfigRoomTabService.listParticipateCalculateTabs(propertyId, year);
            if (CollectionUtils.isNotEmpty(tabs)) {
                tabIds = tabs.stream().map(BudgetDatabaseConfigRoomTab::getId).toList();
            }
            totalDetailDTOS.add(packageRoomTotalDetailDTO(propertyId, year, tabIds, true));

            // fb revenue
            List<Integer> fbItemIds = Collections.emptyList();
            List<Integer> fbTabIds = budgetDatabaseConfigFbTabService.listParticipateCalculateTabs(propertyId, year);
            if (CollectionUtils.isNotEmpty(fbTabIds)) {
                // 查询 revenue 分类指标 id
                fbItemIds = budgetDatabaseConfigFbTabItemService.listByTabIdsAndClassification(fbTabIds, FbClassificationEnum.REVENUE.getCode())
                        .stream().map(BudgetDatabaseConfigFbTabItem::getId).toList();
            }
            totalDetailDTOS.add(packageTotalDetailDTO(propertyId, BudgetDatabaseTitleCons.FB, beanMapper.mapList(budgetDatabaseFbRevenueService.listByItemIds(fbItemIds), OneYearRevenueBO.class)));

            // other revenue
            List<Integer> otherIds = Collections.emptyList();
            List<Integer> otherTabIds = budgetDatabaseConfigOtherTabService.listParticipateCalculateTabs(propertyId, year);
            if (CollectionUtils.isNotEmpty(otherTabIds)) {
                // 查询 revenue 分类指标 id
                otherIds = budgetDatabaseConfigOtherTabItemService.listByTabIds(otherTabIds).stream().map(BudgetDatabaseConfigOtherTabItem::getId).toList();
            }
            totalDetailDTOS.add(packageTotalDetailDTO(propertyId, BudgetDatabaseTitleCons.OTHER, beanMapper.mapList(budgetDatabaseOtherRevenueService.listByItemIds(otherIds), OneYearRevenueBO.class)));

            // total
            totalDetailDTOS.add(packageTotalDetailDTO(propertyId, BudgetDatabaseTitleCons.TOTAL, beanMapper.mapList(totalDetailDTOS, OneYearRevenueBO.class)));

            return totalDetailDTOS;
        }

        /**
         * room total
         *
         * @param propertyId       资产 id
         * @param tabIds           tab id
         * @param summaryOtherData 是否汇总 other 数据
         * @return room total
         * @author yyt
         * @date 2025/8/14
         **/
        private TotalDetailDTO packageRoomTotalDetailDTO(int propertyId, int year, List<Integer> tabIds, boolean summaryOtherData) {
            Map<Integer, List<BigDecimal>> budgetRevenueByMonth = Collections.emptyMap();
            BudgetYearDTO budgetYearDTOForRoomOther = null;
            List<BudgetDatabaseRoom> rooms = Collections.emptyList();
            if (CollectionUtils.isNotEmpty(tabIds)) {
                rooms = budgetDatabaseRoomService.listByTabIds(tabIds);
                if (CollectionUtils.isNotEmpty(rooms)) {
                    budgetRevenueByMonth = rooms.stream().filter(item -> Objects.nonNull(item.getBudgetRevenue()))
                            .collect(Collectors.groupingBy(BudgetDatabaseRoom::getYearMonth,
                                    Collectors.mapping(BudgetDatabaseRoom::getBudgetRevenue, Collectors.toList())));
                }
                if (summaryOtherData) {
                    List<BudgetDatabaseRoomOther> roomOthers = budgetDatabaseRoomOtherService.listByTabIds(tabIds);
                    if (CollectionUtils.isNotEmpty(roomOthers)) {
                        budgetYearDTOForRoomOther = budgetDatabaseCalculateService.calculateRevenueTotal(propertyId, beanMapper.mapList(roomOthers, OneYearRevenueBO.class));
                    }
                }
            }

            TotalDetailDTO totalDetailDTO = new TotalDetailDTO();
            totalDetailDTO.setTitle(BudgetDatabaseTitleCons.ROOM);
            // 如果两都有值，则累加
            BigDecimal january = summaryRevenue(budgetRevenueByMonth, year, Month.JANUARY.getValue());
            if (Objects.nonNull(budgetYearDTOForRoomOther)) {
                BigDecimal januaryForOther = budgetYearDTOForRoomOther.getJanuary();
                if (Objects.nonNull(januaryForOther)) {
                    january = Objects.isNull(january) ? januaryForOther : january.add(januaryForOther);
                }
            }
            totalDetailDTO.setJanuary(january);

            BigDecimal february = summaryRevenue(budgetRevenueByMonth, year, Month.FEBRUARY.getValue());
            if (Objects.nonNull(budgetYearDTOForRoomOther)) {
                BigDecimal februaryForOther = budgetYearDTOForRoomOther.getFebruary();
                if (Objects.nonNull(februaryForOther)) {
                    february = Objects.isNull(february) ? februaryForOther : february.add(februaryForOther);
                }
            }
            totalDetailDTO.setFebruary(february);

            BigDecimal march = summaryRevenue(budgetRevenueByMonth, year, Month.MARCH.getValue());
            if (Objects.nonNull(budgetYearDTOForRoomOther)) {
                BigDecimal marchForOther = budgetYearDTOForRoomOther.getMarch();
                if (Objects.nonNull(marchForOther)) {
                    march = Objects.isNull(march) ? marchForOther : march.add(marchForOther);
                }
            }
            totalDetailDTO.setMarch(march);

            BigDecimal april = summaryRevenue(budgetRevenueByMonth, year, Month.APRIL.getValue());
            if (Objects.nonNull(budgetYearDTOForRoomOther)) {
                BigDecimal aprilForOther = budgetYearDTOForRoomOther.getApril();
                if (Objects.nonNull(aprilForOther)) {
                    april = Objects.isNull(april) ? aprilForOther : april.add(aprilForOther);
                }
            }
            totalDetailDTO.setApril(april);

            BigDecimal may = summaryRevenue(budgetRevenueByMonth, year, Month.MAY.getValue());
            if (Objects.nonNull(budgetYearDTOForRoomOther)) {
                BigDecimal mayForOther = budgetYearDTOForRoomOther.getMay();
                if (Objects.nonNull(mayForOther)) {
                    may = Objects.isNull(may) ? mayForOther : may.add(mayForOther);
                }
            }
            totalDetailDTO.setMay(may);

            BigDecimal june = summaryRevenue(budgetRevenueByMonth, year, Month.JUNE.getValue());
            if (Objects.nonNull(budgetYearDTOForRoomOther)) {
                BigDecimal juneForOther = budgetYearDTOForRoomOther.getJune();
                if (Objects.nonNull(juneForOther)) {
                    june = Objects.isNull(june) ? juneForOther : june.add(juneForOther);
                }
            }
            totalDetailDTO.setJune(june);

            BigDecimal july = summaryRevenue(budgetRevenueByMonth, year, Month.JULY.getValue());
            if (Objects.nonNull(budgetYearDTOForRoomOther)) {
                BigDecimal julyForOther = budgetYearDTOForRoomOther.getJuly();
                if (Objects.nonNull(julyForOther)) {
                    july = Objects.isNull(july) ? julyForOther : july.add(julyForOther);
                }
            }
            totalDetailDTO.setJuly(july);

            BigDecimal august = summaryRevenue(budgetRevenueByMonth, year, Month.AUGUST.getValue());
            if (Objects.nonNull(budgetYearDTOForRoomOther)) {
                BigDecimal augustForOther = budgetYearDTOForRoomOther.getAugust();
                if (Objects.nonNull(augustForOther)) {
                    august = Objects.isNull(august) ? augustForOther : august.add(augustForOther);
                }
            }
            totalDetailDTO.setAugust(august);

            BigDecimal september = summaryRevenue(budgetRevenueByMonth, year, Month.SEPTEMBER.getValue());
            if (Objects.nonNull(budgetYearDTOForRoomOther)) {
                BigDecimal septemberForOther = budgetYearDTOForRoomOther.getSeptember();
                if (Objects.nonNull(septemberForOther)) {
                    september = Objects.isNull(september) ? septemberForOther : september.add(septemberForOther);
                }
            }
            totalDetailDTO.setSeptember(september);

            BigDecimal october = summaryRevenue(budgetRevenueByMonth, year, Month.OCTOBER.getValue());
            if (Objects.nonNull(budgetYearDTOForRoomOther)) {
                BigDecimal octoberForOther = budgetYearDTOForRoomOther.getOctober();
                if (Objects.nonNull(octoberForOther)) {
                    october = Objects.isNull(october) ? octoberForOther : october.add(octoberForOther);
                }
            }
            totalDetailDTO.setOctober(october);

            BigDecimal november = summaryRevenue(budgetRevenueByMonth, year, Month.NOVEMBER.getValue());
            if (Objects.nonNull(budgetYearDTOForRoomOther)) {
                BigDecimal novemberForOther = budgetYearDTOForRoomOther.getNovember();
                if (Objects.nonNull(novemberForOther)) {
                    november = Objects.isNull(november) ? novemberForOther : november.add(novemberForOther);
                }
            }
            totalDetailDTO.setNovember(november);

            BigDecimal december = summaryRevenue(budgetRevenueByMonth, year, Month.DECEMBER.getValue());
            if (Objects.nonNull(budgetYearDTOForRoomOther)) {
                BigDecimal decemberForOther = budgetYearDTOForRoomOther.getDecember();
                if (Objects.nonNull(decemberForOther)) {
                    december = Objects.isNull(december) ? decemberForOther : december.add(decemberForOther);
                }
            }
            totalDetailDTO.setDecember(december);

            totalDetailDTO.setBudgetTotal(budgetDatabaseCalculateService.summaryOneYearRevenue(totalDetailDTO.getJanuary(), totalDetailDTO.getFebruary(),
                    totalDetailDTO.getMarch(), totalDetailDTO.getApril(), totalDetailDTO.getMay(), totalDetailDTO.getJune(),
                    totalDetailDTO.getJuly(), totalDetailDTO.getAugust(), totalDetailDTO.getSeptember(), totalDetailDTO.getOctober(),
                    totalDetailDTO.getNovember(), totalDetailDTO.getDecember()));

            List<BigDecimal> actualRevenueList = rooms.stream().map(BudgetDatabaseRoom::getActualRevenue).filter(Objects::nonNull).toList();
            BigDecimal actualTotal = CollectionUtils.isEmpty(actualRevenueList) ? null : actualRevenueList.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
            if (Objects.nonNull(budgetYearDTOForRoomOther)) {
                BigDecimal actualTotalForOther = budgetYearDTOForRoomOther.getActualTotal();
                if (Objects.nonNull(actualTotalForOther)) {
                    actualTotal = Objects.isNull(actualTotal) ? actualTotalForOther : actualTotal.add(actualTotalForOther);
                }
            }
            totalDetailDTO.setActualTotal(actualTotal);

            totalDetailDTO.setYoyChg(budgetDatabaseCalculateService.calculateYoyChg(propertyId, totalDetailDTO.getBudgetTotal(), totalDetailDTO.getActualTotal()));

            return totalDetailDTO;
        }

        /**
         * 汇总 revenue
         *
         * @param budgetRevenueByMonth 每月 revenue
         * @param year                 年
         * @param month                月
         * @return revenue
         * @author yyt
         * @date 2025/8/14
         **/
        private BigDecimal summaryRevenue(Map<Integer, List<BigDecimal>> budgetRevenueByMonth, int year, int month) {
            List<BigDecimal> revenues = budgetRevenueByMonth.get(Integer.parseInt(YearMonthUtils.DATE_TIME_FORMATTER.format(YearMonth.of(year, month))));
            return CollectionUtils.isEmpty(revenues) ? null : revenues.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
        }

        /**
         * 汇总 total detail
         *
         * @param propertyId 资产 id
         * @param title      title
         * @param revenues   revenue
         * @return total detail
         * @author yyt
         * @date 2025/8/14
         **/
        private TotalDetailDTO packageTotalDetailDTO(int propertyId, String title, List<OneYearRevenueBO> revenues) {
            BudgetYearDTO budgetYearDTO = budgetDatabaseCalculateService.calculateRevenueTotal(propertyId, revenues);
            TotalDetailDTO totalDetailDTO = beanMapper.map(budgetYearDTO, TotalDetailDTO.class);
            totalDetailDTO.setTitle(title);
            return totalDetailDTO;
        }

        /**
         * 获取 RoomTotal
         *
         * @param tab room tab
         * @return RoomTotal
         * @author yyt
         **/
        @Override
        public RoomTotalDTO listRoomTotal(BudgetDatabaseConfigRoomTab tab) {
            Integer tabId = tab.getId();
            Integer propertyId = tab.getPropertyId();
            Integer year = tab.getBudgetYear();
            // 若 other 指标 + room 指标，都没有配置，则 return
            List<BudgetDatabaseConfigRoomTabItem> roomTabItems = budgetDatabaseConfigRoomTabItemService.listByTabId(tabId);
            if (CollectionUtils.isEmpty(roomTabItems)) {
                return new RoomTotalDTO();
            }

            // 所有指标
            int size = roomTabItems.size();
            List<RoomTotalDetailDTO> roomTotalDetailDTOS = Lists.newArrayListWithExpectedSize(size);
            // 汇总指标最下面的 total 时使用
            List<OneYearRevenueBO> totalBOs = Lists.newArrayListWithExpectedSize(size);

            Map<Integer, List<BudgetDatabaseConfigRoomTabItem>> itemsByClassification = roomTabItems.stream().collect(Collectors.groupingBy(BudgetDatabaseConfigRoomTabItem::getClassification));
            // 查询 other 指标数据
            List<BudgetDatabaseConfigRoomTabItem> otherItems = itemsByClassification.getOrDefault(RoomClassificationEnum.OTHER.getCode(), Collections.emptyList());
            if(CollectionUtils.isNotEmpty(otherItems)){
                Map<Integer, BudgetDatabaseRoomOther> otherById = budgetDatabaseRoomOtherService.listByItemIds(otherItems.stream().map(BudgetDatabaseConfigRoomTabItem::getId).toList())
                        .stream().collect(Collectors.toMap(BudgetDatabaseRoomOther::getRoomId, v -> v));

                otherItems.sort(Comparator.comparing(BudgetDatabaseConfigRoomTabItem::getId));

                for (BudgetDatabaseConfigRoomTabItem other : otherItems) {
                    OneYearRevenueBO oneYearRevenueBO = beanMapper.map(otherById.getOrDefault(other.getId(), new BudgetDatabaseRoomOther()), OneYearRevenueBO.class);
                    totalBOs.add(oneYearRevenueBO);

                    RoomTotalDetailDTO detailDTO = beanMapper.map(budgetDatabaseCalculateService.calculateRevenueTotal(propertyId, Lists.newArrayList(oneYearRevenueBO)), RoomTotalDetailDTO.class);
                    detailDTO.setId(other.getId());
                    detailDTO.setName(other.getName());
                    detailDTO.setRevenueParticipateCalculate(other.getEnableBudgetRevenue());
                    roomTotalDetailDTOS.add(detailDTO);
                }
            }

            // room 数据汇总
            RoomTotalDetailDTO roomDetailDTO = beanMapper.map(packageRoomTotalDetailDTO(propertyId, year, Lists.newArrayList(tabId), false), RoomTotalDetailDTO.class);
            roomDetailDTO.setId(-1);
            roomDetailDTO.setName(BudgetDatabaseTitleCons.ROOM_REVENUE);
            roomTotalDetailDTOS.add(roomDetailDTO);
            totalBOs.add(beanMapper.map(roomDetailDTO, OneYearRevenueBO.class));

            // total
            RoomTotalDetailDTO totalDetailDTO = beanMapper.map(budgetDatabaseCalculateService.calculateRevenueTotal(propertyId, totalBOs), RoomTotalDetailDTO.class);
            totalDetailDTO.setId(BaseConstant.ZERO);
            totalDetailDTO.setName(BudgetDatabaseTitleCons.TOTAL_REVENUE);
            roomTotalDetailDTOS.add(totalDetailDTO);

            RoomTotalDTO roomTotalDTO = new RoomTotalDTO();
            // 最后修改人
            roomTotalDTO.setUpdatedInfo(budgetDatabaseDataLogRoomMonthStatisticsService.getLastLogDTO(tabId, RoomLogAreaCons.TOTAL));
            // 明细信息
            roomTotalDTO.setItems(roomTotalDetailDTOS);
            return roomTotalDTO;
        }

    }