# ProfitServiceImpl.java

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

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.MoudleEventSubjectEnum;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.TransactionUtils;
    import cloud.wconnect.commonlib.util.YearMonthUtils;
    import cloud.wconnect.sns.http.processor.Sender;
    import cloud.wconnect.workperformance.cons.monthlyreport.AreaEnum;
    import cloud.wconnect.workperformance.entity.monthlyreport.*;
    import cloud.wconnect.workperformance.mapper.monthlyreport.ProfitMapper;
    import cloud.wconnect.workperformance.mapper.monthlyreport.ReportRemarkMapper;
    import cloud.wconnect.workperformance.pojo.bo.modelevent.monthlyreport.MonthlyReportEventBO;
    import cloud.wconnect.workperformance.pojo.bo.modelevent.monthlyreport.MonthlyReportEventDataBO;
    import cloud.wconnect.workperformance.pojo.bo.modelevent.monthlyreport.MonthlyReportEventValueBO;
    import cloud.wconnect.workperformance.pojo.dto.monthlyreport.parsereport.ProfitByReportDTO;
    import cloud.wconnect.workperformance.pojo.dto.monthlyreport.profit.ProfitDTO;
    import cloud.wconnect.workperformance.pojo.dto.monthlyreport.profit.ProfitDetailDTO;
    import cloud.wconnect.workperformance.service.monthlyreport.*;
    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 org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.time.YearMonth;
    import java.util.*;
    import java.util.concurrent.Executor;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * 月报Profit（利润）区域表 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2023-09-01
     */
    @Service
    @DS("monthly_report")
    public class ProfitServiceImpl extends ServiceImpl<ProfitMapper, Profit> implements IProfitService, IHandlePropertySettingChange, IHandleAreaTypeConfigChange {

        @Lazy
        @Autowired
        private IMonthlyReportService monthlyReportService;
        @Autowired
        private IReportRemarkService reportRemarkService;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private IAreaTypeConfigService areaTypeConfigService;
        @Autowired
        private ReportRemarkMapper reportRemarkMapper;
        @Autowired
        private ProfitMapper profitMapper;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private Executor executor;
        @Autowired
        @Qualifier("moduleEventSender")
        private Sender moduleEventSender;

        /**
         * 查询Profit区域数据
         *
         * @param monthlyReport 月报
         * @param propertyDTO   资产DTO
         * @return cloud.wconnect.workperformance.pojo.dto.monthlyreport.profit.ProfitDetailDTO
         * @author lichen
         * @date 2023/9/2
         */
        @Override
        public ProfitDetailDTO getProfit(MonthlyReport monthlyReport, PropertyDTO propertyDTO) {
            //查询备注
            String remark = reportRemarkService.listByReportIdAndAreaId(monthlyReport.getId(), AreaEnum.PROFIT.getAreaId());

            List<ProfitDTO> profitDTOList = list(new LambdaQueryWrapper<Profit>()
                    .eq(Profit::getReportId, monthlyReport.getId())
                    .eq(Profit::getPropertyId, propertyDTO.getId())
                    .orderByAsc(Profit::getCategoryId))
                    .stream().map(item -> beanMapper.map(item, ProfitDTO.class)).toList();

            //校验是否历史月报
            Boolean isHistoryMonthlyReport = monthlyReportService.checkIsHistoryMonthlyReport(monthlyReport, propertyDTO);

            /**
             * 历史月报：按照区域表存储的数据，返回
             * 当前正在使用或未来的数据：按照最新的区域类型配置 组装返回对象
             */
            if (isHistoryMonthlyReport) {
                return new ProfitDetailDTO(profitDTOList, remark);
            }

            Map<Integer, ProfitDTO> categoryId2DTO = profitDTOList.stream()
                    .collect(Collectors.toMap(ProfitDTO::getCategoryId, Function.identity()));
            //区域分类集合
            List<AreaTypeConfig> areaTypeConfigs = areaTypeConfigService.listByAreaId(propertyDTO.getId(), Lists.newArrayList(AreaEnum.PROFIT.getAreaId()));

            List<ProfitDTO> resultList = Lists.newArrayListWithCapacity(areaTypeConfigs.size());
            for (AreaTypeConfig areaTypeConfig : areaTypeConfigs) {
                ProfitDTO profitDTO = categoryId2DTO.get(areaTypeConfig.getId());
                if (Objects.isNull(profitDTO)) {
                    resultList.add(ProfitDTO.builder()
                            .categoryId(areaTypeConfig.getId())
                            .categoryName(areaTypeConfig.getName())
                            .contentType(areaTypeConfig.getContentType())
                            .build());
                } else {
                    resultList.add(profitDTO);
                }
            }
            return new ProfitDetailDTO(resultList, remark);
        }

        /**
         * 保存Profit区域数据
         *
         * @param monthlyReport   月报
         * @param propertyDTO     资产DTO
         * @param profitDetailDTO 区域信息
         * @param loginUserId     登录用户id
         * @return void
         * @author lichen
         * @date 2023/9/2
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void save(MonthlyReport monthlyReport, PropertyDTO propertyDTO, ProfitDetailDTO profitDetailDTO, int loginUserId) {
            remove(new LambdaQueryWrapper<Profit>()
                    .eq(Profit::getReportId, monthlyReport.getId())
                    .eq(Profit::getPropertyId, propertyDTO.getId()));

            //保存区域信息
            List<Profit> profits = beanMapper.mapList(profitDetailDTO.getList(), Profit.class, (dto, entity) -> {
                entity.setTenantId(propertyDTO.getTenantId());
                entity.setPropertyId(propertyDTO.getId());
                entity.setReportId(monthlyReport.getId());
                entity.setUpdateId(loginUserId);
                entity.setUpdateTime(DateTimeUtils.nowTimestamp());
            });
            saveBatch(profits);

            //保存备注
            ReportRemark reportRemark = ReportRemark.builder()
                    .tenantId(propertyDTO.getTenantId())
                    .propertyId(propertyDTO.getId())
                    .reportId(monthlyReport.getId())
                    .areaId(AreaEnum.PROFIT.getAreaId())
                    .remark(profitDetailDTO.getRemark())
                    .build();
            reportRemarkMapper.saveOrUpdateByUnique(reportRemark);

            HashMap<Long, MonthlyReport> id2MonthlyReport = new HashMap<>();
            id2MonthlyReport.put(monthlyReport.getId(), monthlyReport);
            //发送事件
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                this.sendProfitSnsEvent(propertyDTO.getId(), profits, id2MonthlyReport);
            });
        }

        /**
         * Profit 区域数据变化时，发送sns事件
         *
         * @param propertyId       资产id
         * @param profitList       Room Revenue区域数据
         * @param id2MonthlyReport 月报集合
         * @author zc
         * @date 2025/8/26
         */
        private void sendProfitSnsEvent(int propertyId, List<Profit> profitList, Map<Long, MonthlyReport> id2MonthlyReport) {
            Map<Long, List<Profit>> reportId2ProfitList = profitList.stream().collect(Collectors.groupingBy(Profit::getReportId));

            List<MonthlyReportEventDataBO> monthlyReportEventDataBOS = Lists.newArrayListWithCapacity(id2MonthlyReport.size());
            id2MonthlyReport.forEach((reportId, monthlyReport) -> {
                //获取当前月的数据并转化成BO
                List<MonthlyReportEventValueBO> monthlyReportEventValueBOS = reportId2ProfitList.getOrDefault(reportId, Collections.emptyList())
                        .stream().map(item -> MonthlyReportEventValueBO.builder()
                                .categoryName(item.getCategoryName())
                                .target(item.getBudget())
                                .actual(item.getActual())
                                .ly(item.getLastYear())
                                .build()
                        ).toList();

                //生成每个月的数据并添加到集合中
                long yearMonth = YearMonthUtils.parseYearMonthToLong(YearMonth.of(monthlyReport.getYear(), monthlyReport.getMonth()));
                monthlyReportEventDataBOS.add(MonthlyReportEventDataBO.builder()
                        .yearMonth(yearMonth)
                        .data(monthlyReportEventValueBOS)
                        .build());
            });
            MonthlyReportEventBO monthlyReportEventBO = new MonthlyReportEventBO(propertyId, monthlyReportEventDataBOS);
            moduleEventSender.publish(MoudleEventSubjectEnum.MONTHLY_REPORT_PROFIT.getSubject(),
                    JsonUtils.convertObjToString(objectMapper, monthlyReportEventBO));
        }

        /**
         * 保存导入的数据
         *
         * @param tenantId              租户id
         * @param propertyId            资产id
         * @param monthlyReport         月报
         * @param userId                用户id
         * @param profitByReportDTOList 区域数据集合
         * @param areaTypeConfigs       区域分类集合
         * @return void
         * @author lichen
         * @date 2023/9/7
         */
        @Override
        public void saveImportData(Integer tenantId, Integer propertyId, MonthlyReport monthlyReport, int userId,
                                   List<ProfitByReportDTO> profitByReportDTOList, List<AreaTypeConfig> areaTypeConfigs) {
            Map<String, AreaTypeConfig> name2Category = areaTypeConfigs.stream().collect(Collectors.toMap(AreaTypeConfig::getName, Function.identity()));
            Set<String> categoryNames = name2Category.keySet();

            List<Profit> profits = profitByReportDTOList.stream()
                    .filter(item -> categoryNames.contains(item.getCategoryName()))
                    .map(item -> beanMapper.map(item, Profit.class, entity -> {
                        entity.setTenantId(tenantId);
                        entity.setPropertyId(propertyId);
                        entity.setReportId(monthlyReport.getId());
                        entity.setCategoryId(name2Category.get(item.getCategoryName()).getId());
                        entity.setContentType(name2Category.get(item.getCategoryName()).getContentType());
                        entity.setUpdateId(userId);
                        entity.setUpdateTime(DateTimeUtils.nowTimestamp());
                    })).toList();
            if (CollectionUtils.isEmpty(profits)) return;
            profitMapper.saveOrUpdateByUnique(profits);

            Map<Long, MonthlyReport> reportId2MonthlyReport = new HashMap<>();
            reportId2MonthlyReport.put(monthlyReport.getId(), monthlyReport);
            List<Profit> list = list(new LambdaQueryWrapper<Profit>()
                    .eq(Profit::getReportId, monthlyReport.getId())
                    .eq(Profit::getPropertyId, propertyId));
            //发送事件
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                this.sendProfitSnsEvent(propertyId, list, reportId2MonthlyReport);
            });
        }

        /**
         * 删除区域数据
         *
         * @param propertyDTO    资产DTO
         * @param monthlyReports 月报集合
         * @return void
         * @author lichen
         * @date 2023/9/5
         */
        @Override
        public void delete(PropertyDTO propertyDTO, List<MonthlyReport> monthlyReports) {
            List<Long> reportIds = monthlyReports.stream().map(MonthlyReport::getId).toList();
            if (CollectionUtils.isEmpty(reportIds)) return;
            remove(new LambdaQueryWrapper<Profit>()
                    .in(Profit::getReportId, reportIds)
                    .eq(Profit::getPropertyId, propertyDTO.getId()));

            Map<Long, MonthlyReport> reportId2MonthlyReport = monthlyReports.stream()
                    .collect(Collectors.toMap(MonthlyReport::getId, Function.identity()));
            //发送事件
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                this.sendProfitSnsEvent(propertyDTO.getId(), Collections.emptyList(), reportId2MonthlyReport);
            });
        }

        /**
         * 更新分类
         *
         * @param propertyDTO       资产DTO
         * @param monthlyReports    月报集合
         * @param areaTypeConfig    分类
         * @param oriAreaTypeConfig 修改前的 分类
         * @return void
         * @author lichen
         * @date 2023/9/6
         */
        @Override
        public void updateCategory(PropertyDTO propertyDTO, List<MonthlyReport> monthlyReports, AreaTypeConfig areaTypeConfig, AreaTypeConfig oriAreaTypeConfig) {
            List<Long> monthlyReportIds = monthlyReports.stream().map(MonthlyReport::getId).collect(Collectors.toList());

            //查询需要更新分类名称的数据
            List<Profit> profits = list(new LambdaQueryWrapper<Profit>()
                    .in(Profit::getReportId, monthlyReportIds)
                    .eq(Profit::getPropertyId, propertyDTO.getId())
                    .eq(Profit::getCategoryId, areaTypeConfig.getId()));
            if (CollectionUtils.isEmpty(profits)) return;

            //设置新的分类名称 和 内容类型
            profits.forEach(item -> item.setCategoryName(areaTypeConfig.getName()).setContentType(areaTypeConfig.getContentType()));
            //更新
            updateBatchById(profits);

            Map<Long, MonthlyReport> reportId2MonthlyReport = monthlyReports.stream()
                    .collect(Collectors.toMap(MonthlyReport::getId, Function.identity()));
            List<Profit> list = list(new LambdaQueryWrapper<Profit>()
                    .in(Profit::getReportId, monthlyReportIds)
                    .eq(Profit::getPropertyId, propertyDTO.getId()));

            //发送事件
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                this.sendProfitSnsEvent(propertyDTO.getId(), list, reportId2MonthlyReport);
            });
        }

        /**
         * 删除区域分类id对应的数据
         *
         * @param propertyDTO    资产DTO
         * @param monthlyReports 月报集合
         * @param categoryId     分类id
         * @return void
         * @author lichen
         * @date 2023/9/6
         */
        @Override
        public void deleteAreaDataByCategoryId(PropertyDTO propertyDTO, List<MonthlyReport> monthlyReports, Integer categoryId) {
            List<Long> monthlyReportIds = monthlyReports.stream().map(MonthlyReport::getId).collect(Collectors.toList());

            //删除区域分类id对应的数据
            remove(new LambdaQueryWrapper<Profit>()
                    .in(Profit::getReportId, monthlyReportIds)
                    .eq(Profit::getPropertyId, propertyDTO.getId())
                    .eq(Profit::getCategoryId, categoryId));

            Map<Long, MonthlyReport> reportId2MonthlyReport = monthlyReports.stream()
                    .collect(Collectors.toMap(MonthlyReport::getId, Function.identity()));
            List<Profit> list = list(new LambdaQueryWrapper<Profit>()
                    .in(Profit::getReportId, monthlyReportIds)
                    .eq(Profit::getPropertyId, propertyDTO.getId()));

            //发送事件
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                this.sendProfitSnsEvent(propertyDTO.getId(), list, reportId2MonthlyReport);
            });
        }

        /**
         * 根据分类，新增区域数据
         *
         * @param propertyDTO    资产DTO
         * @param monthlyReports 月报集合
         * @param areaTypeConfig 区域分类
         * @return void
         * @author lichen
         * @date 2023/9/8
         */
        @Override
        public void insertAreaDataByCategory(PropertyDTO propertyDTO, List<MonthlyReport> monthlyReports, AreaTypeConfig areaTypeConfig) {
            List<Profit> profits = monthlyReports.stream()
                    .map(monthlyReport -> Profit.builder()
                            .tenantId(propertyDTO.getTenantId())
                            .propertyId(propertyDTO.getId())
                            .reportId(monthlyReport.getId())
                            .categoryId(areaTypeConfig.getId())
                            .categoryName(areaTypeConfig.getName())
                            .contentType(areaTypeConfig.getContentType())
                            .updateId(0)
                            .updateTime(DateTimeUtils.nowTimestamp())
                            .build())
                    .toList();
            saveBatch(profits);

            List<Long> monthlyReportIds = monthlyReports.stream().map(MonthlyReport::getId).toList();
            Map<Long, MonthlyReport> reportId2MonthlyReport = monthlyReports.stream()
                    .collect(Collectors.toMap(MonthlyReport::getId, Function.identity()));
            List<Profit> list = list(new LambdaQueryWrapper<Profit>()
                    .in(Profit::getReportId, monthlyReportIds)
                    .eq(Profit::getPropertyId, propertyDTO.getId()));
            //发送事件
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                this.sendProfitSnsEvent(propertyDTO.getId(), list, reportId2MonthlyReport);
            });
        }

        /**
         * 初始化区域数据
         *
         * @param propertyDTO            资产DTO
         * @param monthlyReports         月报集合
         * @param areaId2AreaTypeConfigs 区域分类集合
         * @return void
         * @author lichen
         * @date 2023/9/8
         */
        @Override
        public void init(PropertyDTO propertyDTO, List<MonthlyReport> monthlyReports,
                         Map<Integer, List<AreaTypeConfig>> areaId2AreaTypeConfigs) {
            //过滤出开通对应区域的月报
            List<MonthlyReport> openAreaMonthlyReports = monthlyReports.stream().filter(monthlyReport -> {
                List<Integer> openAreaIds = JsonUtils.convertStringToList(objectMapper, monthlyReport.getAreaIds(), Integer.class);
                return openAreaIds.contains(AreaEnum.PROFIT.getAreaId());
            }).toList();

            if (CollectionUtils.isEmpty(openAreaMonthlyReports)) return;

            //获取分类集合
            List<AreaTypeConfig> areaTypeConfigs = areaId2AreaTypeConfigs.get(AreaEnum.PROFIT.getAreaId());
            if (CollectionUtils.isEmpty(areaTypeConfigs)) return;

            List<Profit> profits = Lists.newArrayListWithCapacity(areaTypeConfigs.size() * openAreaMonthlyReports.size());

            for (MonthlyReport openAreaMonthlyReport : openAreaMonthlyReports) {
                for (AreaTypeConfig areaTypeConfig : areaTypeConfigs) {
                    profits.add(Profit.builder()
                            .tenantId(propertyDTO.getTenantId())
                            .propertyId(propertyDTO.getId())
                            .reportId(openAreaMonthlyReport.getId())
                            .categoryId(areaTypeConfig.getId())
                            .categoryName(areaTypeConfig.getName())
                            .contentType(areaTypeConfig.getContentType())
                            .updateId(0)
                            .updateTime(DateTimeUtils.nowTimestamp())
                            .build());
                }
            }
            saveBatch(profits);

            List<Long> monthlyReportIds = openAreaMonthlyReports.stream().map(MonthlyReport::getId).toList();
            Map<Long, MonthlyReport> reportId2MonthlyReport = openAreaMonthlyReports.stream()
                    .collect(Collectors.toMap(MonthlyReport::getId, Function.identity()));
            List<Profit> list = list(new LambdaQueryWrapper<Profit>()
                    .in(Profit::getReportId, monthlyReportIds)
                    .eq(Profit::getPropertyId, propertyDTO.getId()));

            //发送事件
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                this.sendProfitSnsEvent(propertyDTO.getId(), list, reportId2MonthlyReport);
            });
        }
    }