# SaleForecastController.java

    package cloud.wconnect.workperformance.controller.sva.saleforecast;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.BeanMapper;
    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.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.workperformance.cons.sva.SvaSettingDriverTypeEnum;
    import cloud.wconnect.workperformance.entity.sva.SvaProductivityRatioSettings;
    import cloud.wconnect.workperformance.entity.sva.saleforecast.SaleForecast;
    import cloud.wconnect.workperformance.pojo.dto.sva.saleforecast.SaleForecastDTO;
    import cloud.wconnect.workperformance.pojo.dto.sva.saleforecast.SaleForecastExceptionDTO;
    import cloud.wconnect.workperformance.pojo.dto.sva.saleforecast.SaleForecastInfoDTO;
    import cloud.wconnect.workperformance.pojo.dto.sva.saleforecast.SaleForecastWrapperDTO;
    import cloud.wconnect.workperformance.service.sva.saleforecast.ISaleForecastService;
    import cloud.wconnect.workperformance.service.sva.setting.ISvaProductivityRatioSettingsService;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Sets;
    import jakarta.annotation.Resource;
    import jakarta.validation.Valid;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.web.bind.annotation.*;

    import java.util.*;

    /**
     * <p>
     * 控制器类：Sale Forecast信息
     * </p>
     *
     * @author zc
     * @since 2023-03-27
     */
    @RestController
    @RequestMapping("/sva/sale-forecast")
    public class SaleForecastController {
        @Resource
        private ISaleForecastService saleForecastService;
        @Resource
        private BeanMapper beanMapper;
        @Resource
        private UserInternalService userInternalService;
        @Resource
        private PropertyI18nRedisService propertyI18nRedisService;
        @Resource
        private SessionContext sessionContext;
        @Resource
        private I18nMessageUtils i18nMessageUtils;
        @Resource
        private ISvaProductivityRatioSettingsService svaProductivityRatioSettingsService;

        /**
         * 获取指定月份内的 Sale Forecast 信息集合
         *
         * @param property   资产标识
         * @param targetDate 目标日期（月初时间戳）
         * @return Sale Forecast 信息集合
         * @author zc
         * @date 2023/3/27
         */
        @ApiId(185)
        @GetMapping
        public SaleForecastWrapperDTO list(@RequestParam String property, @RequestParam long targetDate) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            SaleForecastWrapperDTO result = new SaleForecastWrapperDTO();
            List<SaleForecast> saleForecastList = saleForecastService.list(propertyDTO, targetDate);
            if (CollectionUtils.isEmpty(saleForecastList)) {
                return result;
            }
            result.setList(beanMapper.mapList(saleForecastList, SaleForecastDTO.class));

            // 最近更新人,最近更新时间
            SaleForecast saleForecast = saleForecastList.stream().max(Comparator.comparingLong(SaleForecast::getLastUpdatedTime))
                    .orElse(new SaleForecast());
            if (saleForecast.getLastUpdatedBy() > 0) {
                result.setUpdatedByName(userInternalService.getOne(propertyDTO.getTenantId(), saleForecast.getLastUpdatedBy()).getName());
            }
            result.setUpdatedTime(saleForecast.getLastUpdatedTime());
            return result;
        }

        /**
         * 保存 Sale Forecast
         *
         * @param property    资产标识
         * @param saveInfoDTO SaleForecast 内容
         * @return empty map
         * @author zc
         * @date 2025/4/10
         */
        @ApiId(187)
        @PostMapping
        public Map<Object, Object> save(@RequestParam String property, @RequestBody @Valid SaleForecastInfoDTO saveInfoDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            // 查询配置
            int year = DateTimeUtils.longToLocalDate(saveInfoDTO.getTheDate(), propertyDTO.getTimezone()).getYear();
            List<SvaProductivityRatioSettings> svaProductivityRatioSettings = svaProductivityRatioSettingsService.listByYears(propertyId, Sets.newHashSet(year));
            List<String> driverTypes = Collections.emptyList();
            if(CollectionUtils.isNotEmpty(svaProductivityRatioSettings)){
                driverTypes = svaProductivityRatioSettings.stream()
                        .map(SvaProductivityRatioSettings::getDriverType).distinct().toList();
            }

            // 校验保存的数据是否都为空
            List<Object> checkList = Lists.newArrayList(saveInfoDTO.getEstiFbRev(), saveInfoDTO.getEstiRoomSales(),
                    saveInfoDTO.getEstiAdr(), saveInfoDTO.getRoomsAvailSale(), saveInfoDTO.getOccPercent(),
                    saveInfoDTO.getArrivals(), saveInfoDTO.getDirty(), saveInfoDTO.getDepartures());
            if(driverTypes.contains(SvaSettingDriverTypeEnum.RESTAURANT_COVERS.getValue())){
                checkList.add(saveInfoDTO.getRestaurantCovers());
            }
            if(driverTypes.contains(SvaSettingDriverTypeEnum.BANQUET_COVERS.getValue())){
                checkList.add(saveInfoDTO.getBanquetCovers());
            }
            if(driverTypes.contains(SvaSettingDriverTypeEnum.AOI_COVERS.getValue())){
                checkList.add(saveInfoDTO.getAoiCovers());
            }
            if(driverTypes.contains(SvaSettingDriverTypeEnum.BANQUET_FB_COVERS.getValue())){
                checkList.add(saveInfoDTO.getBanquetFbCovers());
            }
            if(driverTypes.contains(SvaSettingDriverTypeEnum.BANQUET_SETUP_COVERS.getValue())){
                checkList.add(saveInfoDTO.getBanquetSetupCovers());
            }

            if (checkList.stream().noneMatch(Objects::nonNull)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("savedData"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The saved data cannot all be null");
            }

            saleForecastService.save(propertyDTO, saveInfoDTO, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 获取 Sale Forecast 未按时填写数据的异常集合
         *
         * @param property 资产标识
         * @return Sale Forecast 集合
         * @author zc
         * @date 2023/3/28
         */
        @ApiId(188)
        @GetMapping("/exceptions")
        public List<SaleForecastExceptionDTO> listExceptions(@RequestParam String property) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            return saleForecastService.listExceptions(propertyDTO);
        }

        /**
         * 忽略 Sale Forecast 未按时填写数据的异常
         *
         * @param property   资产标识
         * @param targetDate 目标日期（月初时间戳）
         * @return empty map
         * @author zc
         * @date 2023/3/28
         */
        @ApiId(189)
        @PutMapping("/exceptions/ignore")
        public Map<Object, Object> ignore(@RequestParam String property, @RequestParam long targetDate) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            saleForecastService.ignore(propertyDTO, targetDate, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

    }