# MonthlyReportServiceImpl.java

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

    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.InternalServerErrorException;
    import cloud.wconnect.commonlib.exception.ParseReportException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.workperformance.cons.monthlyreport.AreaEnum;
    import cloud.wconnect.workperformance.cons.monthlyreport.MonthlyReportErrorI18nConstant;
    import cloud.wconnect.workperformance.entity.monthlyreport.AreaTypeConfig;
    import cloud.wconnect.workperformance.entity.monthlyreport.MonthlyReport;
    import cloud.wconnect.workperformance.entity.monthlyreport.PropertyMonthlyReportSet;
    import cloud.wconnect.workperformance.mapper.monthlyreport.MonthlyReportMapper;
    import cloud.wconnect.workperformance.pojo.dto.monthlyreport.MonthlyReportImportDTO;
    import cloud.wconnect.workperformance.pojo.dto.monthlyreport.parsereport.ExpenseCategoryByReportDTO;
    import cloud.wconnect.workperformance.pojo.dto.monthlyreport.parsereport.ParseReportDTO;
    import cloud.wconnect.workperformance.pojo.dto.monthlyreport.parsereport.ProfitByReportDTO;
    import cloud.wconnect.workperformance.pojo.dto.monthlyreport.parsereport.RoomRevenueByReportDTO;
    import cloud.wconnect.workperformance.service.monthlyreport.*;
    import cloud.wconnect.workperformance.service.monthlyreport.parsereport.IParseReport;
    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.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.multipart.MultipartFile;

    import java.io.IOException;
    import java.io.InputStream;
    import java.text.MessageFormat;
    import java.time.ZoneId;
    import java.time.ZonedDateTime;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * <p>
     * 月报表 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2023-09-01
     */
    @Service
    @DS("monthly_report")
    public class MonthlyReportServiceImpl extends ServiceImpl<MonthlyReportMapper, MonthlyReport> implements IMonthlyReportService {

        @Autowired
        private ApplicationContext applicationContext;
        @Lazy
        @Autowired
        private IAreaTypeConfigService areaTypeConfigService;
        @Autowired
        private ObjectMapper objectMapper;
        @Lazy
        @Autowired
        private IRoomRevenueService roomRevenueService;
        @Lazy
        @Autowired
        private IExpenseCategoryService expenseCategoryService;
        @Lazy
        @Autowired
        private IProfitService profitService;
        @Lazy
        @Autowired
        private IPropertyMonthlyReportSetService propertyMonthlyReportSetService;

        /**
         * 查询月报
         *
         * @param propertyId 资产id
         * @param year       年
         * @param month      月
         * @return cloud.wconnect.workperformance.entity.monthlyreport.MonthlyReport
         * @author lichen
         * @date 2023/9/1
         */
        @Override
        public MonthlyReport getMonthlyReportByYearAndMonth(Integer propertyId, int year, int month) {
            return getOne(new LambdaQueryWrapper<MonthlyReport>()
                    .eq(MonthlyReport::getPropertyId, propertyId)
                    .eq(MonthlyReport::getYear, year)
                    .eq(MonthlyReport::getMonth, month));
        }

        /**
         * 查询月报集合
         *
         * @param propertyId 资产id
         * @param startDate  开始时间(yyyyMM)
         * @param endDate    结束时间(yyyyMM)
         * @return java.util.List<cloud.wconnect.workperformance.entity.monthlyreport.MonthlyReport>
         * @author zc
         * @date 2025/8/26
         */
        @Override
        public List<MonthlyReport> listByDateRange(int propertyId, long startDate, long endDate) {
            return list(new LambdaQueryWrapper<MonthlyReport>()
                    .eq(MonthlyReport::getPropertyId, propertyId)
                    .apply("year * 100 + month >= {0}", startDate)
                    .apply("year * 100 + month <= {0}", endDate));
        }

        /**
         * 检测报告是否存在
         *
         * @param reportId 报告id
         * @return cloud.wconnect.workperformance.entity.monthlyreport.MonthlyReport
         * @author lichen
         * @date 2023/9/1
         */
        @Override
        public MonthlyReport checkExist(long reportId) {
            MonthlyReport monthlyReport = getById(reportId);
            if (Objects.isNull(monthlyReport)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND, BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        MessageFormat.format("The monthly report not found, monthlyReportId:{0}", reportId));
            }
            return monthlyReport;
        }

        /**
         * 校验月报是否重复
         *
         * @param propertyId 资产id
         * @param year       年
         * @param month      月
         * @return
         */
        @Override
        public void checkRepeat(Integer propertyId, int year, int month) {
            long count = count(new LambdaQueryWrapper<MonthlyReport>()
                    .eq(MonthlyReport::getPropertyId, propertyId)
                    .eq(MonthlyReport::getYear, year)
                    .eq(MonthlyReport::getMonth, month));
            if (count > 0) {
                throw new BadRequestException(MonthlyReportErrorI18nConstant.MONTHLY_REPORT_ALREADY_EXIST, BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        MessageFormat.format("The monthly report already existed, propertyId:{0}, year:{1}, month:{2}",
                                propertyId, year, month));
            }
        }

        /**
         * 检查是否历史月报
         *
         * @param monthlyReport 月报
         * @param propertyDTO   资产DTO
         * @return
         */
        @Override
        public Boolean checkIsHistoryMonthlyReport(MonthlyReport monthlyReport, PropertyDTO propertyDTO) {
            //当前时间
            ZonedDateTime currentZonedDateTime = DateTimeUtils.dateNow(propertyDTO.getTimezone());

            //月报的时间
            Integer year = monthlyReport.getYear();
            Integer month = monthlyReport.getMonth();
            ZonedDateTime zonedDateTime = ZonedDateTime.of(year, month, 1, 0, 0, 0, 0,
                    ZoneId.of(propertyDTO.getTimezone()));
            return DateTimeUtils.differenceMonth(currentZonedDateTime, zonedDateTime) < -1;
        }

        /**
         * 查询正在使用和未来的月报
         *
         * @param propertyId 资产id
         * @param timezone   时区
         * @return java.util.List<cloud.wconnect.workperformance.entity.monthlyreport.MonthlyReport>
         * @author lichen
         * @date 2023/9/5
         */
        @Override
        public List<MonthlyReport> listUsingAndFuture(Integer propertyId, String timezone) {
            //当前正在使用的月报时间（上个月）
            ZonedDateTime lastMonthZonedDateTime = DateTimeUtils.dateNow(timezone).minusMonths(1);
            return list(new LambdaQueryWrapper<MonthlyReport>()
                    .eq(MonthlyReport::getPropertyId, propertyId)
                    .and(andQueryWrapper ->
                            andQueryWrapper.nested(query -> query.eq(MonthlyReport::getYear, lastMonthZonedDateTime.getYear())
                                            .ge(MonthlyReport::getMonth, lastMonthZonedDateTime.getMonthValue()))
                                    .or().gt(MonthlyReport::getYear, lastMonthZonedDateTime.getYear())));
        }

        /**
         * 导入报告
         *
         * @param propertyDTO            资产DTO
         * @param file                   文件
         * @param monthlyReportImportDTO 导入dto
         * @param monthlyReport          月报
         * @param loginUserId            用户id
         * @return void
         * @author lichen
         * @date 2023/9/6
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void importReport(PropertyDTO propertyDTO, MultipartFile file, MonthlyReportImportDTO monthlyReportImportDTO, MonthlyReport monthlyReport, int loginUserId) {
            ParseReportDTO parseReportDTO = null;
            try (InputStream in = file.getInputStream()) {
                Collection<IParseReport> parseReports = applicationContext.getBeansOfType(IParseReport.class).values();
                for (IParseReport parseReport : parseReports) {
                    if (parseReport.supports(monthlyReportImportDTO.getSystem(), monthlyReportImportDTO.getReportType())) {
                        parseReportDTO = parseReport.parse(propertyDTO, in);
                    }
                }
            } catch (IOException e) {
                throw new ParseReportException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "Exception occured when importing report", e);
            }

            if (Objects.isNull(parseReportDTO)) {
                throw new InternalServerErrorException(BaseErrorCodeEnum.INTERNAL_SERVICE_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.INTERNAL_SERVICE_ERROR.getCode(),
                        "the IParseReport is not found; system: " + monthlyReportImportDTO.getSystem());
            }

            //保存导入的数据
            saveImportData(propertyDTO.getTenantId(), propertyDTO.getId(), loginUserId, monthlyReport, parseReportDTO);
        }

        /**
         * 保存导入的数据
         *
         * @param tenantId       租户id
         * @param propertyId     资产id
         * @param loginUserId    当前登录人
         * @param monthlyReport  月报
         * @param parseReportDTO 导入的数据
         * @return void
         * @author lichen
         * @date 2023/9/6
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveImportData(Integer tenantId, Integer propertyId, int loginUserId,
                                   MonthlyReport monthlyReport, ParseReportDTO parseReportDTO) {
            //查询支持导入区域的分类
            Map<Integer, List<AreaTypeConfig>> areaId2AreaTypeConfigList = areaTypeConfigService.listByAreaId(propertyId, Lists.newArrayList(AreaEnum.ROOM_REVENUE.getAreaId(),
                            AreaEnum.PROFIT.getAreaId(), AreaEnum.EXPENSES.getAreaId()))
                    .stream().collect(Collectors.groupingBy(AreaTypeConfig::getAreaId));

            //查询月报开通的区域
            List<Integer> areaIds = JsonUtils.convertStringToList(objectMapper, monthlyReport.getAreaIds(), Integer.class);

            List<RoomRevenueByReportDTO> roomRevenueByReportDTOList = parseReportDTO.getRoomRevenueByReportDTOList();
            List<AreaTypeConfig> areaTypeConfigByRevenueCompSet = areaId2AreaTypeConfigList.getOrDefault(AreaEnum.ROOM_REVENUE.getAreaId(), Collections.emptyList());
            if (areaIds.contains(AreaEnum.ROOM_REVENUE.getAreaId())
                    && CollectionUtils.isNotEmpty(roomRevenueByReportDTOList) && CollectionUtils.isNotEmpty(areaTypeConfigByRevenueCompSet)) {
                //保存Room Revenue区域内容
                roomRevenueService.saveImportData(tenantId, propertyId, monthlyReport, loginUserId, roomRevenueByReportDTOList, areaTypeConfigByRevenueCompSet);
            }

            List<ProfitByReportDTO> profitByReportDTOList = parseReportDTO.getProfitByReportDTOList();
            List<AreaTypeConfig> areaTypeConfigByProfit = areaId2AreaTypeConfigList.getOrDefault(AreaEnum.PROFIT.getAreaId(), Collections.emptyList());
            if (areaIds.contains(AreaEnum.PROFIT.getAreaId())
                    && CollectionUtils.isNotEmpty(profitByReportDTOList) && CollectionUtils.isNotEmpty(areaTypeConfigByProfit)) {
                //保存Profit区域内容
                profitService.saveImportData(tenantId, propertyId, monthlyReport, loginUserId, profitByReportDTOList, areaTypeConfigByProfit);
            }

            List<ExpenseCategoryByReportDTO> expenseCategoryByReportDTOList = parseReportDTO.getExpenseCategoryByReportDTOList();
            List<AreaTypeConfig> areaTypeConfigByExpenses = areaId2AreaTypeConfigList.getOrDefault(AreaEnum.EXPENSES.getAreaId(), Collections.emptyList());
            if (areaIds.contains(AreaEnum.EXPENSES.getAreaId())
                    && CollectionUtils.isNotEmpty(expenseCategoryByReportDTOList) && CollectionUtils.isNotEmpty(areaTypeConfigByExpenses)) {
                //保存Expenses区域内容
                expenseCategoryService.saveImportData(tenantId, propertyId, monthlyReport.getId(), loginUserId, expenseCategoryByReportDTOList, areaTypeConfigByExpenses);
            }
        }

        /**
         * 初始化区域数据
         *
         * @param needInitDataAreaIds 需要初始化的区域id集合
         * @param monthlyReports      月报集合
         * @param propertyDTO         资产DTO
         * @return void
         * @author lichen
         * @date 2023/9/8
         */
        public void initAreaData(List<Integer> needInitDataAreaIds, List<MonthlyReport> monthlyReports, PropertyDTO propertyDTO) {
            Map<Integer, List<AreaTypeConfig>> areaId2AreaTypeConfigs = areaTypeConfigService.listByAreaId(propertyDTO.getId(), Collections.emptyList())
                    .stream().collect(Collectors.groupingBy(AreaTypeConfig::getAreaId));
            //过滤出要初始化区域数据对应的业务层
            List<String> initAreaDataServiceImpls = Arrays.stream(AreaEnum.values())
                    .filter(areaEnum -> needInitDataAreaIds.contains(areaEnum.getAreaId()) && StringUtils.isNotBlank(areaEnum.getInitAreaServiceImpl()))
                    .map(AreaEnum::getInitAreaServiceImpl)
                    .flatMap(item -> Arrays.stream(item.split(",")))
                    .toList();
            initAreaDataServiceImpls.forEach(serviceImpl -> {
                IHandlePropertySettingChange handlePropertySettingChange = (IHandlePropertySettingChange) applicationContext.getBean(serviceImpl);
                //初始化区域对应的数据
                handlePropertySettingChange.init(propertyDTO, monthlyReports, areaId2AreaTypeConfigs);
            });
        }

        /**
         * 新增月报
         *
         * @param year        年
         * @param month       月
         * @param propertyDTO 资产DTO
         * @param loginUserId 用户id
         * @return 月报id
         * @author lichen
         * @date 2023/9/8
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public Long save(int year, int month, PropertyDTO propertyDTO, int loginUserId) {
            //校验资产是否配置区域
            PropertyMonthlyReportSet propertyMonthlyReportSet = propertyMonthlyReportSetService.checkSettingExist(propertyDTO.getId());

            MonthlyReport monthlyReport = MonthlyReport.builder()
                    .tenantId(propertyDTO.getTenantId())
                    .propertyId(propertyDTO.getId())
                    .year(year)
                    .month(month)
                    .areaIds(propertyMonthlyReportSet.getAreaIds())
                    .createId(loginUserId)
                    .createTime(DateTimeUtils.nowTimestamp())
                    .build();
            save(monthlyReport);

            //初始化月报数据
            List<Integer> neetInitDataAreaIds = JsonUtils.convertStringToList(objectMapper, propertyMonthlyReportSet.getAreaIds(), Integer.class);
            initAreaData(neetInitDataAreaIds, Lists.newArrayList(monthlyReport), propertyDTO);

            return monthlyReport.getId();
        }

        /**
         * 根据年、月查询月报
         *
         * @param propertyId 资产id
         * @param year       年
         * @param month      月
         * @return java.util.List<cloud.wconnect.workperformance.entity.monthlyreport.MonthlyReport>
         * @author lichen
         * @date 2025/12/30
         */
        @Override
        public List<MonthlyReport> listByYearAndMonth(int propertyId, int year, int month) {
            return list(new LambdaQueryWrapper<MonthlyReport>()
                    .eq(MonthlyReport::getPropertyId, propertyId)
                    .eq(MonthlyReport::getYear, year)
                    .eq(month > 0, MonthlyReport::getMonth, month)
                    .orderByDesc(MonthlyReport::getMonth));
        }
    }