# InitWalkRoomDateinOtherTask.java

    package cloud.wconnect.frontoffice.businesstask;

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.FunctionIdConstant;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.query.OpenFunctionPropertyQuery;
    import cloud.wconnect.commonlib.service.MailInternalService;
    import cloud.wconnect.commonlib.service.PropertyInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.frontoffice.entity.walkroomrequest.WalkRoomApproveLevel;
    import cloud.wconnect.frontoffice.entity.walkroomrequest.WalkRoomDateinOther;
    import cloud.wconnect.frontoffice.entity.walkroomrequest.WalkRoomRequest;
    import cloud.wconnect.frontoffice.service.walkroomrequest.IWalkRoomApproveLevelService;
    import cloud.wconnect.frontoffice.service.walkroomrequest.IWalkRoomDateinOtherService;
    import cloud.wconnect.frontoffice.service.walkroomrequest.IWalkRoomRequestService;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.google.common.collect.Lists;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;

    import java.time.LocalDate;
    import java.time.LocalDateTime;
    import java.time.ZoneId;
    import java.time.format.DateTimeFormatter;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.Set;
    import java.util.concurrent.TimeUnit;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;

    /**
     * 定时初始化walk room功能的入住记录
     *
     * @author lichen
     * @date 2023/7/21
     */
    @Slf4j
    @Component
    public class InitWalkRoomDateinOtherTask {

        @Autowired
        private IWalkRoomRequestService walkRoomRequestService;
        @Autowired
        private MailInternalService mailInternalService;
        @Autowired
        private IWalkRoomDateinOtherService walkRoomDateinOtherService;
        @Autowired
        private IWalkRoomApproveLevelService approveLevelService;
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
        @Autowired
        private PropertyInternalService propertyInternalService;
        private static final String prefixKey = "wconnect:task:walk-room:init-datein:";

        /**
         * 初始化walk room功能的入住记录
         *
         * @return void
         * @author lichen
         * @date 2023/7/21
         */
        @Scheduled(cron = "${schedule.cron.init-walk-room-datein}", zone = "${default.timezone}")
        public void initWalkRoomDateinOther() {
            try {
                String currentDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYMMDDHH));
                if (!preventConcurrency(prefixKey + currentDate)) {
                    return;
                }
                /**
                 * 根据接收方资产时区去初始化walk room request入住记录数据
                 * 原因：发送方资产和接收方资产可能不在一个时区，入住记录的时间戳统一用接收方的时区处理。
                 */
                //查询开通Walk room request功能的资产
                List<PropertyDTO> openModuleProperty = propertyInternalService.getOpenFunctionProperty(OpenFunctionPropertyQuery.builder()
                        .openFunctionIds(Lists.newArrayList(FunctionIdConstant.WALK_ROOM_REQUEST)).build());
                Map<String, List<Integer>> timezone2PropertyIds = openModuleProperty.stream().collect(Collectors.groupingBy(PropertyDTO::getTimezone,
                        Collectors.mapping(PropertyDTO::getId, Collectors.toList())));
                for (Map.Entry<String, List<Integer>> entry : timezone2PropertyIds.entrySet()) {
                    //时区
                    String timezone = entry.getKey();
                    List<Integer> propertyIdsByTimezone = entry.getValue();
                    //指定时区下的当前日期
                    Integer dateNow = Integer.valueOf(DateTimeUtils.localDateToStr(LocalDate.now(ZoneId.of(timezone)), BaseConstant.DATE_FORMAT_YYYYMMDD));
                    //当invalid_date==0时：check in <= ? < out；当invalid_date>0时，check in <= ? <= invalid_date
                    List<WalkRoomRequest> walkRoomRequests = walkRoomRequestService.list(new LambdaQueryWrapper<WalkRoomRequest>()
                            .in(WalkRoomRequest::getToPropertyId, propertyIdsByTimezone)
                            .le(WalkRoomRequest::getCheckInDate, dateNow)
                            .and(child ->
                                    child.nested(qw -> qw.eq(WalkRoomRequest::getInvalidDate, 0).gt(WalkRoomRequest::getCheckOutDate, dateNow))
                                            .or()
                                            .nested(qw -> qw.gt(WalkRoomRequest::getInvalidDate, 0).ge(WalkRoomRequest::getInvalidDate, dateNow))
                            ));
                    if (CollectionUtils.isEmpty(walkRoomRequests)) {
                        continue;
                    }
                    List<Long> reqIds = walkRoomRequests.stream().map(WalkRoomRequest::getId).collect(Collectors.toList());
                    //查询walk room对应当天的入住记录
                    Map<Long, WalkRoomDateinOther> reqId2DateinOther = walkRoomDateinOtherService.listByReqIdsAndDates(reqIds, List.of(dateNow))
                            .stream().collect(Collectors.toMap(WalkRoomDateinOther::getReqId, Function.identity()));

                    //收集资产id
                    Set<Integer> propertyIds = walkRoomRequests.stream().flatMap(item -> Stream.of(item.getFromPropertyId(), item.getToPropertyId())).collect(Collectors.toSet());
                    //查询审批层级集合
                    Map<String, List<WalkRoomApproveLevel>> propertyIdAndType2Levels = approveLevelService.listByPropertyIds(propertyIds)
                            .stream()
                            .collect(Collectors.groupingBy(item -> WalkRoomApproveLevel.generateKey(item.getPropertyId(), item.getType())));

                    //批量新增的入住记录集合
                    List<WalkRoomDateinOther> insertBatch = Lists.newArrayListWithExpectedSize(walkRoomRequests.size());
                    //需要批量更新finalOtherId的walkRoom集合
                    List<WalkRoomRequest> updateBatchFinalOtherIdRequests = Lists.newArrayListWithExpectedSize(walkRoomRequests.size());

                    for (WalkRoomRequest walkRoomRequest : walkRoomRequests) {
                        Long reqId = walkRoomRequest.getId();
                        if (Objects.isNull(reqId2DateinOther.get(reqId))) {
                            //构建入住记录
                            insertBatch.add(walkRoomDateinOtherService.buildDateinOther(walkRoomRequest, dateNow, propertyIdAndType2Levels));
                            //只更新在住的finalOtherId，因为已经离店的walk room request的finalOther在离店时已经更新过
                            if (walkRoomRequest.getInvalidDate() == 0) {
                                updateBatchFinalOtherIdRequests.add(walkRoomRequest);
                            }
                        }
                    }
                    if (CollectionUtils.isEmpty(insertBatch)) {
                        continue;
                    }
                    //处理入住记录
                    walkRoomDateinOtherService.processDateinOther(insertBatch, updateBatchFinalOtherIdRequests);
                }
            } catch (Exception e) {
                String alertTitle = "[Warning] - Exceptions about walk room init data";
                StringBuilder warningEmail = new StringBuilder("<p>There is something wrong when insert init data.</p>");
                warningEmail.append("<p>Exception:").append(e).append("</p>");
                //发送预警邮件
                mailInternalService.sendAlertEmail(alertTitle, warningEmail.toString());
            }
        }

        /**
         * 防止并发操作
         *
         * @param key redis的key
         * @return java.lang.Boolean
         * @author lichen
         * @date 2023/9/29
         */
        private Boolean preventConcurrency(String key) {
            return redisTemplate.opsForValue().setIfAbsent(key, 0, 1, TimeUnit.DAYS);
        }

    }