# RateShopController.java

    package cloud.wconnect.rateshop.controller.rateshop;

    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.exception.BadRequestException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.rateshop.cons.rateshop.*;
    import cloud.wconnect.rateshop.entity.rateshop.RoomType;
    import cloud.wconnect.rateshop.entity.rateshop.Setting;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.RateConfigDTO;
    import cloud.wconnect.rateshop.pojo.query.rateshop.ExportQuery;
    import cloud.wconnect.rateshop.service.rateshop.IRateShopService;
    import cloud.wconnect.rateshop.service.rateshop.IRoomTypeService;
    import cloud.wconnect.rateshop.service.rateshop.ISettingService;
    import com.fasterxml.jackson.databind.ObjectMapper;
    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.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import jakarta.servlet.http.HttpServletResponse;
    import jakarta.validation.Valid;
    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.charset.StandardCharsets;
    import java.text.MessageFormat;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * Rate shop控制层
     *
     * @author lichen
     * @date 2024/11/25
     */
    @RestController
    @RequestMapping("/rate-shop")
    public class RateShopController {

        @Autowired
        private SessionContext sessionContext;
        @Autowired
        private ISettingService settingService;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private IRoomTypeService roomTypeService;
        @Autowired
        private IRateShopService rateShopService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 导出
         *
         * @param property    资产标识
         * @param exportQuery 导出查询的对象
         * @return void
         * @author lichen
         * @date 2024/11/25
         */
        @ApiId(1309)
        @GetMapping("/export")
        public void export(String property, @Valid ExportQuery exportQuery, HttpServletResponse response) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            //校验资产是否设置rate shop功能
            Setting setting = settingService.getByPropertyId(propertyId);
            if (Objects.isNull(setting)) {
                throw new BadRequestException(RateShopErrorI18nConstant.PROPERTY_NOT_CONFIG_RATE_SHOP_SETTING,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The property has no rateShop set, propertyId = " + propertyId);
            }

            //把yyyy-MM-dd的日期转化成时间戳
            long fromTimestamp = DateTimeUtils.dateStrToEpochSecond(exportQuery.getFromDate(), BaseConstant.DATE_FORMAT_YYYY_MM_DD, propertyDTO.getTimezone());
            long toTimestamp = DateTimeUtils.dateStrToEpochSecond(exportQuery.getToDate(), BaseConstant.DATE_FORMAT_YYYY_MM_DD, propertyDTO.getTimezone());
            //开始时间不能大于结束时间
            if (fromTimestamp > toTimestamp) {
                throw new BadRequestException(CommonErrorI18nConstant.START_DATE_CANNOT_GREATER_THAN_END_DATE,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "The toDate must be greater than or equal to the fromDate");
            }
            //判断时间间隔不能大于92天
            long diffDay = DateTimeUtils.dateDiffOfDays(fromTimestamp, toTimestamp, propertyDTO.getTimezone());
            if (Math.abs(diffDay) > 92) {
                throw new BadRequestException(CommonErrorI18nConstant.SELECT_DATE_LIMIT,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate(92),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "The interval between fromDate and toDate must be less than or equal to 92 days");
            }

            //校验房价的查询条件
            checkPriceQueryCondition(propertyDTO, exportQuery, setting);
            //当前用户选择的语言
            Locale locale = LocaleContextHolder.getLocale();
            //设置文件内容
            HashMap<String, Object> data = new HashMap<>();
            rateShopService.packageDataToFile(propertyDTO, setting, exportQuery, data, locale);
            try (InputStream inputStream = new ClassPathResource("/tmpl-xls/rate_shop_tmpl_" + locale + ".xlsx").getInputStream()) {
                String fileName = propertyDTO.getAbbr() + "_" + "rate_shop.xlsx";
                fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
                response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
                response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
                response.setHeader("Content-Disposition", String.join("", "attachment; filename=", fileName));
                JxlsPoiTemplateFillerBuilder.newInstance()
                        .withTemplate(inputStream)
                        .buildAndFill(data, response::getOutputStream);
            } catch (IOException e) {
                throw new BadRequestException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                        BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "Export file failed; exception:" + e);
            }
        }

        /**
         * 校验房价的查询条件
         *
         * @param propertyDTO 资产
         * @param exportQuery 查询条件
         * @param setting     rate shop的配置
         * @return void
         * @author lichen
         * @date 2024/11/25
         */
        private void checkPriceQueryCondition(PropertyDTO propertyDTO, ExportQuery exportQuery, Setting setting) {
            //房价参数配置
            List<RateConfigDTO> rateConfigDTOS = JsonUtils.convertStringToList(objectMapper, setting.getRateConfig(), RateConfigDTO.class);

            Set<Integer> rateTypeSet = rateConfigDTOS.stream().map(RateConfigDTO::getRateType).collect(Collectors.toSet());
            //校验房价类型是否合法
            if (!rateTypeSet.contains(exportQuery.getRateType())) {
                List<String> rateTypeI18nCodeList = Arrays.stream(RateTypeEnum.values()).filter(rateTypeEnum -> rateTypeSet.contains(rateTypeEnum.getRateType()))
                        .map(RateTypeEnum::getI18nCode).toList();
                throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(RateShopFieldI18nConstant.RATE_TYPE, rateTypeI18nCodeList),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        MessageFormat.format("property not configured rateType, propertyId:{0}, rateType:{1}", propertyDTO.getId(), exportQuery.getRateType()));
            }

            //校验数据源是否合法
            if (Objects.nonNull(exportQuery.getDatasourceId())) {
                Map<Integer, List<Integer>> rateType2DatasourceIds = rateConfigDTOS.stream()
                        .collect(Collectors.groupingBy(RateConfigDTO::getRateType, Collectors.mapping(RateConfigDTO::getDatasourceId, Collectors.toList())));
                List<Integer> datasourceIds = rateType2DatasourceIds.get(exportQuery.getRateType());
                if (!datasourceIds.contains(exportQuery.getDatasourceId())) {
                    List<String> datasourceI18nCodeList = Arrays.stream(DatasourceEnum.values()).filter(item -> datasourceIds.contains(item.getDatasourceId()))
                            .map(DatasourceEnum::getI18nCode).toList();
                    throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(RateShopFieldI18nConstant.DATASOURCE, datasourceI18nCodeList),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            MessageFormat.format("DatasourceId Invalid, propertyId:{0}, rateType:{1}, datasourceId:{2}",
                                    propertyDTO.getId(), exportQuery.getRateType(), exportQuery.getDatasourceId()));
                }
            }

            //校验平台是否合法
            if (Objects.nonNull(exportQuery.getPlatform())) {
                Map<String, List<Integer>> rateTypeAndDatasourceId2PlatformIds = rateConfigDTOS.stream()
                        .collect(Collectors.toMap(item -> RateConfigDTO.generateKey(item.getRateType(), item.getDatasourceId()),
                                RateConfigDTO::getPlatformIds));
                List<Integer> platformIdsByConfig = rateTypeAndDatasourceId2PlatformIds.get(RateConfigDTO.generateKey(exportQuery.getRateType(), exportQuery.getDatasourceId()));
                if (!platformIdsByConfig.contains(exportQuery.getPlatform())) {
                    List<String> platformI18nCodeList = Arrays.stream(PlatformEnum.values()).filter(item -> platformIdsByConfig.contains(item.getCode()))
                            .map(PlatformEnum::getI18nCode).toList();
                    throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(RateShopFieldI18nConstant.PLATFORM, platformI18nCodeList),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            MessageFormat.format("platform Invalid, propertyId:{0}, rateType:{1}, datasourceId:{2}, platform:{3}",
                                    propertyDTO.getId(), exportQuery.getRateType(), exportQuery.getDatasourceId(), exportQuery.getPlatform()));
                }
            }
            if (exportQuery.getType() != BaseConstant.ZERO) {
                //校验类型是否存在
                RoomType roomType = roomTypeService.checkExist(exportQuery.getType());
                //校验类型是否归属于指定资产、指定平台、指定销售地区
                roomTypeService.checkBelong(roomType, propertyDTO.getId(), exportQuery.getPlatform(), setting.getPos());
            }
        }
    }