# AdRequestServiceImpl.java

    package cloud.wconnect.frontoffice.service.adjustment.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.SystemPlatformEnum;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.pojo.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.UserDTO;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationDateTimeDTO;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationNumberDTO;
    import cloud.wconnect.commonlib.pojo.dto.scheduler.JobParameterDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.frontoffice.cons.ApproveCons;
    import cloud.wconnect.frontoffice.cons.I18nConstant;
    import cloud.wconnect.frontoffice.cons.S3BucketFunctionCons;
    import cloud.wconnect.frontoffice.cons.adjustment.*;
    import cloud.wconnect.frontoffice.entity.adjustment.*;
    import cloud.wconnect.frontoffice.mapper.adjustment.AdRequestMapper;
    import cloud.wconnect.frontoffice.mapper.adjustment.AdjApproveLogMapper;
    import cloud.wconnect.frontoffice.pojo.dto.adjustment.*;
    import cloud.wconnect.frontoffice.pojo.query.adjustment.AdRequestQuery;
    import cloud.wconnect.frontoffice.pojo.query.adjustment.MatchedAdjustmentQuery;
    import cloud.wconnect.frontoffice.pojo.query.adjustment.NeedAssignQuery;
    import cloud.wconnect.frontoffice.pojo.query.adjustment.StatisticsTypeTrendQuery;
    import cloud.wconnect.frontoffice.pojo.vo.adjustment.*;
    import cloud.wconnect.frontoffice.pojo.vo.adjustment.statistic.*;
    import cloud.wconnect.frontoffice.service.adjustment.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.ImmutableMap;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Maps;
    import com.google.common.collect.Sets;
    import jakarta.annotation.Resource;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.poi.ss.usermodel.IndexedColors;
    import org.redisson.api.RLock;
    import org.redisson.api.RedissonClient;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.math.BigDecimal;
    import java.text.MessageFormat;
    import java.time.LocalDate;
    import java.time.ZoneId;
    import java.time.ZonedDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.*;
    import java.util.concurrent.Executor;
    import java.util.function.Function;
    import java.util.stream.Stream;

    import static java.util.stream.Collectors.*;

    /**
     * <p>
     * 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2023-03-30
     */
    @Service
    @DS("logbook")
    public class AdRequestServiceImpl extends ServiceImpl<AdRequestMapper, AdRequest> implements IAdRequestService {

        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private AdRequestMapper adRequestMapper;
        @Autowired
        @Lazy
        private IAdjSenderService adjSenderService;
        @Autowired
        private IAdjApproveLogService approveLogService;
        @Autowired
        private IAdjApproveConfigService approveConfigService;
        @Autowired
        private IAdjApproveLevelService approveLevelService;
        @Autowired
        @Lazy
        private IAdjSendNotifyService sendEmailService;
        @Autowired
        private UserInternalService userInternalService;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private Executor executor;
        @Value("${aws-s3.region}")
        private String region;
        @Value("#{${aws-s3.function-bucket}}")
        private Map<String, Map<String, Object>> bucketMap;
        @Autowired
        private I18nMessageUtils i18NMessageUtils;
        @Autowired
        private IAdjOperateLogService adjOperateLogService;
        @Autowired
        private AdjApproveLogMapper adjApproveLogMapper;
        @Autowired
        private IAdRequestCommentService adRequestCommentService;
        @Autowired
        private IAdjPmsAccountService adjPmsAccountService;
        @Autowired
        private IAdjPmsConfigService adjPmsConfigService;
        @Autowired
        private IAdRequestMirrorService adRequestMirrorService;
        @Autowired
        private IAdjOutOfOfficeConfigService adjOutOfOfficeConfigService;
        @Autowired
        private IAdjCheckUserEffectiveService adjCheckUserEffectiveService;
        @Resource
        private RedissonClient redissonClient;

        /**
         * 保存
         *
         * @param adRequestDTO adjustment数据
         * @param loginUserId  登录用户id
         * @return java.lang.Long
         * @author lichen
         * @date 2024/9/27
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public Long save(AdRequestDTO adRequestDTO, int loginUserId) {
            RLock rLock = redissonClient.getLock(AdjConstant.GET_MAX_ADJ_NUMBER_LOCK_PREFIX + adRequestDTO.getPropertyId());
            if (RedissonLockUtil.getLock(rLock, 3, 2)) {
                try {
                    // 执行删除逻辑
                    return doSave(adRequestDTO, loginUserId);
                } finally {
                    if (rLock.isHeldByCurrentThread()) {
                        // 释放锁
                        rLock.unlock();
                    }
                }
            } else {
                throw new RuntimeException("save error, The number of execution failures reached the upper limit");
            }
        }

        /**
         * 保存
         *
         * @param adRequestDTO adjustment数据
         * @param loginUserId  登录用户id
         * @return java.lang.Long
         * @author lichen
         * @date 2026/2/24
         */
        private Long doSave(AdRequestDTO adRequestDTO, int loginUserId) {
            //查询资产信息
            PropertyDTO propertyDTO = propertyI18nRedisService.get(adRequestDTO.getPropertyId());
            //adRequestDTO -> AdRequest
            AdRequest adRequest = beanMapper.map(adRequestDTO, AdRequest.class);
            //设置属性
            adRequest.setCreateBy(loginUserId);
            adRequest.setCreateTime(DateTimeUtils.nowTimestamp());
            adRequest.setAutoCreated(false);
            adRequest.setStatus(AdjustmentStatusEnum.UNDETERMINED.getStatus());
            adRequest.setBatchId(BaseConstant.LONG_ZERO);
            adRequest.setApproveIndex(BaseConstant.ZERO);
            adRequest.setApproveLevelId(BaseConstant.LONG_ZERO);
            adRequest.setFlowStatus(FlowStatusEnum.DRAFT.getFlowStatus());
            adRequest.setIsArchive(false);
            if (StringUtils.isBlank(adRequest.getAttachments())) {
                adRequest.setAttachments("[]");
            }
            if (StringUtils.isBlank(adRequest.getTrxDesc())) {
                adRequest.setTrxDesc("");
            }
            //pmsAccount设置为空字符串
            adRequest.setPmsAccount("");
            //设置adjnumber
            setAdjNumBatch(Lists.newArrayList(adRequest), propertyDTO);
            //设置为未配对
            adRequest.setBusinessDate(0);
            adRequest.setMatchTime(0L);
            //保存adjustment
            save(adRequest);
            //发送adj变更的消息
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                adjSenderService.publishSave(propertyDTO.getId(), adRequest.getId());
            });
            return adRequest.getId();
        }

        /**
         * adjustment集合设置adj编号
         *
         * @param adjList     adjustment数据集合
         * @param propertyDTO 资产
         * @author lichen
         * @date 2024/4/15
         */
        @Override
        public void setAdjNumBatch(List<AdRequest> adjList, PropertyDTO propertyDTO) {
            //组装adjNumber前缀
            ZonedDateTime zonedDateTime = DateTimeUtils.dateTimeNow(propertyDTO.getTimezone());
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYMMDD);
            String dateStr = zonedDateTime.format(dateTimeFormatter);
            String prefix = StringUtils.upperCase(propertyDTO.getAbbr()) + dateStr;
            // 查询当前前缀下最多的adj编号
            // ___表示完成匹配 3 个任意字符（数字或字母）
            String maxAdjNumber = adRequestMapper.selectMaxAdjNumForUpdate(propertyDTO.getId(), prefix + "___");
            //最大填充长度为3
            int maxLength = 3;

            if (maxAdjNumber == null) {
                int index = 1;
                for (AdRequest adj : adjList) {
                    adj.setAdjNumber(prefix + StringUtils.leftPad(String.valueOf(index), maxLength, '0'));
                    index++;
                }
                return;
            }

            String indexStr = StringUtils.removeStart(maxAdjNumber, prefix);
            int index = Integer.parseInt(indexStr) + 1;
            for (AdRequest adj : adjList) {
                adj.setAdjNumber(prefix + StringUtils.leftPad(String.valueOf(index), maxLength, '0'));
                index++;
            }
        }

        /**
         * 修改
         *
         * @param oriAdRequest 修改前的调账数据
         * @param adRequestDTO 修改后的DTO
         * @param loginUserId  登录用户id
         * @author lichen
         * @date 2025/8/18
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void update(AdRequest oriAdRequest, AdRequestDTO adRequestDTO, int loginUserId) {
            AdRequest updateEntity = beanMapper.map(adRequestDTO, AdRequest.class);
            //设置修改人和修改时间
            updateEntity.setUpdateBy(loginUserId);
            updateEntity.setUpdateTime(DateTimeUtils.nowTimestamp());
            //修改数据
            updateById(updateEntity);

            //发送adj变更的消息
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                //发送adj变更消息
                adjSenderService.publishSave(oriAdRequest.getPropertyId(), oriAdRequest.getId());

                //修改前的s3Key集合
                List<String> updateBeforeS3Keys = AttachmentUtil.getKeysByAbbrAttachmentArrayJson(oriAdRequest.getAttachments(), objectMapper);
                //修改后的s3Key集合
                List<String> updateAfterS3Keys = AttachmentUtil.getKeysByAbbrAttachmentArrayJson(adRequestDTO.getAttachments(), objectMapper);
                //删除的附件 = 修改前的附件集合 - 修改后的附件集合
                List<String> delS3Keys = Lists.newArrayList(updateBeforeS3Keys);
                delS3Keys.removeAll(updateAfterS3Keys);
                S3Utils.batchDeleteFile(region, bucketMap.get(S3BucketFunctionCons.ADJUSTMENT), delS3Keys);
            });
        }

        /**
         * 撤回调账数据
         *
         * @param propertyId  资产id
         * @param adRequest   调账数据
         * @param opinion     意见
         * @param loginUserId 登录用户id
         * @return void
         * @author lichen
         * @date 2025/8/18
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void withdrawn(int propertyId, AdRequest adRequest, String opinion, int loginUserId) {
            //更新【状态=待定 流程状态=草稿】
            adRequest.setFlowStatus(FlowStatusEnum.DRAFT.getFlowStatus());
            adRequest.setStatus(AdjustmentStatusEnum.UNDETERMINED.getStatus());
            adRequest.setApproveIndex(BaseConstant.ZERO);
            adRequest.setApproveLevelId(BaseConstant.LONG_ZERO);
            adRequest.setBatchId(BaseConstant.LONG_ZERO);
            adRequest.setIsArchive(false);
            updateById(adRequest);

            //记录操作日志
            adjOperateLogService.save(AdjOperateLog.builder()
                    .propertyId(propertyId)
                    .adjId(adRequest.getId())
                    .userId(loginUserId)
                    .operateType(OperateTypeEnum.WITHDRAWN_BY_CREATE.getOperateType())
                    .operateTime(DateTimeUtils.nowTimestamp())
                    .opinion(opinion)
                    .build());
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                //发送adj变更数据
                adjSenderService.publishSave(propertyId, adRequest.getId());
            });
        }

        /**
         * 送审数据
         *
         * @param adRequest   调账数据
         * @param propertyId  资产id
         * @param loginUserId 登录用户id
         * @return void
         * @author lichen
         * @date 2025/8/18
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void toApprove(AdRequest adRequest, int propertyId, int loginUserId) {
            //记录送审的操作日志
            adjOperateLogService.save(AdjOperateLog.builder()
                    .propertyId(adRequest.getPropertyId())
                    .adjId(adRequest.getId())
                    .userId(loginUserId)
                    .operateType(OperateTypeEnum.TO_APPROVAL.getOperateType())
                    .operateTime(DateTimeUtils.nowTimestamp())
                    .opinion("")
                    .build());

            //生成批次id规则：数据id + 当前时间戳
            adRequest.setBatchId(Long.parseLong(adRequest.getId().toString() + DateTimeUtils.nowTimestamp()));

            //查询当前操作人员是否审批人员或补位人员
            Integer requestType = adRequest.getRequestType();
            boolean loginUserIsSubstitutePersonnel = false;
            List<AdjOutOfOfficeConfig> substitutePersonnelsForCurrentApproveLevel = Collections.emptyList();
            AdjApproveConfig approveConfigByUserId = approveConfigService.getByUserId(adRequest.getPropertyId(), requestType, loginUserId);
            if (Objects.isNull(approveConfigByUserId)) {
                // 查找登陆用户是否是补位审批人
                // 先找补位人员对应的有效的补位配置，再根据配置找对应的审批层级下有效的审批人，若找到了多个审批层级，则返回最小层级，若没有，则当前补位人员失效
                List<AdjOutOfOfficeConfig> outOfOfficeConfigs = adjOutOfOfficeConfigService.listEffectiveConfigForSupplementaryPersonnel(propertyId, requestType, loginUserId);
                if (CollectionUtils.isNotEmpty(outOfOfficeConfigs)) {
                    // 检查用户有效（1.状态开启，2.有审批权限）
                    Map<Integer, AdjOutOfOfficeConfig> substitutePersonnelByApproveUserId = outOfOfficeConfigs.stream().
                            collect(toMap(AdjOutOfOfficeConfig::getUpdatedBy, v -> v));
                    List<Integer> approverIds = substitutePersonnelByApproveUserId.keySet().stream().toList();
                    approverIds = adjCheckUserEffectiveService.checkUserEffective(propertyId, approverIds);
                    if (CollectionUtils.isNotEmpty(approverIds)) {
                        List<AdjApproveConfig> adjApproveConfigs = approveConfigService.getMinLevelConfigByUserIds(propertyId, requestType, approverIds);
                        if (CollectionUtils.isNotEmpty(adjApproveConfigs)) {
                            loginUserIsSubstitutePersonnel = true;
                            approveConfigByUserId = adjApproveConfigs.get(0);
                            List<Integer> approverUserIds = adjApproveConfigs.stream().map(AdjApproveConfig::getUserId).toList();
                            substitutePersonnelsForCurrentApproveLevel = outOfOfficeConfigs.stream()
                                    .filter(item -> approverUserIds.contains(item.getUpdatedBy()))
                                    .collect(toList());
                        }
                    }
                }
            }

            if (Objects.isNull(approveConfigByUserId)) {
                /**
                 * 当前操作人员不是审批人员：查询当前资产、当前 adj 类型配置的第一个合适的审批层级，进行送审处理
                 * 送审处理逻辑：
                 *      1. 当前资产没有设置审批层级，数据更新为【状态=有效】【流程状态=审批完成】
                 *      2. 当前资产设置了审批层级，数据更新为【状态=待定】【流程状态=审批中】并发邮件通知审批人
                 */
                AdjApproveLevel firstApproveLevel = approveLevelService.getAppropriateApprovalLevel(adRequest);
                if (Objects.isNull(firstApproveLevel)) {
                    //更新数据为【状态=有效】【流程状态=审批完成】
                    adRequest.setStatus(AdjustmentStatusEnum.VALID.getStatus());
                    adRequest.setFlowStatus(FlowStatusEnum.APPROVAL_COMPLETED.getFlowStatus());
                    updateById(adRequest);
                    //发送邮件给数据创建人
                    TransactionUtils.afterCommitAsyncExecute(executor, () -> sendEmailService.notifyDutyUser(adRequest, AdjApprovalStatusEnum.APPROVED.getStatus()));
                } else {
                    //更新为【状态=待定】【流程状态=审批中】
                    adRequest.setStatus(AdjustmentStatusEnum.UNDETERMINED.getStatus());
                    adRequest.setFlowStatus(FlowStatusEnum.APPROVAING.getFlowStatus());
                    adRequest.setApproveIndex(firstApproveLevel.getApproveIndex());
                    adRequest.setApproveLevelId(firstApproveLevel.getId());
                    updateById(adRequest);
                    //发送邮件
                    TransactionUtils.afterCommitAsyncExecute(executor, () -> sendEmailService.notifyApprover(adRequest));
                }
            } else {
                /**
                 * 当前操作人员是审批人员：先新增一条审批日志，再根据审批人员归属的审批层级，进行送审处理
                 * 送审处理逻辑：
                 *      1. 审批层级是任一人审批时：数据需要推送到下一个节点
                 *      2. 审批层级是全部人审批时：根据审批配置人员，进行送审处理
                 */
                //查询审批层级信息
                AdjApproveLevel approveLevel = approveLevelService.getById(approveConfigByUserId.getLevelId());
                //新增一条审批日志
                approveLogService.save(new AdjApproveLog()
                        .setPropertyId(adRequest.getPropertyId())
                        .setBatchId(adRequest.getBatchId())
                        .setAdjId(adRequest.getId())
                        .setLevelId(approveLevel.getId())
                        .setApproveIndex(approveLevel.getApproveIndex())
                        .setApproveBy(loginUserId)
                        //审批时间加1的原因：当送审人和审批人是同一个人时，为了让审批的时间大于送审的时间
                        .setApproveTime(DateTimeUtils.nowTimestamp() + 1)
                        .setApproveStatus(AdjApprovalStatusEnum.APPROVED.getStatus()));

                //根据审批层级规则进行送审处理
                if (approveLevel.getRule() == ApproveCons.RULE_ANY) {
                    //推送数据至下一个节点
                    adRequest.setApproveIndex(approveLevel.getApproveIndex());
                    adRequest.setApproveLevelId(approveLevel.getId());
                    pushNextIndex(adRequest);
                } else {
                    //查询当前节点所需的审批人员集合
                    List<Integer> approverByApproveLevel = approveConfigService.listApproverByLevelId(adRequest.getPropertyId(), approveLevel.getId());
                    // 检查创建人和责任人跟审批人是否是同一批人，若不是，则再检查跟补位审批人是否是同一批人
                    Set<Integer> owner = Sets.newHashSet(adRequest.getCreateBy(), adRequest.getDutyUser());
                    List<Integer> approverForCheckCreateByAndDutyUser = new ArrayList<>(approverByApproveLevel);
                    if (CollectionUtils.isNotEmpty(substitutePersonnelsForCurrentApproveLevel)) {
                        Map<Integer, Integer> substitutePersonnelByApproveUserId = substitutePersonnelsForCurrentApproveLevel.stream().collect(toMap(AdjOutOfOfficeConfig::getUpdatedBy, AdjOutOfOfficeConfig::getSubstitutePersonnelId));
                        for (AdjOutOfOfficeConfig outOfOfficeConfig : substitutePersonnelsForCurrentApproveLevel) {
                            if (substitutePersonnelByApproveUserId.containsKey(outOfOfficeConfig.getUpdatedBy())) {
                                approverForCheckCreateByAndDutyUser.remove(outOfOfficeConfig.getUpdatedBy());
                                approverForCheckCreateByAndDutyUser.add(substitutePersonnelByApproveUserId.get(outOfOfficeConfig.getUpdatedBy()));
                            }
                        }
                    }
                    if (owner.containsAll(approverByApproveLevel) || owner.containsAll(approverForCheckCreateByAndDutyUser)) {//owner 包含 需审批人员集合
                        //推送数据至下一个节点
                        adRequest.setApproveIndex(approveLevel.getApproveIndex());
                        adRequest.setApproveLevelId(approveLevel.getId());
                        pushNextIndex(adRequest);
                    } else {//owner 不包含 需审批人员集合
                        // 过滤下当前审批人（或补位审批人）和禁用用户，若剩下的人都是禁用用户，则跳到下个层级
                        List<Integer> excludeUser = new ArrayList<>();
                        if (approverByApproveLevel.contains(loginUserId)) {
                            excludeUser.add(loginUserId);
                        }
                        if (loginUserIsSubstitutePersonnel) {
                            excludeUser.addAll(substitutePersonnelsForCurrentApproveLevel.stream()
                                    .filter(item -> item.getSubstitutePersonnelId().equals(loginUserId))
                                    .map(AdjOutOfOfficeConfig::getUpdatedBy).toList());
                        }
                        if (CollectionUtils.isNotEmpty(excludeUser)) {
                            approverByApproveLevel = approverByApproveLevel.stream().filter(item -> !excludeUser.contains(item)).toList();
                        }
                        approverByApproveLevel = userInternalService.getUsers(propertyI18nRedisService.get(propertyId).getTenantId(), approverByApproveLevel)
                                .stream().filter(UserDTO::getStatus).map(UserDTO::getId).toList();
                        if (CollectionUtils.isEmpty(approverByApproveLevel)) {
                            adRequest.setApproveIndex(approveLevel.getApproveIndex());
                            adRequest.setApproveLevelId(approveLevel.getId());
                            AdjApproveLevel nextApprovalLevel = approveLevelService.getAppropriateApprovalLevel(adRequest);
                            // 若没有下个层级，则继续留在当前层级
                            if(Objects.isNull(nextApprovalLevel)){
                                adRequest.setStatus(AdjustmentStatusEnum.UNDETERMINED.getStatus());
                                adRequest.setFlowStatus(FlowStatusEnum.APPROVAING.getFlowStatus());
                                adRequest.setApproveIndex(approveLevel.getApproveIndex());
                                adRequest.setApproveLevelId(approveLevel.getId());
                                updateById(adRequest);
                            } else {
                                // 若有下个层级，则推送到下个层级
                                adRequest.setStatus(AdjustmentStatusEnum.UNDETERMINED.getStatus());
                                adRequest.setFlowStatus(FlowStatusEnum.APPROVAING.getFlowStatus());
                                adRequest.setApproveIndex(nextApprovalLevel.getApproveIndex());
                                adRequest.setApproveLevelId(nextApprovalLevel.getId());
                                updateById(adRequest);
                                //发送邮件
                                TransactionUtils.afterCommitAsyncExecute(executor, () -> sendEmailService.notifyApprover(adRequest));
                            }
                        } else {
                            //更新为【状态=待定】【流程状态=审批中】
                            adRequest.setStatus(AdjustmentStatusEnum.UNDETERMINED.getStatus());
                            adRequest.setFlowStatus(FlowStatusEnum.APPROVAING.getFlowStatus());
                            adRequest.setApproveIndex(approveLevel.getApproveIndex());
                            adRequest.setApproveLevelId(approveLevel.getId());
                            updateById(adRequest);
                            //发送邮件
                            TransactionUtils.afterCommitAsyncExecute(executor, () -> sendEmailService.notifyApprover(adRequest));
                        }
                    }
                }
            }

            //发送adj变更事件
            TransactionUtils.afterCommitAsyncExecute(executor, () ->
                    adjSenderService.publishSave(adRequest.getPropertyId(), adRequest.getId())
            );
        }

        /**
         * 审核数据
         *
         * @param adRequest  调账数据
         * @param approveDTO 审批DTO
         * @param propertyId 资产id
         * @return void
         * @author lichen
         * @date 2025/8/18
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void approve(AdRequest adRequest, AdjApproveDTO approveDTO, int propertyId) {
            //查询当前审批层级数据
            AdjApproveLevel currentApproveLevel = approveLevelService.getById(adRequest.getApproveLevelId());
            Integer rule = currentApproveLevel.getRule();

            //查询当前节点的审批人集合
            //登陆人是审批人可以审批，登陆人是补位人员也可以审批
            List<Integer> currentApproveUsers = approveConfigService.listApproverByLevelId(currentApproveLevel.getPropertyId(), currentApproveLevel.getId());
            currentApproveUsers = userInternalService.getUsers(propertyI18nRedisService.get(propertyId).getTenantId(), currentApproveUsers)
                    .stream().filter(UserDTO::getStatus).map(UserDTO::getId).toList();
            List<AdjOutOfOfficeConfig> adjOutOfOfficeConfigs = adjOutOfOfficeConfigService.listEffectiveConfigForApprover(propertyId, currentApproveLevel.getType(), currentApproveUsers);
            List<Integer> substitutePersonnelIds = adjOutOfOfficeConfigs.stream().map(AdjOutOfOfficeConfig::getSubstitutePersonnelId).toList();
            if (!currentApproveUsers.contains(approveDTO.getApproveBy()) && !substitutePersonnelIds.contains(approveDTO.getApproveBy())) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        "Have no authority to approve");
            }

            //保存审批日志
            AdjApproveLog approveLog = new AdjApproveLog()
                    .setPropertyId(adRequest.getPropertyId()).setBatchId(adRequest.getBatchId()).setAdjId(adRequest.getId())
                    .setLevelId(currentApproveLevel.getId()).setApproveIndex(adRequest.getApproveIndex())
                    .setApproveBy(approveDTO.getApproveBy()).setApproveOpinion(approveDTO.getApproveOpinion())
                    .setApproveStatus(approveDTO.getApproveStatus())
                    .setApproveTime(DateTimeUtils.nowTimestamp());
            approveLogService.save(approveLog);

            //审核通过
            if (approveDTO.getApproveStatus() == AdjApprovalStatusEnum.APPROVED.getStatus()) {//审批通过
                if (rule == ApproveCons.RULE_ALL) {
                    //查询已经审批的人
                    Set<Integer> alreadyApprover = approveLogService.getCurrentNodeAlreadyApprovalUser(adRequest);
                    // 已经审批的人 包含 当前节点所需的审批人集合
                    // 先检查是否有未审批的人，若有，则看看审批人对应的补位审批人是否审批过，若审批过，则审批人无需再审批
                    List<Integer> notApprovers = currentApproveUsers.stream().filter(approveUserId -> !alreadyApprover.contains(approveUserId)).toList();
                    if (CollectionUtils.isEmpty(notApprovers)) {
                        //推送数据至下一个节点
                        pushNextIndex(adRequest);
                    } else {
                        // 看看补位人是否审批过
                        if(checkSubstitutePersonnelApprove(notApprovers, alreadyApprover, adjOutOfOfficeConfigs)){
                            pushNextIndex(adRequest);
                        }
                    }
                } else {
                    //推送数据至下一个节点
                    pushNextIndex(adRequest);
                }
            } else if (approveDTO.getApproveStatus() == AdjApprovalStatusEnum.REJECTED.getStatus()) {//审批拒绝
                //更新数据为【状态=无效】 【流程状态=审批完成】，并发送邮件至数据创建人
                adRequest.setStatus(AdjustmentStatusEnum.INVALID.getStatus());
                adRequest.setFlowStatus(FlowStatusEnum.APPROVAL_COMPLETED.getFlowStatus());
                updateById(adRequest);

                TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                    //发送邮件给数据创建人
                    sendEmailService.notifyDutyUser(adRequest, AdjApprovalStatusEnum.REJECTED.getStatus());
                });
            } else { //退回
                //更新数据为【状态=待定】 【流程状态=Draft】，并发送邮件至数据创建人
                adRequest.setStatus(AdjustmentStatusEnum.UNDETERMINED.getStatus());
                adRequest.setFlowStatus(FlowStatusEnum.DRAFT.getFlowStatus());
                updateById(adRequest);
                TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                    //发送邮件给数据创建人
                    sendEmailService.notifyDutyUser(adRequest, AdjApprovalStatusEnum.RETURN.getStatus());
                });
            }
            //发送adj变更事件
            TransactionUtils.afterCommitAsyncExecute(executor, () ->
                    adjSenderService.publishSave(adRequest.getPropertyId(), adRequest.getId())
            );
        }

        /**
         * 检查补位人员是否审批过
         *
         * @param notApprovers          未审批人
         * @param alreadyApprover       已经审批过的人
         * @param adjOutOfOfficeConfigs 补位人员
         * @return true：补位人员都已审批，false：有补位人员未审批
         * @author yyt
         * @date 2026/3/24
         **/
        @Override
        public boolean checkSubstitutePersonnelApprove(List<Integer> notApprovers, Set<Integer> alreadyApprover, List<AdjOutOfOfficeConfig> adjOutOfOfficeConfigs) {
            if (CollectionUtils.isEmpty(adjOutOfOfficeConfigs)) return false;

            Map<Integer, Integer> substitutePersonnelIdByApproveUserId = adjOutOfOfficeConfigs.stream()
                    .collect(toMap(AdjOutOfOfficeConfig::getUpdatedBy, AdjOutOfOfficeConfig::getSubstitutePersonnelId));
            for (Integer notApprover : notApprovers) {
                Integer substitutePersonnelId = substitutePersonnelIdByApproveUserId.get(notApprover);
                if (Objects.isNull(substitutePersonnelId) || !alreadyApprover.contains(substitutePersonnelId)) {
                    return false;
                }
            }
            return true;
        }

        /**
         * 导出数据
         *
         * @param propertyDTO         资产DTO
         * @param currentThreadLocale 当前上下文语言
         * @param createTimeBegin     开始日期，格式yyyyMMdd
         * @param createTimeEnd       结束日期，格式yyyyMMdd
         * @return cloud.wconnect.frontoffice.pojo.vo.adjustment.ExportVO
         * @author lichen
         * @date 2025/12/4
         */
        @Override
        public ExportVO export(PropertyDTO propertyDTO, Locale currentThreadLocale, String createTimeBegin, String createTimeEnd) {
            ExportVO exportVO = new ExportVO();
            //日期区间
            long startDate = DateTimeUtils.dateStrToEpochSecond(createTimeBegin, BaseConstant.DATE_FORMAT_YYYYMMDD, propertyDTO.getTimezone());
            long endDate = DateTimeUtils.localDateToLong(
                    DateTimeUtils.strToLocalDate(createTimeEnd, BaseConstant.DATE_FORMAT_YYYYMMDD).plusDays(1),
                    propertyDTO.getTimezone());
            //查询数据
            LambdaQueryWrapper<AdRequest> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(AdRequest::getPropertyId, propertyDTO.getId());
            queryWrapper.ge(AdRequest::getCreateTime, startDate);
            queryWrapper.lt(AdRequest::getCreateTime, endDate);
            queryWrapper.orderByAsc(AdRequest::getIsCancel).orderByAsc(AdRequest::getIsArchive).orderByDesc(AdRequest::getId);
            List<AdRequest> adjList = list(queryWrapper);
            if (CollectionUtils.isEmpty(adjList)) {
                return exportVO;
            }

            //用户集合
            Set<Integer> uids = adjList.stream().flatMap(adj -> Stream.of(adj.getCreateBy(), adj.getDutyUser())).collect(toSet());

            //查询adj表单的审批信息
            List<AdjApproveLog> logs = adjApproveLogMapper.getLogsByBatchIdAndAdjId(propertyDTO.getId(), adjList);
            Map<Long, List<AdjApproveLog>> logMap = Maps.newHashMapWithExpectedSize(logs.size());
            if (CollectionUtils.isNotEmpty(logs)) {
                logMap = logs.stream().collect(groupingBy(AdjApproveLog::getAdjId));
                uids.addAll(logs.stream().map(AdjApproveLog::getApproveBy).collect(toSet()));
            }

            //查询用户信息
            Map<Integer, String> userId2Name = userInternalService.getNameMapByIds(propertyDTO.getTenantId(), uids);
            //转换数据
            List<AdjExportVO> adjExportVO = convertToAdjExportVO(adjList, propertyDTO, userId2Name, logMap, currentThreadLocale);
            exportVO.setAdjList(adjExportVO);

            //累计调账金额之和
            BigDecimal totalAmount = adjList.stream().map(AdRequest::getAdjustedDollar).reduce(BigDecimal.ZERO, BigDecimal::add);

            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            //格式化金额
            String totalAmountStr = InternationalizationUtils.formatAmountViewValue(totalAmount, number.getCalculationRule(), number.getCurrency());
            exportVO.setTotalAmount(totalAmountStr);
            return exportVO;
        }

        /**
         * 转化成导出VO
         *
         * @param adjList             adj的数据集合
         * @param propertyDTO         资产DTO
         * @param userMap             用户集合
         * @param logMap              审批日志集合
         * @param currentThreadLocale 当前上下文的语言（前端输入的语言）
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.vo.adjustment.AdjExportVO>
         * @author lichen
         * @date 2025/12/4
         */
        private List<AdjExportVO> convertToAdjExportVO(List<AdRequest> adjList, PropertyDTO propertyDTO, Map<Integer, String> userMap,
                                                       Map<Long, List<AdjApproveLog>> logMap, Locale currentThreadLocale) {
            String timezone = propertyDTO.getTimezone();
            //配置的日期格式
            InternationalizationDateTimeDTO dateTime = propertyDTO.getInternationalization().getDateTime();
            String longDateFormat = dateTime.getLongDate();
            String hourClock = dateTime.getHourClock();
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            //获取货币符号
            String currency = number.getCurrency();

            //国际化信息
            String i18nYesMsg = i18NMessageUtils.message(I18nConstant.YES, currentThreadLocale);
            String i18nNoMsg = i18NMessageUtils.message(I18nConstant.NO, currentThreadLocale);
            String i18nArchivedMsg = i18NMessageUtils.message(I18nConstant.ARCHIVED, currentThreadLocale);
            String i18nApproved = i18NMessageUtils.message(AdjApprovalStatusEnum.APPROVED.getI18nCode(), currentThreadLocale);
            String i18nRejected = i18NMessageUtils.message(AdjApprovalStatusEnum.REJECTED.getI18nCode(), currentThreadLocale);
            String matched = i18NMessageUtils.message(AdjFieldI18nConstant.MATCHED, currentThreadLocale);
            String unMatched = i18NMessageUtils.message(AdjFieldI18nConstant.UN_MATCHED, currentThreadLocale);

            return beanMapper.mapList(adjList, AdjExportVO.class, (adj, vo) -> {
                if (adj.getRequestType() == 0) {
                    vo.setRequestType("--");
                } else {
                    String i18nCodeByAdjType = AdjTypeEnum.getI18nCode(adj.getRequestType());
                    vo.setRequestType(i18NMessageUtils.message(i18nCodeByAdjType, currentThreadLocale));
                }
                //设置业务日期
                Integer businessDate = adj.getBusinessDate();
                String businessDateStr = businessDate > 0
                        ? InternationalizationUtils.formatDateTime(
                        DateTimeUtils.dateStrToEpochSecond(businessDate.toString(), BaseConstant.DATE_FORMAT_YYYYMMDD, timezone),
                        longDateFormat, StringUtils.EMPTY, timezone, currentThreadLocale)
                        : "--";
                vo.setBusinessDate(businessDateStr);
                //设置createBy
                Integer createBy = adj.getCreateBy();
                String createdByStr = userMap.getOrDefault(createBy, "--");
                vo.setCreatedBy(createdByStr);
                //设置dutyUser
                Integer dutyUser = adj.getDutyUser();
                String dutyUserName = userMap.getOrDefault(dutyUser, "--");
                vo.setDutyUser(dutyUserName);
                //设置checkInDate checkOutDate
                long checkInDate = adj.getCheckInDate();
                String checkInDateStr = checkInDate > 0
                        ? InternationalizationUtils.formatDateTime(checkInDate, longDateFormat, StringUtils.EMPTY, timezone, currentThreadLocale)
                        : "--";
                vo.setCheckInDate(checkInDateStr);

                long checkOutDate = adj.getCheckOutDate();
                String checkOutDateStr = checkOutDate > 0
                        ? InternationalizationUtils.formatDateTime(checkOutDate, longDateFormat, StringUtils.EMPTY, timezone, currentThreadLocale)
                        : "--";
                vo.setCheckOutDate(checkOutDateStr);
                //格式化金额
                String totalAmountStr = InternationalizationUtils.formatAmountViewValue(adj.getAdjustedDollar(), number.getCalculationRule(), currency);
                vo.setAdjustedDollar(totalAmountStr);
                //设置Excel导出内容中的Reason信息
                String i18nCodeByAdjReason = AdjReasonEnum.getI18nCode(adj.getChoosedReason());
                if (StringUtils.isNotBlank(i18nCodeByAdjReason)) {
                    vo.setChoosedReason(i18NMessageUtils.message(i18nCodeByAdjReason, currentThreadLocale));
                } else {
                    vo.setChoosedReason("--");
                }
                //设置mistake
                int mistakeType = adj.getMistakeType();
                String mistakeBy = adj.getMistakeBy();
                if (mistakeType == MistakeTypeEnum.EMPLOYEE_NAME.getType()) {
                    vo.setMistakeBy(mistakeBy);
                } else if (mistakeType == MistakeTypeEnum.NA.getType()) {
                    vo.setMistakeBy(i18NMessageUtils.message(I18nConstant.NA, currentThreadLocale) + "\r\n" + mistakeBy);
                } else {
                    vo.setMistakeBy("--");
                }
                //设置RevenueLostFlag
                String revenueLostDesc = Objects.isNull(adj.getRevenueLost()) ? "--" : adj.getRevenueLost() ? i18nYesMsg : i18nNoMsg;
                vo.setRevenueLostFlag(revenueLostDesc);
                //设置审批信息
                List<AdjApproveLog> approveLogList = logMap.get(adj.getId());
                if (CollectionUtils.isNotEmpty(approveLogList)) {
                    List<AdjApproveLog> sortedList = approveLogList.stream().sorted(Comparator.comparing(AdjApproveLog::getId)).toList();
                    StringBuilder approveLogSb = new StringBuilder();
                    for (AdjApproveLog adjApproveLog : sortedList) {
                        //审核人
                        String approver = userMap.getOrDefault(adjApproveLog.getApproveBy(), "--");
                        approveLogSb.append(approver).append("--");
                        if(adjApproveLog.getApproveStatus() == AdjApprovalStatusEnum.SKIPPED.getStatus()){
                            // 已跳过不显示审核时间
                            approveLogSb.append(i18NMessageUtils.message(AdjApprovalStatusEnum.getI18nCode(AdjApprovalStatusEnum.SKIPPED.getStatus()), currentThreadLocale)).append("\r\n");
                        } else {
                            //审核时间
                            String approveTimeStr = InternationalizationUtils.formatDateTime(adjApproveLog.getApproveTime(), longDateFormat, StringUtils.EMPTY, timezone, currentThreadLocale);
                            //审核结果
                            String approveResultI18nCode = AdjApprovalStatusEnum.getI18nCode(adjApproveLog.getApproveStatus());
                            approveLogSb.append(approveTimeStr).append("--").append(i18NMessageUtils.message(approveResultI18nCode, currentThreadLocale)).append("\r\n");
                        }
                    }
                    vo.setApproveDetials(approveLogSb.toString());
                }

                //设置显示状态
                String viewStatus = "";
                if (adj.getFlowStatus() == FlowStatusEnum.DRAFT.getFlowStatus()) {
                    if (adj.getStatus() == AdjustmentStatusEnum.CANCEL.getStatus()) {
                        viewStatus = i18NMessageUtils.message(AdjustmentStatusEnum.CANCEL.getI18nCode(), currentThreadLocale);
                    } else {
                        viewStatus = i18NMessageUtils.message(FlowStatusEnum.DRAFT.getI18nCode(), currentThreadLocale);
                    }
                } else if (adj.getFlowStatus() == FlowStatusEnum.APPROVAING.getFlowStatus()) {
                    viewStatus = i18NMessageUtils.message(FlowStatusEnum.APPROVAING.getI18nCode(), currentThreadLocale);
                } else {
                    if (adj.getStatus() == AdjustmentStatusEnum.VALID.getStatus()) {
                        if (adj.getIsArchive()) {
                            viewStatus = i18nArchivedMsg + "\r\n" + "(" + i18nApproved + ")";
                        } else {
                            viewStatus = i18nApproved;
                        }
                    } else if (adj.getStatus() == AdjustmentStatusEnum.INVALID.getStatus()) {
                        if (adj.getIsArchive()) {
                            viewStatus = i18nArchivedMsg + "\r\n" + "(" + i18nRejected + ")";
                        } else {
                            viewStatus = i18nRejected;
                        }
                    }
                }
                vo.setViewStatus(viewStatus);

                //设置创建日期
                String createTimeStr = InternationalizationUtils.formatDateTime(adj.getCreateTime(),
                        longDateFormat, hourClock, timezone, currentThreadLocale);
                vo.setCreateTime(createTimeStr);

                //设置匹配状态
                vo.setIsMatch(adj.getBusinessDate() > 0 ? matched : unMatched);

                // 已配对且审批拒绝的数据，设置背景色
                if (adj.getBusinessDate() > 0 && adj.getStatus() == AdjustmentStatusEnum.INVALID.getStatus()) {
                    vo.setBackground(IndexedColors.ROSE.getIndex());
                }

                if (StringUtils.isBlank(vo.getAccountNumber())) {
                    vo.setAccountNumber("--");
                }
            });
        }

        /**
         * 数据推送至下一个节点
         *
         * @param adRequest 调整
         * @description 数据推送至下一个节点
         * @author lichen
         * @date 2023/4/3 8:58
         */
        private void pushNextIndex(AdRequest adRequest) {
            AdjApproveLevel nextApproveIndex = approveLevelService.getAppropriateApprovalLevel(adRequest);
            if (Objects.isNull(nextApproveIndex)) {
                //更新数据为【状态=有效，流程状态=审批结束】
                adRequest.setFlowStatus(FlowStatusEnum.APPROVAL_COMPLETED.getFlowStatus());
                adRequest.setStatus(AdjustmentStatusEnum.VALID.getStatus());
                updateById(adRequest);
                //发邮件给创建人
                TransactionUtils.afterCommitAsyncExecute(executor, () -> sendEmailService.notifyDutyUser(adRequest, AdjApprovalStatusEnum.APPROVED.getStatus()));
            } else {
                //更新数据为【状态=待定，流程状态=审批中】
                adRequest.setFlowStatus(FlowStatusEnum.APPROVAING.getFlowStatus());
                adRequest.setStatus(AdjustmentStatusEnum.UNDETERMINED.getStatus());
                adRequest.setApproveIndex(nextApproveIndex.getApproveIndex());
                adRequest.setApproveLevelId(nextApproveIndex.getId());
                updateById(adRequest);
                //发邮件
                TransactionUtils.afterCommitAsyncExecute(executor, () -> sendEmailService.notifyApprover(adRequest));
            }
        }

        /**
         * 获取已配对的总金额
         *
         * @param propertyId 资产ID
         * @return java.math.BigDecimal
         * @author lichen
         * @date 2023/12/11
         */
        @Override
        public BigDecimal getMatchedTotalAmount(Integer propertyId) {
            //构建queryWrapper
            LambdaQueryWrapper<AdRequest> totalAmountQueryWrapper = new QueryWrapper<AdRequest>()
                    .select("sum(adjusted_dollar) totalAmount")
                    .lambda()
                    .eq(AdRequest::getPropertyId, propertyId)
                    .gt(AdRequest::getBusinessDate, 0)
                    .ne(AdRequest::getStatus, AdjustmentStatusEnum.CANCEL.getStatus());
            Map<String, Object> totalAmountMap = getMap(totalAmountQueryWrapper);
            return Objects.isNull(totalAmountMap) ? BigDecimal.ZERO :
                    new BigDecimal(totalAmountMap.get("totalAmount").toString());
        }

        /**
         * 批量更新adjustment的责任人
         *
         * @param propertyId    资产ID
         * @param adRequests    adjustment数据集合
         * @param editRequestBy 编辑requestBy的对象
         * @param loginUserId   登录用户id
         * @author lichen
         * @date 2023/5/31
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void updateRequestBy(Integer propertyId, List<AdRequest> adRequests, EditRequestBy editRequestBy, int loginUserId) {
            adRequests.forEach(adj -> {
                adj.setDutyUser(editRequestBy.getRequestBy());
                adj.setRequestType(editRequestBy.getRequestType());
            });
            //更新adj数据
            updateBatchById(adRequests);

            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                //发送adjustment数据变更事件
                adjSenderService.batchPublishSave(propertyId,
                        adRequests.stream().map(AdRequest::getId).toList());
            });

        }

        /**
         * 根据adjNumber和adjType 查询adj数据
         *
         * @param propertyId    资产id
         * @param adjNumberList adjNumber 集合
         * @param adjType       requestType
         * @return java.util.List<cloud.wconnect.frontoffice.entity.adjustment.AdRequest>
         * @author lichen
         * @date 2023/9/26
         */
        @Override
        public List<AdRequest> listByPropertyIdAndAdjNumbersAndRequestType(Integer propertyId, List<String> adjNumberList, int adjType) {
            if (CollectionUtils.isEmpty(adjNumberList)) return Collections.emptyList();
            return list(new LambdaQueryWrapper<AdRequest>()
                    .eq(AdRequest::getPropertyId, propertyId)
                    .in(AdRequest::getAdjNumber, adjNumberList)
                    .eq(AdRequest::getRequestType, adjType));
        }

        /**
         * 查询审批中调账数据
         *
         * @param propertyId 资产id
         * @return java.util.List<cloud.wconnect.frontoffice.entity.adjustment.AdRequest>
         * @author lichen
         * @date 2026/1/28
         */
        @Override
        public List<AdRequest> listApproving(int propertyId) {
            //审批中的调账数据集合
            return list(new LambdaQueryWrapper<AdRequest>()
                    .eq(AdRequest::getPropertyId, propertyId)
                    .eq(AdRequest::getFlowStatus, FlowStatusEnum.APPROVAING.getFlowStatus()));
        }

        /**
         * 获取审批中的调账数据
         *
         * @param propertyId 资产id
         * @param levelIds   审批层级id集合
         * @return 调账数据
         * @author yyt
         * @date 2026/3/19
         */
        @Override
        public List<AdRequest> listApprovingByLevels(Integer propertyId, List<Long> levelIds) {
            return list(new LambdaQueryWrapper<AdRequest>()
                    .eq(AdRequest::getPropertyId, propertyId)
                    .eq(AdRequest::getFlowStatus, FlowStatusEnum.APPROVAING.getFlowStatus())
                    .in(AdRequest::getApproveLevelId, levelIds));
        }

        /**
         * 获取待用户审批的adjustment数量
         *
         * @param propertyId 资产id
         * @param userId     用户id
         * @return java.lang.Long
         * @author lichen
         * @date 2023/12/11
         */
        @Override
        public Long getWaitApprovalTotalByApprover(Integer propertyId, int userId) {
            //根据用户ID查询审批配置
            List<Long> levelIds = approveConfigService.getApproveConfigByUserId(propertyId, userId)
                    .stream().map(AdjApproveConfig::getLevelId).toList();
            if (CollectionUtils.isEmpty(levelIds)) return 0L;

            return count(new LambdaQueryWrapper<AdRequest>().eq(AdRequest::getPropertyId, propertyId)
                    .in(AdRequest::getApproveLevelId, levelIds)
                    .eq(AdRequest::getFlowStatus, FlowStatusEnum.APPROVAING.getFlowStatus()));
        }

        /**
         * 查询adjustment数量和金额的趋势
         *
         * @param query           查询条件
         * @param propertyDTOList 资产集合
         * @return cloud.wconnect.frontoffice.pojo.vo.adjustment.statistic.StatisticsTrendVO
         * @author lichen
         * @date 2023/12/11
         */
        @Override
        public StatisticsTrendVO listPropertyTotalAndAmountTrend(StatisticsTypeTrendQuery query, List<PropertyDTO> propertyDTOList) {
            List<AdjCountTrendVO> countList = Lists.newArrayListWithCapacity(propertyDTOList.size());
            List<AdjAmountTrendVO> amountList = Lists.newArrayListWithCapacity(propertyDTOList.size());

            for (PropertyDTO propertyDTO : propertyDTOList) {
                //根据统计单位计算统计日期
                Map<String, LocalDate> statisticDate = calculatestatisticDate(query.getUnit(), query.getSpan(), propertyDTO.getTimezone());
                //资产时区下的开始日期
                String startDate = DateTimeUtils.localDateToStr(statisticDate.get("startLocalDate"), BaseConstant.DATE_FORMAT_YYYYMMDD);
                //资产时区下的结束日期
                String endDate = DateTimeUtils.localDateToStr(statisticDate.get("endLocalDate"), BaseConstant.DATE_FORMAT_YYYYMMDD);
                //查询资产有效的adj
                List<AdRequest> adRequests = listMatchedByDateRange(propertyDTO.getId(), startDate, endDate);

                //根据统计单位和统计时间生成数量 和 趋势集合
                List<StatisticTotalAndAmountDTO> statisticTotalAndAmountDTOList = generateTotalAndAmountTrend(adRequests, query.getUnit(), query.getSpan(), propertyDTO);

                //资产的adjustment数量变化趋势
                List<AdjCountByDateVO> adjCountByDateVOS = statisticTotalAndAmountDTOList.stream()
                        .sorted(Comparator.comparing(StatisticTotalAndAmountDTO::getDate))
                        .map(item -> new AdjCountByDateVO(item.getDate(), item.getTotalNumber())).toList();
                countList.add(new AdjCountTrendVO(propertyDTO.getId(), propertyDTO.getAbbr(), adjCountByDateVOS));

                //资产的adjustment总金额变化趋势
                List<AdjAmountByDateVO> adjAmountByDateVOS = statisticTotalAndAmountDTOList.stream()
                        .sorted(Comparator.comparing(StatisticTotalAndAmountDTO::getDate))
                        .map(item -> new AdjAmountByDateVO(item.getDate(), item.getTotalAmount())).toList();
                amountList.add(new AdjAmountTrendVO(propertyDTO.getId(), propertyDTO.getAbbr(), adjAmountByDateVOS));

            }
            return new StatisticsTrendVO(countList, amountList);
        }

        /**
         * 生成数量和金额的趋势集合
         *
         * @param adRequests  adjustment集合
         * @param unit        统计单位，1：月 2：季度
         * @param span        统计时间跨度
         * @param propertyDTO 资产DTO
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.dto.adjustment.StatisticTotalAndAmountDTO>
         * @author lichen
         * @date 2023/12/11
         */
        private List<StatisticTotalAndAmountDTO> generateTotalAndAmountTrend(List<AdRequest> adRequests, Integer unit, Integer span, PropertyDTO propertyDTO) {
            String timezone = propertyDTO.getTimezone();
            List<StatisticTotalAndAmountDTO> statisticTotalAndAmountDTOList = Lists.newArrayListWithCapacity(span);
            if (StatisticsTypeTrendQuery.MONTH == unit) {
                LocalDate localDate = LocalDate.now(ZoneId.of(timezone));

                for (int i = 1; i <= span; i++) {
                    //获取日期,例如：2023-05
                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
                    String date = localDate.format(formatter);

                    LocalDate firstDayOfMonth = localDate.withDayOfMonth(1);
                    //当月的开始日期
                    long startTime = DateTimeUtils.localDateToLong(firstDayOfMonth, timezone);
                    //当月的结束日期(下个月第一天的整点时刻)
                    long endTime = DateTimeUtils.localDateToLong(firstDayOfMonth.plusMonths(1), timezone);

                    //计算符合条件的adjustment
                    List<AdRequest> matchAdjList = adRequests.stream()
                            .filter(item -> item.getCreateTime() >= startTime && item.getCreateTime() < endTime).toList();
                    BigDecimal totalAmount = matchAdjList.stream()
                            .map(AdRequest::getAdjustedDollar)
                            .reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
                    statisticTotalAndAmountDTOList.add(new StatisticTotalAndAmountDTO(date, matchAdjList.size(), totalAmount));

                    //往前推一个月
                    localDate = localDate.minusMonths(1);
                }
            } else {
                LocalDate localDate = LocalDate.now(ZoneId.of(timezone));

                for (int i = 1; i <= span; i++) {
                    int year = localDate.getYear();
                    //当前日期的所属季度
                    int quarter = (localDate.getMonthValue() - 1) / 3 + 1;

                    //季度的开始日期
                    LocalDate startDate = LocalDate.of(year, quarter * 3 - 2, 1);
                    long startTime = DateTimeUtils.localDateToLong(startDate, timezone);
                    //季度的结束日期 + 1天，为了查询出结束日期当天的数据
                    LocalDate endDate = startDate.plusMonths(3).plusDays(1);
                    //结束日期时间戳 等于 季度的结束日期往后推一天的零点时刻，为了查询出结束日期当天的数据
                    long endTime = DateTimeUtils.localDateToLong(endDate, timezone);

                    //计算符合条件的adjustment
                    List<AdRequest> matchAdjList = adRequests.stream()
                            .filter(item -> item.getCreateTime() >= startTime && item.getCreateTime() < endTime).toList();
                    BigDecimal totalAmount = matchAdjList.stream()
                            .map(AdRequest::getAdjustedDollar)
                            .reduce(BigDecimal::add).orElse(BigDecimal.ZERO);

                    //季度的日期格式为：2023-1
                    String date = year + "-" + quarter;
                    statisticTotalAndAmountDTOList.add(new StatisticTotalAndAmountDTO(date, matchAdjList.size(), totalAmount));

                    //往前推一个季度
                    localDate = localDate.minusMonths(3);
                }
            }
            return statisticTotalAndAmountDTOList;
        }

        /**
         * 查询指定事件范围已配对的调账数据
         *
         * @param propertyId 资产ID
         * @param startDate  开始时间，格式yyyyMMdd
         * @param endDate    结束时间，格式yyyyMMdd
         * @return java.util.List<cloud.wconnect.frontoffice.entity.adjustment.AdRequest>
         * @author lichen
         * @date 2023/12/11
         */
        @Override
        public List<AdRequest> listMatchedByDateRange(Integer propertyId, String startDate, String endDate) {
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            // 时间区间
            long startTime = DateTimeUtils.dateStrToEpochSecond(startDate, BaseConstant.DATE_FORMAT_YYYYMMDD, propertyDTO.getTimezone());
            // 加1天，为了查询出结束日期当天的数据
            long endTime = DateTimeUtils.localDateToLong(
                    DateTimeUtils.strToLocalDate(endDate, BaseConstant.DATE_FORMAT_YYYYMMDD).plusDays(1), propertyDTO.getTimezone());
            return list(new LambdaQueryWrapper<AdRequest>()
                    .eq(AdRequest::getPropertyId, propertyId)
                    .ge(AdRequest::getCreateTime, startTime)
                    .lt(AdRequest::getCreateTime, endTime)
                    //businessDate大于0表示已配对
                    .gt(AdRequest::getBusinessDate, 0)
                    //过滤掉作废的adjustment
                    .ne(AdRequest::getStatus, AdjustmentStatusEnum.CANCEL.getStatus()));
        }

        /**
         * 删除adjustment
         *
         * @param adRequests 调账数据集合
         * @param propertyId 资产id
         * @author lichen
         * @date 2024/1/29
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void remove(List<AdRequest> adRequests, int propertyId) {
            List<Long> ids = adRequests.stream().map(AdRequest::getId).toList();
            //注销
            removeByIds(ids);
            //删除备注
            adRequestCommentService.remove(new LambdaQueryWrapper<AdRequestComment>()
                    .in(AdRequestComment::getArId, ids));
            //删除操作日志
            adjOperateLogService.remove(new LambdaQueryWrapper<AdjOperateLog>()
                    .in(AdjOperateLog::getAdjId, ids));
            //删除审批日志
            approveLogService.remove(new LambdaQueryWrapper<AdjApproveLog>()
                    .in(AdjApproveLog::getAdjId, ids));
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                //发送删除adj的事件
                adjSenderService.publishDelete(propertyId, adRequests);
            });
        }

        /**
         * 更新归档状态
         *
         * @param adRequest   调账数据
         * @param operateDTO  操作DTO
         * @param loginUserId 登录用户id
         * @author lichen
         * @date 2024/1/29
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void updateArchiveStatus(AdRequest adRequest, OperateDTO operateDTO, int loginUserId) {
            //更新归档状态
            AdRequest updateEntity = new AdRequest();
            updateEntity.setId(adRequest.getId());
            updateEntity.setIsArchive(OperateTypeEnum.ARCHIVED.getOperateType() == operateDTO.getType());
            updateEntity.setUpdateBy(loginUserId);
            updateEntity.setUpdateTime(DateTimeUtils.nowTimestamp());
            updateById(updateEntity);

            //记录操作日志
            adjOperateLogService.save(AdjOperateLog.builder()
                    .propertyId(adRequest.getPropertyId())
                    .adjId(adRequest.getId())
                    .userId(loginUserId)
                    .operateType(operateDTO.getType())
                    .operateTime(DateTimeUtils.nowTimestamp())
                    .opinion(operateDTO.getOpinion())
                    .build());
        }

        /**
         * 查询调账数据的操作日志
         *
         * @param propertyId 资产id
         * @param adRequest  调账数据
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.vo.adjustment.OperateLogVO>
         * @author lichen
         * @date 2024/1/30
         */
        @Override
        public List<OperateLogVO> listOperateLog(int propertyId, AdRequest adRequest) {
            Integer tenantId = propertyI18nRedisService.get(propertyId).getTenantId();

            Set<Integer> userIds = Sets.newHashSet();
            List<OperateLogVO> operateLogVOS = Lists.newArrayList();

            //查询操作日志
            List<AdjOperateLog> adjOperateLogs = adjOperateLogService.list(new LambdaQueryWrapper<AdjOperateLog>()
                    .eq(AdjOperateLog::getPropertyId, propertyId)
                    .eq(AdjOperateLog::getAdjId, adRequest.getId())
                    .orderByDesc(AdjOperateLog::getOperateTime));
            userIds.addAll(adjOperateLogs.stream().map(AdjOperateLog::getUserId).toList());

            //审批日志
            List<AdjApproveLog> approveLogs = approveLogService.list(new LambdaQueryWrapper<AdjApproveLog>()
                    .eq(AdjApproveLog::getPropertyId, propertyId)
                    .eq(AdjApproveLog::getAdjId, adRequest.getId())
                    .orderByDesc(AdjApproveLog::getApproveTime));
            userIds.addAll(approveLogs.stream().map(AdjApproveLog::getApproveBy).toList());

            //用户集合
            Map<Integer, UserDTO> userId2UserDTO = userInternalService.getUserMapByIds(tenantId, userIds);

            operateLogVOS.addAll(beanMapper.mapList(adjOperateLogs, OperateLogVO.class, (entity, vo) -> {
                UserDTO userDTO = userId2UserDTO.get(entity.getUserId());
                vo.setUserName(userDTO.getName());
                vo.setEmail(userDTO.getEmail());
                vo.setAvatar(userDTO.getAvatar());
            }));
            operateLogVOS.addAll(approveLogs.stream().map(entity -> {
                UserDTO userDTO = userId2UserDTO.get(entity.getApproveBy());

                OperateLogVO vo = new OperateLogVO();
                vo.setUserId(entity.getApproveBy());
                vo.setUserName(userDTO.getName());
                vo.setEmail(userDTO.getEmail());
                vo.setAvatar(userDTO.getAvatar());
                vo.setOperateTime(entity.getApproveTime());
                vo.setOperateType(entity.getApproveStatus());
                vo.setOpinion(entity.getApproveOpinion());
                return vo;
            }).toList());

            //按照时间倒序排列
            return operateLogVOS.stream().sorted(Comparator.comparing(OperateLogVO::getOperateTime).reversed()).toList();
        }

        /**
         * 分页查询调账数据
         *
         * @param tenantId       租户id
         * @param propertyId     资产id
         * @param adRequestQuery 查询条件
         * @return cloud.wconnect.commonlib.pojo.dto.ListPagedDTO<cloud.wconnect.frontoffice.pojo.vo.adjustment.AdRequestVO>
         * @author lichen
         * @date 2025/8/16
         */
        @Override
        public ListPagedDTO<AdRequestVO> pageList(Integer tenantId, int propertyId, AdRequestQuery adRequestQuery) {
            //查询当前登录人归属的审批层级ID
            List<Long> approveLevelIdList = Collections.emptyList();
            if (Objects.nonNull(adRequestQuery.getApprovalUser())) {
                //查询当前登录人归属的审批层级id集合
                approveLevelIdList = approveConfigService.getApproveConfigByUserId(propertyId, adRequestQuery.getApprovalUser())
                        .stream().map(AdjApproveConfig::getLevelId).toList();
                if (CollectionUtils.isEmpty(approveLevelIdList)) {
                    return new ListPagedDTO<>();
                }
            }

            //构建QueryWrapper
            LambdaQueryWrapper<AdRequest> lambdaQueryWrapper = buildQueryWrapper(adRequestQuery, propertyId);
            //拼装审批层级ID
            lambdaQueryWrapper.in(CollectionUtils.isNotEmpty(approveLevelIdList), AdRequest::getApproveLevelId, approveLevelIdList);
            //排序
            lambdaQueryWrapper.orderByAsc(AdRequest::getIsCancel).orderByAsc(AdRequest::getIsArchive).orderByDesc(AdRequest::getId);
            //创建Page对象
            Page<AdRequest> page = new Page<>(adRequestQuery.getPage(), adRequestQuery.getSize());
            //执行分页查询
            Page<AdRequest> adRequestPage = page(page, lambdaQueryWrapper);
            if (adRequestPage.getTotal() == BaseConstant.ZERO) {
                return new ListPagedDTO<>();
            }

            List<AdRequest> records = adRequestPage.getRecords();

            //查询用户信息
            List<Integer> userIds = records.stream()
                    .flatMap(re -> Stream.of(re.getCreateBy(), re.getDutyUser(), re.getUpdateBy())).distinct().collect(toList());
            Map<Integer, String> userId2Name = userInternalService.getNameMapByIds(tenantId, userIds);

            //查询审批单comment的数量
            List<Long> adjIds = records.stream().map(AdRequest::getId).collect(toList());
            Map<Long, Long> commentCountMap = adRequestCommentService.getCounts(adjIds);

            //List<AdRequest> -> List<AdRequestVO>
            List<AdRequestVO> voList = beanMapper.mapList(records, AdRequestVO.class, (re, vo) -> {
                // 设置审批单附件标识 true：有，false：无
                vo.setHaveAttachments(StringUtils.isNotEmpty(vo.getAttachments()) && !vo.getAttachments().equals("[]"));
                //设置创建人、修改人、责任人姓名
                Integer createBy = vo.getCreateBy();
                vo.setCreateName(userId2Name.get(createBy));
                Integer updateBy = vo.getUpdateBy();
                vo.setUpdateName(userId2Name.get(updateBy));
                Integer dutyUser = vo.getDutyUser();
                vo.setDutyUserName(userId2Name.get(dutyUser));
                //设置comment总数
                vo.setCommentsCount(commentCountMap.getOrDefault(vo.getId(), BaseConstant.LONG_ZERO));
            });
            //返回列表页面
            return new ListPagedDTO<>(adRequestPage.getTotal(), voList);
        }

        /**
         * 分页查询待分配责任人的调账数据
         *
         * @param propertyId      资产id
         * @param needAssignQuery 查询条件
         * @return cloud.wconnect.commonlib.pojo.dto.ListPagedDTO<cloud.wconnect.frontoffice.pojo.vo.adjustment.AdRequestVO>
         * @author lichen
         * @date 2025/8/16
         */
        @Override
        public ListPagedDTO<AdRequestVO> pageNeedAssign(int propertyId, NeedAssignQuery needAssignQuery) {
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            String timezone = propertyDTO.getTimezone();

            // 构建查询条件
            LambdaQueryWrapper<AdRequest> lambdaQueryWrapper = new LambdaQueryWrapper<AdRequest>()
                    .eq(AdRequest::getPropertyId, propertyId)
                    .eq(AdRequest::getFlowStatus, FlowStatusEnum.DRAFT.getFlowStatus())
                    .ne(AdRequest::getStatus, AdjustmentStatusEnum.CANCEL.getStatus())
                    .like(StringUtils.isNotBlank(needAssignQuery.getGuestName()), AdRequest::getGuestName, needAssignQuery.getGuestName())
                    .eq(StringUtils.isNotBlank(needAssignQuery.getRoomNumber()), AdRequest::getRoomNumber, needAssignQuery.getRoomNumber())
                    .eq(StringUtils.isNotBlank(needAssignQuery.getTrxCode()), AdRequest::getTrxCode, needAssignQuery.getTrxCode())
                    .ge(Objects.nonNull(needAssignQuery.getMinDollar()), AdRequest::getAdjustedDollar, needAssignQuery.getMinDollar())
                    .le(Objects.nonNull(needAssignQuery.getMaxDollar()), AdRequest::getAdjustedDollar, needAssignQuery.getMaxDollar())
                    .and(qw -> qw.eq(AdRequest::getDutyUser, 0).or().eq(AdRequest::getRequestType, 0))
                    .orderByDesc(AdRequest::getId);

            // 设置创建日期查询条件
            if (StringUtils.isNotBlank(needAssignQuery.getCreateTimeBegin()) && StringUtils.isNotBlank(needAssignQuery.getCreateTimeEnd())) {
                long createTimeBegin = DateTimeUtils.dateStrToEpochSecond(needAssignQuery.getCreateTimeBegin(), BaseConstant.DATE_FORMAT_YYYYMMDD, timezone);
                long createTimeEnd = DateTimeUtils.localDateToLong(
                        DateTimeUtils.strToLocalDate(needAssignQuery.getCreateTimeEnd(), BaseConstant.DATE_FORMAT_YYYYMMDD).plusDays(1), timezone);
                lambdaQueryWrapper.ge(AdRequest::getCreateTime, createTimeBegin)
                        .lt(AdRequest::getCreateTime, createTimeEnd);
            }

            Page<AdRequest> resultPage = page(new Page<>(needAssignQuery.getPage(), needAssignQuery.getSize()),
                    lambdaQueryWrapper);
            if (resultPage.getTotal() == 0) return new ListPagedDTO<>();

            List<AdRequest> records = resultPage.getRecords();

            //查询用户信息
            List<Integer> userIds = records.stream()
                    .flatMap(re -> Stream.of(re.getCreateBy(), re.getDutyUser(), re.getUpdateBy())).distinct().collect(toList());
            Map<Integer, String> userId2Name = userInternalService.getNameMapByIds(propertyDTO.getTenantId(), userIds);

            //List<AdRequest> -> List<AdRequestVO>
            List<AdRequestVO> voList = beanMapper.mapList(records, AdRequestVO.class, (re, vo) -> {
                // 设置审批单附件标识 true：有，false：无
                vo.setHaveAttachments(StringUtils.isNotEmpty(vo.getAttachments()) && !vo.getAttachments().equals("[]"));
                // 设置comment总数
                vo.setCommentsCount(BaseConstant.LONG_ZERO);
                // 责任人姓名
                Integer dutyUser = vo.getDutyUser();
                vo.setDutyUserName(userId2Name.get(dutyUser));
            });
            //返回列表页面
            return new ListPagedDTO<>(resultPage.getTotal(), voList);
        }

        /**
         * 校验调账数据是否存在
         *
         * @param id 主键
         * @return cloud.wconnect.frontoffice.entity.adjustment.AdRequest
         * @author lichen
         * @date 2025/8/16
         */
        @Override
        public AdRequest checkExist(Long id) {
            AdRequest adRequest = getById(id);
            if (Objects.isNull(adRequest)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "id=" + id + " ,Adjustment Requests not found");
            }
            return adRequest;
        }

        /**
         * 校验调账数据是否属于该资产
         *
         * @param adRequest  调账数据
         * @param propertyId 资产id
         * @author lichen
         * @date 2025/8/16
         */
        @Override
        public void checkBelong(AdRequest adRequest, int propertyId) {
            if (adRequest.getPropertyId().compareTo(propertyId) != 0) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        MessageFormat.format("adjustment not belong currentPropertyId, adjustmentId:{0},belongPropertyId:{1},currentPropertyId:{2}",
                                adRequest.getId(), adRequest.getPropertyId(), propertyId));
            }
        }

        /**
         * 获取调账数据详情
         *
         * @param tenantId     租户id
         * @param propertyId   资产id
         * @param adjustmentId 调账数据id
         * @return cloud.wconnect.frontoffice.pojo.vo.adjustment.AdRequestVO
         * @author lichen
         * @date 2025/8/16
         */
        @Override
        public AdRequestVO details(Integer tenantId, int propertyId, long adjustmentId) {
            AdRequest adRequest = checkExist(adjustmentId);
            checkBelong(adRequest, propertyId);

            AdRequestVO adRequestVO = beanMapper.map(adRequest, AdRequestVO.class);
            //创建人、责任人、修改人集合
            Set<Integer> userIds = Sets.newHashSet(adRequest.getCreateBy(), adRequest.getUpdateBy(), adRequest.getDutyUser());

            //查询评论信息
            LambdaQueryWrapper<AdRequestComment> adRequestCommentQueryWrapper = new LambdaQueryWrapper<>();
            adRequestCommentQueryWrapper.eq(AdRequestComment::getPropertyId, propertyId);
            adRequestCommentQueryWrapper.eq(AdRequestComment::getArId, adRequest.getId());
            adRequestCommentQueryWrapper.orderByDesc(AdRequestComment::getId);
            List<AdRequestComment> commentList = adRequestCommentService.list(adRequestCommentQueryWrapper);
            if (CollectionUtils.isNotEmpty(commentList)) {
                Set<Integer> commentUserSet = commentList.stream().map(AdRequestComment::getCreatedBy).collect(toSet());
                userIds.addAll(commentUserSet);
            }

            //查询用户信息
            Map<Integer, String> userId2Name = userInternalService.getNameMapByIds(tenantId, userIds);

            //设置评论信息
            if (CollectionUtils.isNotEmpty(commentList)) {
                List<AdRequestCommentVO> adRequestCommentVOS = beanMapper.mapList(commentList, AdRequestCommentVO.class, (comment, vo) -> {
                    Integer createdBy = vo.getCreatedBy();
                    vo.setCreatedName(userId2Name.get(createdBy));
                });
                adRequestVO.setComments(adRequestCommentVOS);
                adRequestVO.setCommentsCount((long) adRequestCommentVOS.size());
            } else {
                adRequestVO.setCommentsCount(BaseConstant.LONG_ZERO);
            }

            //设置创建人、修改人、责任人姓名
            String createName = userId2Name.get(adRequest.getCreateBy());
            adRequestVO.setCreateName(createName);
            String updateName = userId2Name.get(adRequest.getUpdateBy());
            adRequestVO.setUpdateName(updateName);
            String dutyName = userId2Name.get(adRequest.getDutyUser());
            adRequestVO.setDutyUserName(dutyName);

            //设置审批单附件标识 true：有，false：无
            adRequestVO.setHaveAttachments(StringUtils.isNotEmpty(adRequestVO.getAttachments()) && !adRequestVO.getAttachments().equals("[]"));
            return adRequestVO;
        }

        /**
         * 查询调账数据汇总信息
         *
         * @param propertyId     资产id
         * @param adRequestQuery 查询条件
         * @return java.util.Map<java.lang.String, java.math.BigDecimal>
         * @author lichen
         * @date 2025/8/16
         */
        @Override
        public Map<String, BigDecimal> summary(int propertyId, AdRequestQuery adRequestQuery) {
            //存在非空的查询条件时，按照查询条件统计金额
            if (AdRequestQuery.isExistNotNullCondition(adRequestQuery)) {
                //查询当前登录人归属的审批层级ID
                List<Long> approveLevelIdList = Collections.emptyList();
                if (Objects.nonNull(adRequestQuery.getApprovalUser())) {
                    //查询当前登录人归属的审批层级id集合
                    approveLevelIdList = approveConfigService.getApproveConfigByUserId(propertyId, adRequestQuery.getApprovalUser())
                            .stream().map(AdjApproveConfig::getLevelId).toList();
                    if (CollectionUtils.isEmpty(approveLevelIdList)) {
                        return ImmutableMap.of("totalAmount", BigDecimal.ZERO);
                    }
                }

                LambdaQueryWrapper<AdRequest> lambdaQueryWrapper = buildQueryWrapper(adRequestQuery, propertyId);
                lambdaQueryWrapper.in(CollectionUtils.isNotEmpty(approveLevelIdList), AdRequest::getApproveLevelId, approveLevelIdList);
                lambdaQueryWrapper.select(AdRequest::getAdjustedDollar);

                List<AdRequest> list = list(lambdaQueryWrapper);
                if (CollectionUtils.isEmpty(list)) {
                    return ImmutableMap.of("totalAmount", BigDecimal.ZERO);
                } else {
                    return ImmutableMap.of("totalAmount", list.stream().map(AdRequest::getAdjustedDollar).reduce(BigDecimal.ZERO, BigDecimal::add));
                }
            }
            //默认查询已配对的调账总金额
            return ImmutableMap.of("totalAmount", getMatchedTotalAmount(propertyId));
        }

        /**
         * 处理报告数据
         *
         * @param propertyDTO 资产DTO
         * @param journalDTOS 报告数据集合
         * @param userId      操作人 id
         * @param dateRange   数据日期范围
         * @author lichen
         * @date 2025/8/16
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleReportData(PropertyDTO propertyDTO, List<JournalDTO> journalDTOS, int userId, Map<String, Long> dateRange) {
            RLock rLock = redissonClient.getLock(AdjConstant.GET_MAX_ADJ_NUMBER_LOCK_PREFIX + propertyDTO.getId().toString());
            if (RedissonLockUtil.getLock(rLock, 3, 5)) {
                try {
                    // 执行处理报告数据逻辑
                    doHandleReportData(propertyDTO, journalDTOS, userId, dateRange);
                } finally {
                    if (rLock.isHeldByCurrentThread()) {
                        // 释放锁
                        rLock.unlock();
                    }
                }
            } else {
                throw new RuntimeException("handleReportData error, The number of execution failures reached the upper limit");
            }
        }

        /**
         * 处理报告数据
         *
         * @param propertyDTO 资产DTO
         * @param journalDTOS 报告数据集合
         * @param userId      操作人 id
         * @param dateRange   数据日期范围
         * @author lichen
         * @date 2025/8/16
         */
        private void doHandleReportData(PropertyDTO propertyDTO, List<JournalDTO> journalDTOS, int userId, Map<String, Long> dateRange) {
            if (CollectionUtils.isEmpty(journalDTOS)) {
                // 清理非作废数据的配对状态
                clearNonCancelData(propertyDTO, dateRange);
                // 清空作废数据
                clearCancelData(propertyDTO, dateRange);
            } else {
                // 批量保存pmsAccount
                adjPmsAccountService.saveBatch(propertyDTO, journalDTOS.stream().map(JournalDTO::getPmsAccount).distinct().toList());

                // 作废的PMS Account
                List<String> pmsAccountListByCancel = adjPmsConfigService.listByPropertyIdAndDataType(propertyDTO.getId(), AdjPmsConfig.DATA_TYPE_CANCEL)
                        .stream().map(AdjPmsConfig::getPmsAccount).toList();

                // 按照是否作废分组
                Map<Boolean, List<JournalDTO>> isCancel2JournalDTOS = journalDTOS.stream().collect(partitioningBy(item -> pmsAccountListByCancel.contains(item.getPmsAccount())));

                // 作废的Journal数据
                List<JournalDTO> journalDTOSByCancel = isCancel2JournalDTOS.getOrDefault(true, Collections.emptyList());
                if (CollectionUtils.isNotEmpty(journalDTOSByCancel)) {
                    // 配对作废的数据
                    matchDataForCancel(propertyDTO, journalDTOSByCancel, userId);
                } else {
                    // 清空作废数据
                    clearCancelData(propertyDTO, dateRange);
                }

                // 非作废的Journal数据
                List<JournalDTO> journalDTOSByNonCancel = isCancel2JournalDTOS.getOrDefault(false, Collections.emptyList());
                if (CollectionUtils.isNotEmpty(journalDTOSByNonCancel)) {
                    // 配对数据
                    matchData(propertyDTO, journalDTOSByNonCancel, userId);
                } else {
                    // 清理非作废数据的配对状态
                    clearNonCancelData(propertyDTO, dateRange);
                }
            }
        }

        /**
         * 配对作废的数据
         *
         * @param propertyDTO         资产DTO
         * @param journalDTOSByCancel 作废的Journal数据
         * @param userId              操作人 id
         * @author lichen
         * @date 2026/2/5
         */
        private void matchDataForCancel(PropertyDTO propertyDTO, List<JournalDTO> journalDTOSByCancel, int userId) {
            List<Integer> businessDates = journalDTOSByCancel.stream().map(JournalDTO::getBusinessDate).distinct().toList();
            // 查询作废的调账数据
            List<AdRequest> existAdjsByBusinessDates = listCancelByBusinessDate(propertyDTO.getId(), businessDates);

            if (CollectionUtils.isEmpty(existAdjsByBusinessDates)) {
                // 批量新增
                insertBatchByReportCancelData(propertyDTO, journalDTOSByCancel, userId, DateTimeUtils.nowTimestamp());
            } else {
                ArrayList<AdRequest> updateBatchList = Lists.newArrayListWithCapacity(existAdjsByBusinessDates.size());
                ArrayList<Long> deleteBatchIds = Lists.newArrayListWithCapacity(existAdjsByBusinessDates.size());

                LinkedList<JournalDTO> linkedListByJournal = new LinkedList<>(journalDTOSByCancel);

                for (AdRequest existItem : existAdjsByBusinessDates) {
                    String signByExistItem = existItem.signByCancel();

                    Map<String, List<JournalDTO>> sign2JournalDTOS = linkedListByJournal.stream().collect(groupingBy(JournalDTO::signByCancel));
                    List<JournalDTO> journalDTOSBySign = sign2JournalDTOS.get(signByExistItem);
                    if (CollectionUtils.isNotEmpty(journalDTOSBySign)) {
                        // IWC 存在，报告存在

                        JournalDTO journalDTO = journalDTOSBySign.get(0);
                        // 报告中存在
                        existItem.setAccountNumber(journalDTO.getAccountNumber());
                        updateBatchList.add(existItem);

                        // 从报告集合中删除已经匹配的数据
                        linkedListByJournal.remove(journalDTO);
                    } else {
                        // IWC 存在，报告不存在
                        // 添加到待删除的集合中
                        deleteBatchIds.add(existItem.getId());
                    }
                }

                if (CollectionUtils.isNotEmpty(linkedListByJournal)) {
                    // 批量新增
                    insertBatchByReportCancelData(propertyDTO, linkedListByJournal, userId, DateTimeUtils.nowTimestamp());
                }
                if (CollectionUtils.isNotEmpty(updateBatchList)) {
                    updateBatchById(updateBatchList);
                }
                if (CollectionUtils.isNotEmpty(deleteBatchIds)) {
                    removeByIds(deleteBatchIds);
                }
            }
        }

        /**
         * 清理非作废数据
         * 1.指定配对日期手动创建的数据，更新为未配对
         * 2.指定配对日期自动创建的数据，物理删除
         *
         * @param propertyDTO 资产DTO
         * @param dateRange   数据日期范围
         * @author lichen
         * @date 2025/12/4
         */
        private void clearNonCancelData(PropertyDTO propertyDTO, Map<String, Long> dateRange) {
            LocalDate startDate = DateTimeUtils.longToLocalDate(dateRange.get(JobParameterDTO.START_DATE), propertyDTO.getTimezone());
            LocalDate endDate = DateTimeUtils.longToLocalDate(dateRange.get(JobParameterDTO.END_DATE), propertyDTO.getTimezone());
            // 获取开始日期到结束日期的每一天
            List<LocalDate> dates = startDate.datesUntil(endDate.plusDays(1)).toList();

            // 业务日期集合，格式yyyyMMdd
            List<Integer> businessDates = dates.stream().map(date -> Integer.valueOf(DateTimeUtils.localDateToStr(date, BaseConstant.DATE_FORMAT_YYYYMMDD))).toList();
            // 查询指定业务日期已配对的调账数据
            List<AdRequest> existAdjsByBusinessDates = listNonCancelByBusinessDate(propertyDTO.getId(), businessDates);
            if (CollectionUtils.isEmpty(existAdjsByBusinessDates)) {
                return;
            }

            // 先更新指定业务日期，手动创建的的数据为未配对
            List<AdRequest> manualCreateAdjs = existAdjsByBusinessDates.stream()
                    .filter(item -> !item.getAutoCreated())
                    .map(item -> {
                        AdRequest adRequest = new AdRequest();
                        adRequest.setId(item.getId());
                        adRequest.setBusinessDate(0);
                        adRequest.setMatchTime(0L);
                        adRequest.setMatchType(0);
                        adRequest.setPmsAccount("");
                        return adRequest;
                    }).toList();
            updateBatchById(manualCreateAdjs);

            // 指定业务日期，自动创建的调账数据
            List<AdRequest> autoCreatedAdjs = existAdjsByBusinessDates.stream()
                    .filter(AdRequest::getAutoCreated).toList();

            // 按照是否被chargeback关联分组
            Map<Boolean, List<AdRequest>> requestType2Adjs = autoCreatedAdjs.stream().collect(partitioningBy(item -> item.getRequestType() == AdjTypeEnum.CHARGEBACK.getAdjType()));
            // 关联chargeback类型的调账数据，更新为手动创建、未配对
            List<AdRequest> autoCreatedChargebackAdjs = requestType2Adjs.getOrDefault(true, Collections.emptyList()).stream().map(item -> {
                AdRequest adRequest = new AdRequest();
                adRequest.setId(item.getId());
                adRequest.setAutoCreated(false);
                adRequest.setCreateTime(item.getCaseRelTime());
                adRequest.setCreateBy(item.getDutyUser());
                // 设置为未配对
                adRequest.setBusinessDate(0);
                adRequest.setMatchTime(0L);
                adRequest.setMatchType(0);
                // PMS Account清空
                adRequest.setPmsAccount("");
                return adRequest;
            }).collect(toList());
            updateBatchById(autoCreatedChargebackAdjs);

            // 未关联chargeback类型的调账数据，物理删除
            List<AdRequest> deleteAdjs = requestType2Adjs.getOrDefault(false, Collections.emptyList());
            remove(deleteAdjs);

            // 删除未配对的备份数据
            adRequestMirrorService.removeNonMatchedByIds(existAdjsByBusinessDates.stream().map(AdRequest::getId).toList());

            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                ArrayList<Long> updateIds = Lists.newArrayList();
                updateIds.addAll(manualCreateAdjs.stream().map(AdRequest::getId).collect(toList()));
                updateIds.addAll(autoCreatedChargebackAdjs.stream().map(AdRequest::getId).collect(toList()));
                adjSenderService.batchPublishSave(propertyDTO.getId(), updateIds);

                adjSenderService.publishDelete(propertyDTO.getId(), deleteAdjs);
            });
        }

        /**
         * 清理作废数据
         * 1.指定配对日期自动创建的数据，物理删除
         *
         * @param propertyDTO 资产DTO
         * @param dateRange   数据日期范围
         * @author lichen
         * @date 2026/2/5
         */
        private void clearCancelData(PropertyDTO propertyDTO, Map<String, Long> dateRange) {
            LocalDate startDate = DateTimeUtils.longToLocalDate(dateRange.get(JobParameterDTO.START_DATE), propertyDTO.getTimezone());
            LocalDate endDate = DateTimeUtils.longToLocalDate(dateRange.get(JobParameterDTO.END_DATE), propertyDTO.getTimezone());
            // 获取开始日期到结束日期的每一天
            List<LocalDate> dates = startDate.datesUntil(endDate.plusDays(1)).toList();

            // 业务日期集合，格式yyyyMMdd
            List<Integer> businessDates = dates.stream().map(date -> Integer.valueOf(DateTimeUtils.localDateToStr(date, BaseConstant.DATE_FORMAT_YYYYMMDD))).toList();
            // 查询指定业务日期已配对、作废的调账数据
            List<AdRequest> existAdjsByBusinessDates = listCancelByBusinessDate(propertyDTO.getId(), businessDates);
            if (CollectionUtils.isEmpty(existAdjsByBusinessDates)) {
                return;
            }

            // 物理删除
            removeBatchByIds(existAdjsByBusinessDates.stream().map(AdRequest::getId).toList());
        }

        /**
         * 配对数据
         *
         * @param propertyDTO 资产DTO
         * @param journalDTOS 报告中的调账数据集合
         * @param userId      操作人id
         * @author lichen
         * @date 2025/12/4
         */
        private void matchData(PropertyDTO propertyDTO, List<JournalDTO> journalDTOS, int userId) {
            long nowTimestamp = DateTimeUtils.nowTimestamp();
            // 发生变化的调账数据id集合
            HashSet<Long> changesAdjIds = Sets.newHashSetWithExpectedSize(journalDTOS.size());
            // 删除的调账数据集合
            ArrayList<AdRequest> deleteAdjs = Lists.newArrayList();

            // 查询指定业务日期的调账数据
            List<Integer> businessDates = journalDTOS.stream().map(JournalDTO::getBusinessDate).distinct().toList();
            List<AdRequest> existAdjsByBusinessDates = listNonCancelByBusinessDate(propertyDTO.getId(), businessDates);

            // 先更新指定业务日期的数据为未配对
            List<AdRequest> updateExistListByBusinessDates = existAdjsByBusinessDates.stream().map(item -> {
                AdRequest adRequest = new AdRequest();
                adRequest.setId(item.getId());
                adRequest.setBusinessDate(0);
                adRequest.setMatchTime(0L);
                adRequest.setMatchType(0);
                adRequest.setPmsAccount("");
                return adRequest;
            }).toList();
            updateBatchById(updateExistListByBusinessDates);

            // 同步更新备份的数据为未配对
            adRequestMirrorService.updateNonMatchedByIds(updateExistListByBusinessDates.stream().map(AdRequest::getId).collect(toList()));

            if (CollectionUtils.isNotEmpty(updateExistListByBusinessDates)) {
                changesAdjIds.addAll(updateExistListByBusinessDates.stream().map(AdRequest::getId).toList());
            }

            // 查询所有未配对的数据
            // 因为优先匹配创建时间更早的数据，所以查询时，按创建时间正序排序
            List<AdRequest> existUnmatchedList = listByUnmatchedOrderByCreateTime(propertyDTO.getId());
            if (CollectionUtils.isEmpty(existUnmatchedList)) {
                /*
                 * IWC 未配对的数据为空时
                 * IWC 为PMS报告创建数据，配对状态等于已配对，配对日期为当前日期
                 */

                // IWC 为PMS报告批量创建数据
                List<Long> ids = insertBatchByReportData(propertyDTO, journalDTOS, userId, nowTimestamp);

                changesAdjIds.addAll(ids);
            } else {
                /*
                 * 将报告数据转换为LinkedList，因为后续在配对过程中需要频繁从集合中间位置删除元素，
                 * LinkedList基于双向链表结构，在删除元素时只需调整前后节点的指针，时间复杂度为O(1)，
                 * 相比ArrayList的O(n)具有更好的性能表现
                 */
                LinkedList<JournalDTO> journalDTOLinkedList = new LinkedList<>(journalDTOS);

                String pmsType = propertyDTO.getPmsType();
                if (Lists.newArrayList(SystemPlatformEnum.SHI_JI.getDesc(), SystemPlatformEnum.PEP.getDesc()).contains(pmsType)) {
                    /*
                     * IWC ConfNumber非空
                     * -先按照（trxCode、adjustedDollar、confNumber）与报告数据匹配
                     * -配对失败后，再使用（guestName、roomNumber、trxCode、adjustedDollar）与报告的数据匹配
                     *
                     * IWC ConfNumber为空
                     * 按照（guestName、roomNumber、trxCode、adjustedDollar）与报告的数据匹配
                     */
                    matchDataByDifferentConditions(journalDTOLinkedList, existUnmatchedList, nowTimestamp);
                } else {
                    /*
                     * 按照（guestName、roomNumber、trxCode、adjustedDollar）与报告的数据匹配
                     */
                    matchedByRule(journalDTOLinkedList, existUnmatchedList, AdRequest.MATCH_BY_GUEST_NAME_ROOM_NUMBER_TRX_CODE_ADJUSTED_AMOUNT, nowTimestamp);
                }

                if (CollectionUtils.isNotEmpty(journalDTOLinkedList)) {
                    // IWC 为PMS报告批量创建数据
                    List<Long> ids = insertBatchByReportData(propertyDTO, journalDTOLinkedList, userId, nowTimestamp);

                    changesAdjIds.addAll(ids);
                }
                // IWC 配对成功的数据
                List<AdRequest> matchedAdjs = existUnmatchedList.stream().filter(item -> item.getBusinessDate() > 0).collect(toList());
                if (CollectionUtils.isNotEmpty(matchedAdjs)) {
                    // 批量更新
                    updateBatchById(matchedAdjs);
                    List<Long> updateIds = matchedAdjs.stream().map(AdRequest::getId).toList();
                    changesAdjIds.addAll(updateIds);

                    // 同步更新备份的数据为已配对
                    adRequestMirrorService.updateMatchedByIds(matchedAdjs);
                }

                /*
                 * 思考：未匹配的【备份】数据处理场景
                 * 1. 手动创建 且 被chargeback关联：删除备份数据
                 * 2. 手动创建 且 未被chargeback关联：没有备份数据
                 * 3. 自动创建 且 被chargeback关联：删除备份数据
                 * 4. 自动创建 且 未被chargeback关联：没有备份数据
                 * 综上所述：未配对的备份数据可以直接删除
                 */
                List<Long> existUnmatchedIds = existUnmatchedList.stream().filter(item -> item.getBusinessDate() == 0)
                        .map(AdRequest::getId)
                        .collect(toList());
                adRequestMirrorService.removeNonMatchedByIds(existUnmatchedIds);

                /*
                 * 思考：未匹配的数据处理场景
                 * 1. 手动创建 且 被chargeback关联：修改为未配对
                 * 2. 手动创建 且 未被chargeback关联：修改为未配对
                 * 3. 自动创建 且 被chargeback关联：数据更新为手动创建的Chargeback数据
                 * 4. 自动创建 且 未被chargeback关联：物理删除
                 */
                // IWC 未配对成功 且 自动创建的数据
                List<AdRequest> autoCreatedNotMatchedAdjs = existUnmatchedList.stream().filter(item -> item.getBusinessDate() == 0 && item.getAutoCreated())
                        .collect(toList());
                if (CollectionUtils.isNotEmpty(autoCreatedNotMatchedAdjs)) {
                    // 按照是否被chargeback关联分组
                    Map<Boolean, List<AdRequest>> requestType2Adjs = autoCreatedNotMatchedAdjs.stream().collect(partitioningBy(item -> item.getRequestType() == AdjTypeEnum.CHARGEBACK.getAdjType()));
                    // 被chargeback关联：数据更新为手动创建的Chargeback数据
                    List<AdRequest> autoCreatedChargebackAdjs = requestType2Adjs.getOrDefault(true, Collections.emptyList()).stream().map(item -> {
                        AdRequest adRequest = new AdRequest();
                        adRequest.setId(item.getId());
                        adRequest.setAutoCreated(false);
                        adRequest.setCreateTime(item.getCaseRelTime());
                        adRequest.setCreateBy(item.getDutyUser());
                        // 设置为未配对
                        adRequest.setBusinessDate(0);
                        adRequest.setMatchTime(0L);
                        adRequest.setMatchType(0);
                        // PMS Account清空
                        adRequest.setPmsAccount("");
                        return adRequest;
                    }).collect(toList());
                    updateBatchById(autoCreatedChargebackAdjs);
                    changesAdjIds.addAll(autoCreatedChargebackAdjs.stream().map(AdRequest::getId).collect(toList()));

                    // 未被chargeback关联：物理删除
                    List<AdRequest> autoCreatedNonChargebackAdjs = requestType2Adjs.getOrDefault(false, Collections.emptyList());
                    remove(autoCreatedNonChargebackAdjs);
                    deleteAdjs.addAll(autoCreatedNonChargebackAdjs);
                }
            }

            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                List<Long> finalChangesAdjIds = new ArrayList<>(changesAdjIds);
                adjSenderService.batchPublishSave(propertyDTO.getId(), finalChangesAdjIds);
                adjSenderService.publishDelete(propertyDTO.getId(), deleteAdjs);
            });
        }

        /**
         * IWC 按照不同的规则与报告的数据匹配
         *
         * @param journalDTOS        报告数据
         * @param existUnmatchedList IWC未配对数据
         * @param nowTimestamp       当前时间
         * @return void
         * @author lichen
         * @date 2026/1/19
         */
        private void matchDataByDifferentConditions(LinkedList<JournalDTO> journalDTOS, List<AdRequest> existUnmatchedList, long nowTimestamp) {
            // existUnmatchedList 按照ConfNumber是否为空分组
            Map<Boolean, List<AdRequest>> confNumberGroup = existUnmatchedList.stream().collect(groupingBy(item -> StringUtils.isNotBlank(item.getConfNumber())));
            List<AdRequest> existUnmatchedByNonNullConfNumberList = confNumberGroup.getOrDefault(true, Collections.emptyList());
            List<AdRequest> existUnmatchedByIsNullConfNumberList = confNumberGroup.getOrDefault(false, Collections.emptyList());

            // IWC ConfNumber非空的数据，按照（trxCode、adjustedDollar、confNumber）与报告数据匹配
            matchedByRule(journalDTOS, existUnmatchedByNonNullConfNumberList, AdRequest.MATCH_BY_TRX_CODE_ADJUSTED_AMOUNT_CONF_NUMBER, nowTimestamp);
            // 筛选出匹配成功的数据
            List<AdRequest> existUnmatchedByNonNullConfNumberMatchedList = existUnmatchedByNonNullConfNumberList.stream()
                    .filter(item -> item.getBusinessDate() > 0).toList();

            // 筛选出匹配不成功的数据
            List<AdRequest> existUnmatchedByNonNullConfNumberNotMatchedList = existUnmatchedByNonNullConfNumberList.stream()
                    .filter(item -> item.getBusinessDate() == 0).toList();
            ArrayList<AdRequest> adRequests = new ArrayList<>();
            adRequests.addAll(existUnmatchedByNonNullConfNumberNotMatchedList);
            adRequests.addAll(existUnmatchedByIsNullConfNumberList);

            // 按照（guestName、roomNumber、trxCode、adjustedDollar）与报告数据匹配
            matchedByRule(journalDTOS, adRequests, AdRequest.MATCH_BY_GUEST_NAME_ROOM_NUMBER_TRX_CODE_ADJUSTED_AMOUNT, nowTimestamp);

            // 配对结果更新到existUnmatchedList集合中
            adRequests.addAll(existUnmatchedByNonNullConfNumberMatchedList);
            existUnmatchedList.clear();
            existUnmatchedList.addAll(adRequests);
        }

        /**
         * 根据规则与报告的数据匹配
         *
         * @param journalDTOS        报告数据
         * @param existUnmatchedList IWC未配对数据
         * @param nowTimestamp       当前时间
         * @return void
         * @author lichen
         * @date 2026/1/17
         */
        private void matchedByRule(LinkedList<JournalDTO> journalDTOS, List<AdRequest> existUnmatchedList, int matchType, long nowTimestamp) {
            for (AdRequest adRequest : existUnmatchedList) {
                if (adRequest.getBusinessDate() > 0) {
                    continue;
                }
                String sign = adRequest.signByMatchType(matchType);

                // 根据签名值获取报告数据
                Map<String, List<JournalDTO>> sign2ReportList = journalDTOS.stream().collect(groupingBy(journalDTO -> journalDTO.signByMatchType(matchType)));
                List<JournalDTO> reportListBySign = sign2ReportList.get(sign);
                if (CollectionUtils.isNotEmpty(reportListBySign)) {
                    // 取第一条数据进行匹配
                    JournalDTO matchReportData = reportListBySign.get(0);

                    // 从报告数据中删除后此数据，防止重复配对
                    journalDTOS.remove(matchReportData);

                    // 设置已配对
                    adRequest.setBusinessDate(matchReportData.getBusinessDate());
                    adRequest.setMatchTime(nowTimestamp);
                    // 设置匹配类型
                    adRequest.setMatchType(matchType);
                    // 回填AccountNumber
                    adRequest.setAccountNumber(matchReportData.getAccountNumber());
                    // 回填PMS Account
                    adRequest.setPmsAccount(matchReportData.getPmsAccount());
                    if (matchType == AdRequest.MATCH_BY_GUEST_NAME_ROOM_NUMBER_TRX_CODE_ADJUSTED_AMOUNT) {
                        // 回填ConfNumber
                        String confNumber = matchReportData.getConfNumber();
                        if (StringUtils.isNotBlank(confNumber)) {
                            adRequest.setConfNumber(confNumber);
                        }
                    } else {
                        // 回填GuestName
                        String guestName = matchReportData.getGuestName();
                        if (StringUtils.isNotBlank(guestName)) {
                            adRequest.setGuestName(guestName);
                        }
                        // 回填RoomNumber
                        String roomNumber = matchReportData.getRoomNumber();
                        if (StringUtils.isNotBlank(roomNumber)) {
                            adRequest.setRoomNumber(roomNumber);
                        }
                    }
                }
            }
        }

        /**
         * IWC 为PMS报告批量创建数据
         *
         * @param propertyDTO  资产DTO
         * @param journalDTOS  报告数据
         * @param userId       用户ID
         * @param nowTimestamp 当前时间
         * @return java.util.List<java.lang.Long>
         * @author lichen
         * @date 2026/1/19
         */
        private List<Long> insertBatchByReportData(PropertyDTO propertyDTO, List<JournalDTO> journalDTOS, int userId, long nowTimestamp) {
            // 填充非空字段
            List<AdRequest> insertBatch = convertAndFillNonNullFields(propertyDTO, journalDTOS, userId);
            // 设置配对类型和配对时间
            insertBatch.forEach(adRequest -> {
                adRequest.setMatchTime(nowTimestamp);
                // 匹配类型
                if (StringUtils.isNotBlank(adRequest.getConfNumber())) {
                    adRequest.setMatchType(AdRequest.MATCH_BY_TRX_CODE_ADJUSTED_AMOUNT_CONF_NUMBER);
                } else {
                    adRequest.setMatchType(AdRequest.MATCH_BY_GUEST_NAME_ROOM_NUMBER_TRX_CODE_ADJUSTED_AMOUNT);
                }
            });
            // 设置调账编号
            setAdjNumBatch(insertBatch, propertyDTO);
            // 批量新增
            saveBatch(insertBatch);
            return insertBatch.stream().map(AdRequest::getId).toList();
        }

        /**
         * IWC 为PMS报告批量创建作废数据
         *
         * @param propertyDTO  资产DTO
         * @param journalDTOS  报告数据
         * @param userId       用户ID
         * @param nowTimestamp 当前时间
         * @author lichen
         * @date 2026/1/19
         */
        private void insertBatchByReportCancelData(PropertyDTO propertyDTO, List<JournalDTO> journalDTOS, int userId, long nowTimestamp) {
            // 填充非空字段
            List<AdRequest> insertBatch = beanMapper.mapList(journalDTOS, AdRequest.class, (journalDTO, adRequest) -> {
                adRequest.setPropertyId(propertyDTO.getId());
                adRequest.setCreateBy(userId);
                adRequest.setConfNumber(StringUtils.isBlank(journalDTO.getConfNumber()) ? "" : journalDTO.getConfNumber());
                adRequest.setMistakeBy("");
                adRequest.setChoosedReasonDetails("");
                adRequest.setComment("");
                adRequest.setCaseNumber("");
                adRequest.setAttachments("[]");
                adRequest.setApproveIndex(0);
                adRequest.setApproveLevelId(0L);
                adRequest.setFlowStatus(FlowStatusEnum.DRAFT.getFlowStatus());
                adRequest.setAutoCreated(true);
                adRequest.setStatus(AdjustmentStatusEnum.CANCEL.getStatus());
                adRequest.setIsArchive(false);
                adRequest.setRequestType(0);
                adRequest.setDutyUser(0);
                adRequest.setMatchTime(nowTimestamp);
                adRequest.setMatchType(0);
                adRequest.setIsCancel(true);
            });
            // 设置调账编号
            setAdjNumBatch(insertBatch, propertyDTO);
            // 批量新增
            saveBatch(insertBatch);
        }

        /**
         * 根据业务日期查询数据
         *
         * @param propertyId    资产id
         * @param businessDates 业务日期集合
         * @return java.util.List<cloud.wconnect.frontoffice.entity.adjustment.AdRequest>
         * @author lichen
         * @date 2025/12/5
         */
        @Override
        public List<AdRequest> listByBusinessDate(Integer propertyId, List<Integer> businessDates) {
            LambdaQueryWrapper<AdRequest> queryWrapper = new LambdaQueryWrapper<AdRequest>()
                    .eq(AdRequest::getPropertyId, propertyId)
                    .in(AdRequest::getBusinessDate, businessDates);
            return list(queryWrapper);
        }

        /**
         * 根据业务日期查询非作废的调账数据
         *
         * @param propertyId    资产id
         * @param businessDates 业务日期集合
         * @return java.util.List<cloud.wconnect.frontoffice.entity.adjustment.AdRequest>
         * @author lichen
         * @date 2026/2/5
         */
        @Override
        public List<AdRequest> listNonCancelByBusinessDate(int propertyId, List<Integer> businessDates) {
            if (CollectionUtils.isEmpty(businessDates)) return Collections.emptyList();
            LambdaQueryWrapper<AdRequest> queryWrapper = new LambdaQueryWrapper<AdRequest>()
                    .eq(AdRequest::getPropertyId, propertyId)
                    .in(AdRequest::getBusinessDate, businessDates)
                    .ne(AdRequest::getStatus, AdjustmentStatusEnum.CANCEL.getStatus());
            return list(queryWrapper);
        }

        /**
         * 根据业务日期查询作废的调账数据
         *
         * @param propertyId    资产id
         * @param businessDates 业务日期集合
         * @return java.util.List<cloud.wconnect.frontoffice.entity.adjustment.AdRequest>
         * @author lichen
         * @date 2026/2/5
         */
        @Override
        public List<AdRequest> listCancelByBusinessDate(int propertyId, List<Integer> businessDates) {
            if (CollectionUtils.isEmpty(businessDates)) {
                return Collections.emptyList();
            }
            LambdaQueryWrapper<AdRequest> queryWrapper = new LambdaQueryWrapper<AdRequest>()
                    .eq(AdRequest::getPropertyId, propertyId)
                    .in(AdRequest::getBusinessDate, businessDates)
                    .eq(AdRequest::getStatus, AdjustmentStatusEnum.CANCEL.getStatus());
            return list(queryWrapper);
        }

        /**
         * 获取未配对数据
         *
         * @param propertyId 资产id
         * @return java.util.List<cloud.wconnect.frontoffice.entity.adjustment.AdRequest>
         * @author lichen
         * @date 2025/8/16
         */
        @Override
        public List<AdRequest> listByUnmatchedOrderByCreateTime(Integer propertyId) {
            return list(new LambdaQueryWrapper<AdRequest>()
                    .eq(AdRequest::getPropertyId, propertyId)
                    .eq(AdRequest::getBusinessDate, 0)
                    .orderByAsc(AdRequest::getCreateTime));
        }

        /**
         * 根据配对字段查询匹配的调账数据
         *
         * @param propertyDTO            资产DTO
         * @param matchedAdjustmentQuery 查询条件
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.vo.adjustment.AdRequestVO>
         * @author lichen
         * @date 2025/8/16
         */
        @Override
        public List<AdRequestVO> listAdjustmentByMatchedField(PropertyDTO propertyDTO, MatchedAdjustmentQuery matchedAdjustmentQuery) {
            LambdaQueryWrapper<AdRequest> lambdaQueryWrapper = new LambdaQueryWrapper<AdRequest>()
                    .eq(AdRequest::getPropertyId, propertyDTO.getId())
                    .ne(AdRequest::getStatus, AdjustmentStatusEnum.CANCEL.getStatus());

            String confNumber = matchedAdjustmentQuery.getConfNumber();
            if (StringUtils.isNotBlank(confNumber)) {
                lambdaQueryWrapper.eq(AdRequest::getTrxCode, matchedAdjustmentQuery.getTrxCode())
                        .eq(AdRequest::getAdjustedDollar, matchedAdjustmentQuery.getAdjustedDollar())
                        .eq(AdRequest::getConfNumber, confNumber);
            } else {
                lambdaQueryWrapper.eq(AdRequest::getRoomNumber, matchedAdjustmentQuery.getRoomNumber())
                        .eq(AdRequest::getTrxCode, matchedAdjustmentQuery.getTrxCode())
                        .eq(AdRequest::getAdjustedDollar, matchedAdjustmentQuery.getAdjustedDollar());

                String guestName = matchedAdjustmentQuery.getGuestName();
                if (guestName.contains(",")) {
                    String[] names = guestName.split(",");

                    lambdaQueryWrapper.and(queryWrapper ->
                            queryWrapper.eq(AdRequest::getGuestName, names[0] + "," + names[1])
                                    .or().eq(AdRequest::getGuestName, names[1] + "," + names[0]));
                } else {
                    lambdaQueryWrapper.eq(AdRequest::getGuestName, matchedAdjustmentQuery.getGuestName());
                }
            }

            List<AdRequest> adRequests = list(lambdaQueryWrapper);
            if (CollectionUtils.isEmpty(adRequests)) return Collections.emptyList();

            //查询用户信息
            List<Integer> userIds = adRequests.stream()
                    .flatMap(re -> Stream.of(re.getCreateBy(), re.getDutyUser(), re.getUpdateBy())).distinct().collect(toList());
            Map<Integer, String> userId2Name = userInternalService.getNameMapByIds(propertyDTO.getTenantId(), userIds);

            //List<AdRequest> -> List<AdRequestVO>
            return beanMapper.mapList(adRequests, AdRequestVO.class, (re, vo) -> {
                // 设置审批单附件标识 true：有，false：无
                vo.setHaveAttachments(StringUtils.isNotEmpty(vo.getAttachments()) && !vo.getAttachments().equals("[]"));
                //设置创建人、修改人、责任人姓名
                Integer createBy = vo.getCreateBy();
                vo.setCreateName(userId2Name.get(createBy));
                Integer updateBy = vo.getUpdateBy();
                vo.setUpdateName(userId2Name.get(updateBy));
                Integer dutyUser = vo.getDutyUser();
                vo.setDutyUserName(userId2Name.get(dutyUser));
            });
        }

        /**
         * 获取指定flowStatus指定责任人的调账数量
         *
         * @param propertyId 资产id
         * @param dutyUserId 责任人id
         * @param flowStatus 流程状态
         * @return java.lang.Long
         * @author lichen
         * @date 2025/8/16
         */
        @Override
        public Long countByFlowStatusAndDutyUser(int propertyId, int dutyUserId, int flowStatus) {
            return count(new LambdaQueryWrapper<AdRequest>()
                    .eq(AdRequest::getPropertyId, propertyId)
                    .eq(AdRequest::getDutyUser, dutyUserId)
                    .eq(AdRequest::getFlowStatus, flowStatus)
                    .ne(AdRequest::getStatus, AdjustmentStatusEnum.CANCEL.getStatus()));
        }

        /**
         * 获取指定状态指定责任人的调账数量
         *
         * @param propertyId 资产id
         * @param dutyUserId 责任人id
         * @param status     状态
         * @return java.lang.Long
         * @author lichen
         * @date 2025/8/16
         */
        @Override
        public Long countByStatusAndDutyUser(int propertyId, int dutyUserId, int status) {
            return count(new LambdaQueryWrapper<AdRequest>()
                    .eq(AdRequest::getPropertyId, propertyId)
                    .eq(AdRequest::getDutyUser, dutyUserId)
                    .eq(AdRequest::getStatus, status));
        }

        /**
         * 获取相同Conf Number的调账数据
         *
         * @param propertyId 资产id
         * @param confNumber Conf Number
         * @return cloud.wconnect.frontoffice.pojo.dto.SameConfAdjDTO
         * @author lichen
         * @date 2025/8/16
         */
        @Override
        public SameConfAdjDTO getSameConfNum(int propertyId, String confNumber) {
            List<AdRequest> adjList = list(new LambdaQueryWrapper<AdRequest>()
                    .eq(AdRequest::getPropertyId, propertyId)
                    .eq(AdRequest::getConfNumber, confNumber));
            if (CollectionUtils.isEmpty(adjList)) return new SameConfAdjDTO();

            //过滤出入住日期、退房日期、备注都存在的数据
            Optional<AdRequest> allExist = adjList.stream().filter(adj -> adj.getCheckInDate() > BaseConstant.ZERO
                            && adj.getCheckOutDate() > BaseConstant.ZERO && StringUtils.isNotBlank(adj.getComment()))
                    .findFirst();
            if (allExist.isPresent()) {
                return SameConfAdjDTO.builder()
                        .checkInDate(allExist.get().getCheckInDate())
                        .checkOutDate(allExist.get().getCheckOutDate())
                        .comment(allExist.get().getComment())
                        .build();
            }

            //查询入住日期、退房日期存在
            Optional<AdRequest> checkInAndCheckOutExist = adjList.stream().filter(adj -> adj.getCheckInDate() > BaseConstant.ZERO
                    && adj.getCheckOutDate() > BaseConstant.ZERO).findFirst();
            if (checkInAndCheckOutExist.isPresent()) {
                return SameConfAdjDTO.builder()
                        .checkInDate(checkInAndCheckOutExist.get().getCheckInDate())
                        .checkOutDate(checkInAndCheckOutExist.get().getCheckOutDate())
                        .build();
            }
            //查询备注存在
            Optional<AdRequest> commentExist = adjList.stream().filter(adj -> StringUtils.isNotEmpty(adj.getComment()))
                    .findFirst();
            if (commentExist.isPresent()) {
                return SameConfAdjDTO.builder()
                        .comment(commentExist.get().getComment())
                        .build();
            }
            return new SameConfAdjDTO();
        }

        /**
         * 获取待分配的调账数据数量
         *
         * @param propertyId  资产id
         * @param loginUserId 登录用户id
         * @return java.lang.Long
         * @author lichen
         * @date 2025/8/16
         */
        @Override
        public Long countNeedsAssign(int propertyId, int loginUserId) {
            return count(new LambdaQueryWrapper<AdRequest>()
                    .eq(AdRequest::getPropertyId, propertyId)
                    .eq(AdRequest::getFlowStatus, FlowStatusEnum.DRAFT.getFlowStatus())
                    .eq(AdRequest::getDutyUser, 0)
                    //过滤掉作废的数据
                    .ne(AdRequest::getStatus, AdjustmentStatusEnum.CANCEL.getStatus()));
        }

        /**
         * 根据调账编号查询调账数据
         *
         * @param adjNumberList 调账编号集合
         * @return java.util.List<cloud.wconnect.frontoffice.entity.adjustment.AdRequest>
         * @author lichen
         * @date 2026/2/10
         */
        @Override
        public List<AdRequest> listByAdjNumbers(List<String> adjNumberList) {
            if (CollectionUtils.isEmpty(adjNumberList)) return Collections.emptyList();
            return list(new LambdaQueryWrapper<AdRequest>().in(AdRequest::getAdjNumber, adjNumberList));
        }

        /**
         * 删除调账数据
         *
         * @param adjs 调账数据集合
         * @author lichen
         * @date 2026/2/11
         */
        @Override
        public void remove(List<AdRequest> adjs) {
            if (CollectionUtils.isEmpty(adjs)) return;
            List<Long> adjIds = adjs.stream().map(AdRequest::getId).collect(toList());
            // 删除调账数据
            removeBatchByIds(adjIds);
            // 删除备注
            adRequestCommentService.removeByAdjIds(adjIds);
            // 删除操作日志
            adjOperateLogService.remove(new LambdaQueryWrapper<AdjOperateLog>()
                    .in(AdjOperateLog::getAdjId, adjIds));
            // 删除审批日志
            approveLogService.remove(new LambdaQueryWrapper<AdjApproveLog>()
                    .in(AdjApproveLog::getAdjId, adjIds));
        }

        /**
         * 转换并填充非空字段
         *
         * @param propertyDTO 资产DTO
         * @param journalDTOS 报告数据集合
         * @param userId      操作人 id
         * @return java.util.List<cloud.wconnect.frontoffice.entity.adjustment.AdRequest>
         * @author lichen
         * @date 2025/12/4
         */
        private List<AdRequest> convertAndFillNonNullFields(PropertyDTO propertyDTO, List<JournalDTO> journalDTOS, int userId) {
            // 查询PMS配置
            List<AdjPmsConfig> adjPmsConfigs = adjPmsConfigService.listByPropertyIdAndDataType(propertyDTO.getId(), AdjPmsConfig.DATA_TYPE_DRAFT);
            Map<String, AdjPmsConfig> pmsAccount2Entity = adjPmsConfigs.stream().collect(toMap(AdjPmsConfig::getPmsAccount, Function.identity()));

            return beanMapper.mapList(journalDTOS, AdRequest.class, (journalDTO, adRequest) -> {
                adRequest.setPropertyId(propertyDTO.getId());
                adRequest.setCreateBy(userId);
                adRequest.setConfNumber(StringUtils.isBlank(journalDTO.getConfNumber()) ? "" : journalDTO.getConfNumber());
                adRequest.setMistakeBy("");
                adRequest.setChoosedReasonDetails("");
                adRequest.setComment("");
                adRequest.setCaseNumber("");
                adRequest.setAttachments("[]");
                adRequest.setApproveIndex(0);
                adRequest.setApproveLevelId(0L);
                adRequest.setFlowStatus(FlowStatusEnum.DRAFT.getFlowStatus());
                adRequest.setAutoCreated(true);
                adRequest.setStatus(AdjustmentStatusEnum.UNDETERMINED.getStatus());
                adRequest.setIsArchive(false);

                //根据配置设置类型和责任人
                AdjPmsConfig adjPmsConfig = pmsAccount2Entity.get(journalDTO.getPmsAccount());
                if (Objects.nonNull(adjPmsConfig)) {
                    adRequest.setRequestType(adjPmsConfig.getRequestType());
                    adRequest.setDutyUser(adjPmsConfig.getUserId());
                } else {
                    adRequest.setRequestType(0);
                    adRequest.setDutyUser(0);
                }
            });
        }

        /**
         * 构建查询条件
         *
         * @param adRequestQuery 查询query对象
         * @param propertyId     资产id
         * @return com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<cloud.wconnect.frontoffice.entity.adjustment.AdRequest>
         * @author lichen
         * @date 2025/8/16
         */
        private LambdaQueryWrapper<AdRequest> buildQueryWrapper(AdRequestQuery adRequestQuery, int propertyId) {
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            String timezone = propertyDTO.getTimezone();

            LambdaQueryWrapper<AdRequest> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(AdRequest::getPropertyId, propertyId);
            queryWrapper.like(StringUtils.isNotBlank(adRequestQuery.getConfNumber()), AdRequest::getConfNumber, adRequestQuery.getConfNumber());
            queryWrapper.like(StringUtils.isNotBlank(adRequestQuery.getMistakeBy()), AdRequest::getMistakeBy, adRequestQuery.getMistakeBy());
            queryWrapper.like(StringUtils.isNotBlank(adRequestQuery.getGuestName()), AdRequest::getGuestName, adRequestQuery.getGuestName());
            queryWrapper.like(StringUtils.isNotBlank(adRequestQuery.getRoomNumber()), AdRequest::getRoomNumber, adRequestQuery.getRoomNumber());
            queryWrapper.eq(Objects.nonNull(adRequestQuery.getRevenueLost()), AdRequest::getRevenueLost, adRequestQuery.getRevenueLost());
            queryWrapper.and(StringUtils.isNotBlank(adRequestQuery.getTrxCode()), qw -> qw.like(AdRequest::getTrxCode, adRequestQuery.getTrxCode())
                    .or().like(AdRequest::getTrxDesc, adRequestQuery.getTrxCode()));
            //业务日期
            String businessDateBegin = adRequestQuery.getBusinessDateBegin();
            String businessDateEnd = adRequestQuery.getBusinessDateEnd();
            queryWrapper.between(StringUtils.isNotBlank(businessDateBegin) && StringUtils.isNotBlank(businessDateEnd),
                    AdRequest::getBusinessDate, businessDateBegin, businessDateEnd);
            //拼装adjusted_dollar条件
            queryWrapper.ge(Objects.nonNull(adRequestQuery.getMinDollar()), AdRequest::getAdjustedDollar, adRequestQuery.getMinDollar());
            queryWrapper.le(Objects.nonNull(adRequestQuery.getMaxDollar()), AdRequest::getAdjustedDollar, adRequestQuery.getMaxDollar());

            if (StringUtils.isNotBlank(adRequestQuery.getCheckInDateBegin()) && StringUtils.isNotBlank(adRequestQuery.getCheckInDateEnd())) {
                //入住日期
                long checkInDateBegin = DateTimeUtils.dateStrToEpochSecond(adRequestQuery.getCheckInDateBegin(), BaseConstant.DATE_FORMAT_YYYYMMDD, timezone);
                long checkInDateEnd = DateTimeUtils.dateStrToEpochSecond(adRequestQuery.getCheckInDateEnd(), BaseConstant.DATE_FORMAT_YYYYMMDD, timezone);
                queryWrapper.between(AdRequest::getCheckInDate, checkInDateBegin, checkInDateEnd);
            }
            //拼装requestType
            queryWrapper.in(CollectionUtils.isNotEmpty(adRequestQuery.getRequestTypeList()),
                    AdRequest::getRequestType, adRequestQuery.getRequestTypeList());
            //拼装查询状态
            List<Integer> queryStatusList = adRequestQuery.getQueryStatusList();
            queryWrapper.and(CollectionUtils.isNotEmpty(queryStatusList), qw -> {
                if (queryStatusList.contains(AdRequestQuery.QUERY_STATUS_DRAFT)) {
                    // 当包含 QUERY_STATUS_DRAFT 时，添加 FlowStatus = Draft 且 Status != Cancel 的条件
                    qw.nested(childQw -> childQw.eq(AdRequest::getFlowStatus, FlowStatusEnum.DRAFT.getFlowStatus())
                            .ne(AdRequest::getStatus, AdjustmentStatusEnum.CANCEL.getStatus()));
                }
                // 其他状态的条件保持不变
                if (queryStatusList.contains(AdRequestQuery.QUERY_STATUS_APPROVING)) {
                    qw.or().eq(AdRequest::getFlowStatus, FlowStatusEnum.APPROVAING.getFlowStatus());
                }
                if (queryStatusList.contains(AdRequestQuery.QUERY_STATUS_APPROVED)) {
                    qw.or().eq(AdRequest::getStatus, AdjustmentStatusEnum.VALID.getStatus());
                }
                if (queryStatusList.contains(AdRequestQuery.QUERY_STATUS_REJECTED)) {
                    qw.or().eq(AdRequest::getStatus, AdjustmentStatusEnum.INVALID.getStatus());
                }
                if (queryStatusList.contains(AdRequestQuery.QUERY_STATUS_ARCHIVED)) {
                    qw.or().eq(AdRequest::getIsArchive, true);
                }
                if (queryStatusList.contains(AdRequestQuery.QUERY_STATUS_CANCEL)) {
                    qw.or().eq(AdRequest::getStatus, AdjustmentStatusEnum.CANCEL.getStatus());
                }
            });
            //拼装choosed_reason
            queryWrapper.in(CollectionUtils.isNotEmpty(adRequestQuery.getChoosedReasonList()),
                    AdRequest::getChoosedReason, adRequestQuery.getChoosedReasonList());
            //拼装adjNumber
            String adjNumber = adRequestQuery.getAdjNumber();
            queryWrapper.and(StringUtils.isNotBlank(adjNumber),
                    qw -> qw.like(AdRequest::getAdjNumber, adjNumber).or(qw2 -> qw2.like(AdRequest::getCaseNumber, adjNumber)));
            //责任人
            queryWrapper.eq(Objects.nonNull(adRequestQuery.getDutyUser()), AdRequest::getDutyUser, adRequestQuery.getDutyUser());
            //创建日期
            if (StringUtils.isNotBlank(adRequestQuery.getCreateTimeBegin()) && StringUtils.isNotBlank(adRequestQuery.getCreateTimeEnd())) {
                //创建日期
                long createTimeBegin = DateTimeUtils.dateStrToEpochSecond(adRequestQuery.getCreateTimeBegin(), BaseConstant.DATE_FORMAT_YYYYMMDD, timezone);
                long createTimeEnd = DateTimeUtils.localDateToLong(
                        DateTimeUtils.strToLocalDate(adRequestQuery.getCreateTimeEnd(), BaseConstant.DATE_FORMAT_YYYYMMDD).plusDays(1), timezone);
                queryWrapper.ge(AdRequest::getCreateTime, createTimeBegin)
                        .lt(AdRequest::getCreateTime, createTimeEnd);
            }
            //是否配对
            if (Objects.nonNull(adRequestQuery.getIsMatch())) {
                if (adRequestQuery.getIsMatch()) {
                    //businessDate大于0表示已配对
                    queryWrapper.gt(AdRequest::getBusinessDate, 0);
                } else {
                    //businessDate等于0表示未配对
                    queryWrapper.eq(AdRequest::getBusinessDate, 0);
                }
            }
            return queryWrapper;
        }

        /**
         * 计算统计日期
         *
         * @param unit     统计单位 1：月 2：季度
         * @param span     时间跨度 n个月/季度
         * @param timezone 时区
         * @return java.util.Map<java.lang.String, java.lang.Integer>
         * @author lichen
         * @date 2023/12/11
         */
        private Map<String, LocalDate> calculatestatisticDate(Integer unit, Integer span, String timezone) {
            HashMap<String, LocalDate> statisticDate = Maps.newHashMapWithExpectedSize(2);
            LocalDate currentLocalDate = LocalDate.now(ZoneId.of(timezone));
            if (StatisticsTypeTrendQuery.MONTH == unit) {
                //当前月份的第一天往前推（n：时间跨度-1）个月
                statisticDate.put("startLocalDate", currentLocalDate.withDayOfMonth(1).minusMonths(span - 1));
                //当月的最后一天
                statisticDate.put("endLocalDate", currentLocalDate.withDayOfMonth(currentLocalDate.lengthOfMonth()));
            } else {
                //获取n个季度前的开始日期
                statisticDate.put("startLocalDate", QuarterUtil.getBeforQuarterStartDate(currentLocalDate, span));
                //获取当前季度的结束日期
                statisticDate.put("endLocalDate", QuarterUtil.getQuarterEndDate(currentLocalDate));
            }
            return statisticDate;
        }
    }