# HotelKpiSettingController.java

    package cloud.wconnect.workperformance.controller.hotelkpi;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.pojo.dto.jsonview.Views;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.workperformance.cons.hotelkpi.*;
    import cloud.wconnect.workperformance.entity.hotelkpi.HotelKpiMetricsConfig;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.kpi.HotelKpiComparisonRuleDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.kpiv2.HotelKpiV2ComparisonRuleDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.setting.*;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.settingv2.HotelKpiV2SettingDetailDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.settingv2.HotelKpiV2SettingMetricsDTO;
    import cloud.wconnect.workperformance.pojo.query.hotelkpi.HotelKpiSettingRuleQuery;
    import cloud.wconnect.workperformance.service.hotelkpi.*;
    import com.fasterxml.jackson.annotation.JsonView;
    import com.google.common.collect.Sets;
    import jakarta.annotation.Resource;
    import jakarta.validation.Valid;
    import org.springframework.web.bind.annotation.*;

    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;

    /**
     * <p>
     * Hotel KPI Setting 前端控制器
     * </p>
     *
     * @author zc
     * @since 2024-06-28
     */
    @RestController
    @RequestMapping("/hotel-kpi")
    public class HotelKpiSettingController {
        @Resource
        private IHotelKpiMetricsSettingService metricsSettingService;
        @Resource
        private IHotelKpiMetricsConfigService metricsConfigService;
        @Resource
        private IHotelKpiMetricsSettingActualService metricsSettingActualService;
        @Resource
        private IHotelKpiMetricsSettingTargetService metricsSettingTargetService;
        @Resource
        private IHotelKpiWconnectFteService wconnectFteService;
        @Resource
        private SessionContext sessionContext;
        @Resource
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 获取指定年的指标设置
         *
         * @param property 资产标识
         * @param year     年份(yyyy)
         * @return HotelKpiSettingDetailDTO 指标设置详情
         * @author zc
         * @date 2024/6/28
         */
        @ApiId(1215)
        @GetMapping("/{year:\\d+}/setting")
        public List<HotelKpiSettingDetailDTO> listSetting(@RequestParam String property, @PathVariable long year) {
            int propertyId = sessionContext.getPropertyId(property);
            return metricsSettingService.listSetting(propertyId, year);
        }

        /**
         * 获取指标设置 V2
         *
         * @param property 资产标识
         * @param year     年份(yyyy)
         * @return HotelKpiV2SettingDetailDTO 集合
         * @author zc
         * @date 2025/8/21
         */
        @ApiId(1572)
        @GetMapping("/{year:\\d+}/setting/v2")
        public List<HotelKpiV2SettingDetailDTO> listSettingV2(@RequestParam String property, @PathVariable long year) {
            int propertyId = sessionContext.getPropertyId(property);
            return metricsSettingService.listSettingV2(propertyId, year);
        }

        /**
         * 保存指标设置
         *
         * @param property          资产标识
         * @param year              年份(yyyy)
         * @param settingMetricsDTO 指标设置DTO
         * @return emptyMap
         * @author zc
         * @date 2024/6/29
         */
        @ApiId(1227)
        @PostMapping("/{year:\\d+}/setting")
        public Map<Object, Object> saveMetricsBasic(@RequestParam String property, @PathVariable long year,
                                                    @Valid @RequestBody HotelKpiSettingMetricsDTO settingMetricsDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验 metrics config 是否存在
            HotelKpiMetricsConfig metricsConfig = metricsConfigService.checkExistByDateAndMetricsType(propertyId,
                    HotelKpiDateTypeCons.YEARLY, year, settingMetricsDTO.getMetricsType(), Boolean.FALSE, HotelKpiCons.V1);
            // 校验比较规则
            HotelKpiComparisonRuleDTO comparisonSetting = settingMetricsDTO.getComparisonSetting();
            if (comparisonSetting.getOn()) {
                // 若启用，至少选择其中一项
                if (Objects.isNull(comparisonSetting.getActualGreaterLast())
                        && Objects.isNull(comparisonSetting.getActualGreaterTarget())) {
                    throw new BadRequestException(HotelKpiErrorI18nConstant.COMPARE_SETTING_MUST_SELECT_LEAST_ONE,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "At least one comparison setting must be selected.");
                }
            }
            // 校验数据源配置
            if (HotelKpiMetricsEnum.FTE.getMetricsType() == settingMetricsDTO.getMetricsType()) {
                wconnectFteService.checkDataSourceConfig(settingMetricsDTO.getDataSource());
            }
            // 保存指标设置
            metricsSettingService.saveSettingMetrics(metricsConfig, year, settingMetricsDTO, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 保存指标设置 V2
         *
         * @param property          资产标识
         * @param year              年份(yyyy)
         * @param settingMetricsDTO 指标设置DTO
         * @return emptyMap
         * @author zc
         * @date 2025/8/21
         */
        @ApiId(1573)
        @PostMapping("/{year:\\d+}/setting/v2")
        public Map<Object, Object> saveMetricsBasicV2(@RequestParam String property, @PathVariable long year,
                                                      @Valid @RequestBody HotelKpiV2SettingMetricsDTO settingMetricsDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验 metrics config 是否存在
            HotelKpiMetricsConfig metricsConfig = metricsConfigService.checkExistByDateAndMetricsType(propertyId,
                    HotelKpiDateTypeCons.YEARLY, year, settingMetricsDTO.getMetricsType(), Boolean.FALSE, HotelKpiCons.V2);
            // 校验比较规则
            HotelKpiV2ComparisonRuleDTO comparisonSetting = settingMetricsDTO.getComparisonSetting();
            if (comparisonSetting.getOn()) {
                // 若启用，至少选择其中一项
                if (Objects.isNull(comparisonSetting.getActualGreaterLast())
                        && Objects.isNull(comparisonSetting.getBudgetChg())
                        && Objects.isNull(comparisonSetting.getLyChg())) {
                    throw new BadRequestException(HotelKpiErrorI18nConstant.COMPARE_SETTING_MUST_SELECT_LEAST_ONE,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "At least one comparison setting must be selected.");
                }
            }
            // 保存指标设置
            metricsSettingService.saveSettingMetricsV2(metricsConfig, settingMetricsDTO, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 获取 Setting Rule
         *
         * @param property 资产标识
         * @param year     年份(yyyy)
         * @param query    查询条件
         * @return HotelKpiSettingCalculationDTO
         * @author zc
         * @date 2025/1/14
         */
        @ApiId(1324)
        @GetMapping("/{year:\\d+}/setting/rule")
        @JsonView(Views.GeneralView.class)
        public HotelKpiSettingCalculationDTO getSettingRule(@RequestParam String property, @PathVariable long year,
                                                            @Valid HotelKpiSettingRuleQuery query) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验 metrics config 是否存在
            HotelKpiMetricsConfig metricsConfig = metricsConfigService.checkExistByDateAndMetricsType(propertyId,
                    HotelKpiDateTypeCons.YEARLY, year, query.getMetricsType(), Boolean.FALSE, HotelKpiCons.V1);
            return metricsSettingService.getSettingRule(metricsConfig, year, query.getValueCategory());
        }

        /**
         * 保存 Setting Rule
         *
         * @param property   资产标识
         * @param year       年份(yyyy)
         * @param settingDTO 指标设置DTO
         * @return emptyMap
         * @author zc
         * @date 2025/1/14
         */
        @ApiId(1216)
        @PostMapping("/{year:\\d+}/setting/rule")
        public Map<Object, Object> saveSettingRule(@RequestParam String property, @PathVariable long year,
                                                   @Valid @RequestBody HotelKpiSettingCalculationDTO settingDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验 metrics config 是否存在
            HotelKpiMetricsConfig metricsConfig = metricsConfigService.checkExistByDateAndMetricsType(propertyId,
                    HotelKpiDateTypeCons.YEARLY, year, settingDTO.getMetricsType(), Boolean.FALSE, HotelKpiCons.V1);
            // 校验指标计算因子设置入参是否正确
            checkFactorSettingValueFormat(settingDTO);
            // 保存指标设置数据
            metricsSettingService.saveSetting(metricsConfig, year, settingDTO, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 校验指标计算因子设置入参
         *
         * @param settingDTO 指标计算因子设置DTO
         * @author zc
         * @date 2025/1/14
         */
        private void checkFactorSettingValueFormat(HotelKpiSettingCalculationDTO settingDTO) {
            List<HotelKpiSettingCalculationFactorDTO> calculationFactor = settingDTO.getCalculationFactor();
            // 校验计算因子设置
            if (calculationFactor.size() == 2 && Objects.isNull(settingDTO.getPercent())) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("percent"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The percent cannot be null.");
            }
            // 校验指标值设置格式
            if (Objects.equals(settingDTO.getValueCategory(), HotelKpiValueCategoryCons.ACTUAL)) {
                // Actual 不包含 Constant, Accumulation, Independent 分类
                boolean anyMatch = calculationFactor.stream().anyMatch(item ->
                        List.of(HotelKpiSettingCategoryEnum.CONSTANT.getCategory(),
                                        HotelKpiSettingCategoryEnum.ACCUMULATION.getCategory(),
                                        HotelKpiSettingCategoryEnum.INDEPENDENT.getCategory())
                                .contains(item.getValueSetting().getCategory()));
                if (anyMatch) {
                    throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(HotelKpiI18nConstant.CALCULATE_CATEGORY,
                                    List.of(HotelKpiSettingCategoryEnum.DYNAMIC.getI18nCode(),
                                            HotelKpiSettingCategoryEnum.DIMINISHING_SURPLUS.getI18nCode())),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The Actual setting category must be in [3:dynamic; 5:diminishing surplus].");
                }
                metricsSettingActualService.checkSettingValueFormat(settingDTO);
            } else {
                // Target 不包含 Diminishing Surplus 分类
                boolean anyMatch = calculationFactor.stream().anyMatch(item ->
                        item.getValueSetting().getCategory() == HotelKpiSettingCategoryEnum.DIMINISHING_SURPLUS.getCategory());
                if (anyMatch) {
                    throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(HotelKpiI18nConstant.CALCULATE_CATEGORY,
                                    List.of(HotelKpiSettingCategoryEnum.CONSTANT.getI18nCode(),
                                            HotelKpiSettingCategoryEnum.ACCUMULATION.getI18nCode(),
                                            HotelKpiSettingCategoryEnum.DYNAMIC.getI18nCode(),
                                            HotelKpiSettingCategoryEnum.INDEPENDENT.getI18nCode())),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The Target setting category must be in [1:constant; 2:accumulation; 3:dynamic; 4:independent].");
                }
                metricsSettingTargetService.checkSettingValueFormat(settingDTO);
            }
        }

        /**
         * 克隆目标年份的指标配置 To 指定年份、指定资产
         *
         * @param property  资产标识
         * @param year      年份(yyyy)
         * @param copyToDTO Copy To DTO
         * @return emptyMap
         * @author zc
         * @date 2024/6/29
         */
        @ApiId(1217)
        @PostMapping("/{year:\\d+}/setting/copy-to")
        public Map<Object, Object> copyTo(@RequestParam String property, @PathVariable long year,
                                          @Valid @RequestBody HotelKpiSettingCopyToDTO copyToDTO) {
            if (!Sets.newHashSet(sessionContext.getPropertyIds()).containsAll(copyToDTO.getPropertyIds())) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        "Cannot be operated others property data; paramPropertyIds: " + copyToDTO.getPropertyIds());
            }
            int propertyId = sessionContext.getPropertyId(property);
            metricsSettingService.copyTo(propertyId, year, copyToDTO, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }
    }