# PlReportController.java

    package cloud.wconnect.finance.controller.pl;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.config.jxlscommand.StyleCommand;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    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.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.result.MsgDTO;
    import cloud.wconnect.commonlib.pojo.dto.m3Config.PropertyM3ConfigDTO;
    import cloud.wconnect.commonlib.pojo.dto.m3Config.TenantM3ConfigDTO;
    import cloud.wconnect.commonlib.pojo.dto.result.ResultDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.M3ConfigInternalService;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.YearMonthUtils;
    import cloud.wconnect.finance.cons.pl.PlErrorI18nConstant;
    import cloud.wconnect.finance.entity.pl.M3Forecast;
    import cloud.wconnect.finance.entity.pl.M3ForecastName;
    import cloud.wconnect.finance.entity.pl.M3PlReport;
    import cloud.wconnect.finance.pojo.dto.pl.ExportPLReportDTO;
    import cloud.wconnect.finance.pojo.dto.pl.PlReportLogDTO;
    import cloud.wconnect.finance.pojo.dto.pl.m3api.CustomerCloseAccountDTO;
    import cloud.wconnect.finance.service.pl.*;
    import com.google.common.collect.ImmutableMap;
    import jakarta.servlet.http.HttpServletResponse;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.jxls.builder.xls.XlsCommentAreaBuilder;
    import org.jxls.transform.poi.JxlsPoiTemplateFillerBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.i18n.LocaleContextHolder;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.web.bind.annotation.*;

    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.charset.StandardCharsets;
    import java.time.YearMonth;
    import java.time.ZoneId;
    import java.util.*;
    import java.util.concurrent.Executor;

    /**
     * PL Report 控制层
     *
     * @author lichen
     * @date 2025/9/29
     */
    @RestController
    @RequestMapping("/pl/report")
    public class PlReportController {

        @Autowired
        private SessionContext sessionContext;
        @Autowired
        private IM3PlReportService m3PlReportService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private IM3PlReportLogService m3PlReportLogService;
        @Autowired
        private M3ApiService m3ApiService;
        @Autowired
        private M3ConfigInternalService m3ConfigInternalService;
        @Autowired
        private Executor executor;
        @Autowired
        private M3DataService m3DataService;
        @Autowired
        private IM3ForecastsNameService m3ForecastsNameService;

        /**
         * 获取PL报告
         *
         * @param property  资产标识
         * @param yearMonth 年月
         * @return java.util.Map<java.lang.String, java.lang.String>
         * @author lichen
         * @date 2025/10/2
         */
        @ApiId(1608)
        @GetMapping
        public Map<String, String> getReportInfo(String property, @RequestParam String yearMonth) {
            int propertyId = sessionContext.getPropertyId(property);
            //校验年-月格式
            checkYearMonth(yearMonth);
            //查询报告数据
            M3PlReport m3PlReport = m3PlReportService.listByYearMonth(propertyId, Integer.valueOf(yearMonth));
            String info = Objects.nonNull(m3PlReport) ? m3PlReport.getInfo() : "";
            return ImmutableMap.of("info", info);
        }

        /**
         * 刷新PL报告
         *
         * @param property  资产标识
         * @param yearMonth 年月
         * @return cloud.wconnect.commonlib.pojo.dto.result.ResultDTO<java.util.Map < java.lang.String, java.lang.Integer>>
         * @author lichen
         * @date 2025/11/12
         */
        @ApiId(1609)
        @PostMapping("/refresh")
        public ResultDTO<Map<String, Integer>> refreshReport(String property, @RequestParam String yearMonth) {
            int propertyId = sessionContext.getPropertyId(property);
            //校验年-月格式
            YearMonth refreshYearMonth = checkYearMonth(yearMonth);

            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            ZoneId zoneId = ZoneId.of(propertyDTO.getTimezone());
            YearMonth currentYearMonth = YearMonth.now(zoneId);

            if (!refreshYearMonth.isBefore(currentYearMonth)) {
                //阻止刷新当月及未来的报表
                throw new BadRequestException(PlErrorI18nConstant.CAN_ONLY_REFRESH_HISTORY_MONTH_REPORT,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Only the reports of historical months can be refreshed");
            }

            //校验租户M3配置
            TenantM3ConfigDTO tenantM3ConfigDTO = checkTenantM3Config(propertyId);
            //校验资产M3配置
            PropertyM3ConfigDTO propertyM3ConfigDTO = checkPropertyM3Config(tenantM3ConfigDTO, propertyId);

            //客户的关账信息
            CustomerCloseAccountDTO customerCloseAccountDTO = m3ApiService.getCustomerCloseAccount(tenantM3ConfigDTO, Integer.toString(refreshYearMonth.getYear()));

            //刷新的月份
            int yearMonthInteger = Integer.parseInt(yearMonth);

            //当前登录用户id
            int loginUserId = sessionContext.getLoginUserId();

            //校验客户是否关账
            boolean closed = m3DataService.isClosed(customerCloseAccountDTO, propertyM3ConfigDTO, yearMonthInteger);
            if (closed) {
                //校验是否可以生成报告
                m3PlReportService.checkCanGeneratePLReport(propertyId, yearMonthInteger, loginUserId);

                //插入处理中的log
                Integer logId = m3PlReportLogService.insertProcessingLog(propertyDTO.getTenantId(), propertyDTO.getId(), yearMonthInteger, loginUserId);

                //异步生成P&L报告
                executor.execute(() -> {
                    //处理M3基础数据
                    m3DataService.handleM3BasicData(tenantM3ConfigDTO, propertyM3ConfigDTO, yearMonthInteger, loginUserId, logId);
                    //生成P&L报告
                    m3PlReportService.generatePLReport(propertyDTO.getId(), yearMonthInteger, loginUserId, logId);
                });
                return ResultDTO.success(Map.of("logId", logId));
            } else {
                //客户未关账
                List<MsgDTO> errorMsgList = i18nMessageUtils.getAllLanguageMsg(PlErrorI18nConstant.CUSTOMER_NOT_CLOSE_ACCOUNT);
                return ResultDTO.dataValidationError(errorMsgList);
            }
        }

        /**
         * 校验资产M3配置
         *
         * @param tenantM3ConfigDTO 租户M3配置
         * @param propertyId        资产id
         * @return cloud.wconnect.commonlib.pojo.dto.m3Config.PropertyM3ConfigDTO
         * @author lichen
         * @date 2025/10/2
         */
        private PropertyM3ConfigDTO checkPropertyM3Config(TenantM3ConfigDTO tenantM3ConfigDTO, int propertyId) {
            List<PropertyM3ConfigDTO> propertyM3ConfigDTOS = tenantM3ConfigDTO.getPropertyM3ConfigDTOS();
            if (CollectionUtils.isEmpty(propertyM3ConfigDTOS)) {
                //资产的M3配置不存在
                throw new BadRequestException(PlErrorI18nConstant.PROPERTY_M3_CONFIG_NOT_EXIST,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "property m3 config not exist");
            }
            return propertyM3ConfigDTOS.get(0);
        }

        /**
         * 校验租户的M3配置
         *
         * @param propertyId 资产id
         * @return cloud.wconnect.commonlib.pojo.dto.m3Config.TenantM3ConfigDTO
         * @author lichen
         * @date 2025/10/2
         */
        private TenantM3ConfigDTO checkTenantM3Config(int propertyId) {
            //获取资产的M3配置
            List<TenantM3ConfigDTO> tenantM3ConfigDTOS = m3ConfigInternalService.listPropertyM3Config(Collections.singletonList(propertyId));
            if (CollectionUtils.isEmpty(tenantM3ConfigDTOS)) {
                //租户的M3配置不存在
                throw new BadRequestException(PlErrorI18nConstant.TENANT_M3_CONFIG_NOT_EXIST,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "tenant m3 config not exist");
            }
            return tenantM3ConfigDTOS.get(0);
        }

        /**
         * 获取PL报告日志
         *
         * @param property  资产标识
         * @param yearMonth 年月
         * @param id        日志id
         * @return java.util.List<cloud.wconnect.finance.pojo.dto.pl.PlReportLogDTO>
         * @author lichen
         * @date 2025/9/29
         */
        @ApiId(1610)
        @GetMapping("/log")
        public List<PlReportLogDTO> getReportLog(String property, @RequestParam String yearMonth,
                                                 @RequestParam(required = false) Integer id) {
            int propertyId = sessionContext.getPropertyId(property);
            Integer tenantId = sessionContext.getTenantSymbolBO().getId();
            //校验年-月格式
            checkYearMonth(yearMonth);
            //查询报告日志
            return m3PlReportLogService.listByYearMonth(tenantId, propertyId, Integer.valueOf(yearMonth), id);
        }

        /**
         * 导出调账数据
         *
         * @param property  资产标识
         * @param yearMonth 年月
         * @author lichen
         * @date 2026/3/17
         */
        @ApiId(1702)
        @GetMapping("/export")
        public void export(String property, @RequestParam String yearMonth, @RequestParam Long forecastId, HttpServletResponse response) {
            //校验年-月格式
            checkYearMonth(yearMonth);

            int propertyId = sessionContext.getPropertyId(property);
            //查询资产信息
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            //查询报告数据
            M3PlReport m3PlReport = m3PlReportService.listByYearMonth(propertyId, Integer.valueOf(yearMonth));
            if (Objects.isNull(m3PlReport)) {
                // 指定月份报告不存在
                throw new BadRequestException(PlErrorI18nConstant.REPORT_NOT_EXIST,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The report for the specified month not exists");
            }

            //查询M3 Forecast
            M3ForecastName m3ForecastName = m3ForecastsNameService.getById(forecastId);
            if (Objects.isNull(m3ForecastName)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The m3 forecast name not found");
            }

            //查询数据
            ExportPLReportDTO exportDTO = m3PlReportService.generateExportDTO(propertyDTO, m3PlReport, m3ForecastName);
            //模板名称
            String templateFileName = StringUtils.join("tmpl-xls/pl_report_tmpl_", LocaleContextHolder.getLocale(), ".xlsx");
            //导出Excel
            try (InputStream is = new ClassPathResource(templateFileName).getInputStream()) {
                HashMap<String, Object> data = new HashMap<>();
                data.put("propertyName", propertyDTO.getName());
                data.put("exportDTO", exportDTO);

                String fileName = property + "_pl_" + yearMonth + ".xlsx";
                fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
                response.setContentType("application/vnd.ms-excel");
                response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
                response.setHeader("Content-Disposition", String.join("", "attachment; filename=", fileName));

                XlsCommentAreaBuilder xlsCommentAreaBuilder = new XlsCommentAreaBuilder();
                xlsCommentAreaBuilder.addCommandMapping(StyleCommand.NAME, StyleCommand.class);

                JxlsPoiTemplateFillerBuilder.newInstance()
                        .withAreaBuilder(xlsCommentAreaBuilder)
                        .withTemplate(is)
                        .buildAndFill(data, response::getOutputStream);
            } catch (IOException e) {
                throw new InternalServerErrorException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(), BaseErrorCodeEnum.SYSTEM_ERROR.getCode(),
                        "IO exception occured when export pl report excel file. property Id: " + propertyDTO.getId() + ", yearMonth: " + yearMonth, e);
            }
        }

        /**
         * 校验年月格式
         *
         * @param yearMonth 年月
         * @return java.time.YearMonth
         * @author lichen
         * @date 2025/9/29
         */
        private YearMonth checkYearMonth(String yearMonth) {
            //校验年-月格式
            YearMonth yearMonthObj;
            try {
                yearMonthObj = YearMonthUtils.getYearMonth(yearMonth);
            } catch (Exception e) {
                throw new BadRequestException(CommonErrorI18nConstant.FORMAT_INCORRECT,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("yearMonth", BaseConstant.DATE_FORMAT_YYYYMM),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "The yearMonth format error");
            }
            return yearMonthObj;
        }
    }