# SalesKpiMonthlyController.java

    package cloud.wconnect.workperformance.controller.saleskpi.monthly;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.CommonFieldI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.YearMonthUtils;
    import cloud.wconnect.workperformance.cons.saleskpi.SalesKpiErrorI18nConstant;
    import cloud.wconnect.workperformance.entity.saleskpi.SalesKpiConfig;
    import cloud.wconnect.workperformance.entity.saleskpi.SalesKpiMonthly;
    import cloud.wconnect.workperformance.entity.saleskpi.SalesKpiMonthlyConfig;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.monthly.SalesKpiMonthlyDTO;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.monthly.SalesKpiMonthlyUpdateItemDTO;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.monthly.basic.MonthlyConfigItemDTO;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.monthly.basic.MonthlyConfigItemDetailDTO;
    import cloud.wconnect.workperformance.service.saleskpi.ISalesKpiConfigService;
    import cloud.wconnect.workperformance.service.saleskpi.ISalesKpiMonthlyConfigService;
    import cloud.wconnect.workperformance.service.saleskpi.ISalesKpiMonthlyService;
    import cloud.wconnect.workperformance.service.saleskpi.accounting.ISalesKpiOperatingStatementParseService;
    import com.google.common.collect.Lists;
    import jakarta.annotation.Resource;
    import jakarta.validation.Valid;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.context.ApplicationContext;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.multipart.MultipartFile;

    import java.time.YearMonth;
    import java.util.*;
    import java.util.stream.Stream;

    /**
     * <p>
     * sales kpi monthly 前端控制器
     * </p>
     *
     * @author zc
     * @since 2023-09-05
     */
    @Validated
    @RestController
    @RequestMapping("/sales-kpi/monthly")
    public class SalesKpiMonthlyController {
        @Resource
        private ISalesKpiMonthlyService salesKpiMonthlyService;
        @Resource
        private ISalesKpiConfigService salesKpiConfigService;
        @Resource
        private ISalesKpiMonthlyConfigService salesKpiMonthlyConfigService;
        @Resource
        private PropertyI18nRedisService propertyI18nRedisService;
        @Resource
        private SessionContext sessionContext;
        @Resource
        private ApplicationContext applicationContext;
        @Resource
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 获取基本指标
         *
         * @param property 资产标识
         * @param salesId  sales config id
         * @return MonthlyConfigItemDTO 集合
         * @author zc
         * @date 2023/9/7
         */
        @ApiId(647)
        @GetMapping("/item")
        public List<MonthlyConfigItemDTO> listMonthlyItems(String property, Integer salesId) {
            int propertyId = sessionContext.getPropertyId(property);

            List<SalesKpiMonthlyConfig> monthlyConfigList = Lists.newArrayList();
            if (Objects.nonNull(salesId)) {
                // 校验配置数据是否存在
                SalesKpiConfig originConfig = salesKpiConfigService.checkExist(salesId);

                // 校验配置数据归属
                salesKpiConfigService.checkBelong(propertyId, originConfig);

                // 获取 segment 指标
                if (StringUtils.isNotBlank(originConfig.getMarketSegments())) {
                    monthlyConfigList.addAll(salesKpiMonthlyConfigService.listByIds(Stream.of(StringUtils
                            .split(originConfig.getMarketSegments(), ",")).toList()));
                }
            }

            // 获取未删除的基本指标
            monthlyConfigList.addAll(salesKpiMonthlyConfigService
                    .getMonthlyConfigs(propertyId, SalesKpiMonthlyConfig.TYPE_GENERAL, null).stream()
                    // 过滤出子节点数据
                    .filter(item -> item.getParentId() != BaseConstant.ZERO).toList());

            return monthlyConfigList.stream().map(item ->
                    MonthlyConfigItemDTO.builder()
                            .itemId(item.getId())
                            .title(item.getTitle())
                            .type(item.getType())
                            .build()).toList();
        }

        /**
         * 获取 monthly 列表数据
         *
         * @param property 资产标识
         * @param monthly  月份（yyyyMM）
         * @return SalesKpiMonthlyDTO 集合
         * @author zc
         * @date 2023/9/7
         */
        @ApiId(640)
        @GetMapping("/{monthly:\\d+}")
        public List<SalesKpiMonthlyDTO> list(String property, @PathVariable long monthly) {
            // 校验日期有效性
            checkDateFormat(monthly);
            int tenantId = sessionContext.getTenantSymbolBO().getId();
            int propertyId = sessionContext.getPropertyId(property);
            return salesKpiMonthlyService.list(tenantId, propertyId, monthly);
        }

        /**
         * 保存 Monthly 数据
         *
         * @param id             数据 id
         * @param property       资产标识
         * @param itemDetailList Monthly 数据
         * @return empty map
         * @author zc
         * @date 2023/9/8
         */
        @ApiId(641)
        @PutMapping("/{id:\\d+}")
        public Map<Object, Object> save(@PathVariable int id, String property,
                                        @RequestBody List<@Valid MonthlyConfigItemDetailDTO> itemDetailList) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验数据是否存在
            SalesKpiMonthly originSalesKpiMonthly = salesKpiMonthlyService.checkExist(id);
            // 校验归属
            salesKpiMonthlyService.checkBelong(propertyId, originSalesKpiMonthly);
            // 保存数据
            salesKpiMonthlyService.save(originSalesKpiMonthly, itemDetailList, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 修改指标名称
         *
         * @param monthly       月份（yyyyMM）
         * @param itemId        指标 id
         * @param property      资产标识
         * @param updateItemDTO 内容
         * @return empty map
         * @author zc
         * @date 2023/9/8
         */
        @ApiId(642)
        @PutMapping("/{monthly:\\d+}/item/{itemId:\\d+}/title")
        public Map<Object, Object> saveItemTitle(@PathVariable long monthly, @PathVariable int itemId, String property,
                                                 @RequestBody @Valid SalesKpiMonthlyUpdateItemDTO updateItemDTO) {
            // 校验月份入参
            checkDateFormat(monthly);
            int propertyId = sessionContext.getPropertyId(property);
            // 校验指标数据是否存在
            SalesKpiMonthlyConfig originSalesKpiMonthlyConfig = salesKpiMonthlyConfigService.checkExist(itemId);
            // 校验指标数据归属
            salesKpiMonthlyConfigService.checkBelong(propertyId, originSalesKpiMonthlyConfig);
            // 保存 item
            salesKpiMonthlyService.saveItemTitle(propertyId, monthly, itemId, updateItemDTO);
            return Collections.emptyMap();
        }

        /**
         * 导入报告
         *
         * @param monthly      月份（yyyyMM）
         * @param property     资产标识
         * @param systemSource 报告来源
         * @param file         报告
         * @return empty map
         * @author zc
         * @date 2023/9/9
         */
        @ApiId(643)
        @PostMapping("/{monthly:\\d+}/report")
        public Map<Object, Object> importReport(@PathVariable long monthly, String property,
                                                String systemSource, @RequestParam MultipartFile file) {
            // 校验月份入参
            checkDateFormat(monthly);
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            ISalesKpiOperatingStatementParseService parser = getOperatingStatementParser(systemSource);

            // 校验文件是否满足需求
            parser.checkFile(propertyDTO, file);

            // 处理数据
            parser.handleData(propertyDTO, file, monthly);

            return Collections.emptyMap();
        }

        /**
         * 校验日期有效性
         *
         * @param monthly 月份（yyyyMM）
         * @author zc
         * @date 2023/9/7
         */
        private void checkDateFormat(long monthly) {
            YearMonth targetYearMonth;
            try {
                targetYearMonth = YearMonthUtils.getYearMonth(monthly + "");
            } catch (Exception e) {
                LinkedHashMap<String, Boolean> map = new LinkedHashMap<>();
                map.put(CommonFieldI18nConstant.MONTH, true);
                map.put(BaseConstant.DATE_FORMAT_YYYYMM,false);
                throw new BadRequestException(CommonErrorI18nConstant.FORMAT_INCORRECT,
                        i18nMessageUtils.getErrorParamMap(map),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The monthly param is wrong format.");
            }

            // 只能操作历史月数据
            YearMonth now = YearMonth.now();
            if (!targetYearMonth.isBefore(now)) {
                throw new BadRequestException(SalesKpiErrorI18nConstant.CANNOT_OPERATE_FEATURE,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Cannot operate feature data.");
            }
        }

        /**
         * 获取报告解析服务
         *
         * @param source 来源标识
         * @return 报告解析服务
         * @author zc
         * @date 2024/8/21
         */
        private ISalesKpiOperatingStatementParseService getOperatingStatementParser(String source) {
            Collection<ISalesKpiOperatingStatementParseService> parserList =
                    applicationContext.getBeansOfType(ISalesKpiOperatingStatementParseService.class).values();
            ISalesKpiOperatingStatementParseService parser = null;
            for (ISalesKpiOperatingStatementParseService parseService : parserList) {
                if (parseService.supports(source)) {
                    parser = parseService;
                    break;
                }
            }
            if (Objects.isNull(parser)) {
                throw new BadRequestException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(),
                        "Can not find Sales KPI Operating Statement report parser");
            }
            return parser;
        }
    }