# 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();
            // 当前补位人员对应的审批人
            List<AdjOutOfOfficeConfig> substitutePersonnelForCurrentLoginUser = new ArrayList<>();
            // 当前补位人员对应的审批人所属层级下的所有审批人设置的补位人员
            List<AdjOutOfOfficeConfig> substitutePersonnelForCurrentApprovelLevel = new ArrayList<>();
            AdjApproveConfig approveConfigByUserId = approveConfigService.getByUserId(adRequest.getPropertyId(), requestType, loginUserId);
            if (Objects.isNull(approveConfigByUserId)) {
                // 查找登陆用户是否是补位审批人
                // 先找补位人员对应的有效的补位配置，再根据配置找对应的审批层级下有效的审批人，若找到了多个审批层级，则返回最小层级，若没有，则当前补位人员失效
                // 另外在汇总 adj 创建人和责任人对应的补位配置，用于判断是否跳过下个层级
                approveConfigByUserId = getApproverConfigByLoginUser(adRequest, loginUserId, substitutePersonnelForCurrentLoginUser, substitutePersonnelForCurrentApprovelLevel);
            } else {
                // 查询当前审批层下的设置的补位人员
                getCurrentApproveLevelSubstitutePersonnel(approveConfigByUserId, substitutePersonnelForCurrentApprovelLevel);
            }

            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());
                    if (checkPushNextIndex(owner, approverByApproveLevel, substitutePersonnelForCurrentApprovelLevel)) {//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 (CollectionUtils.isNotEmpty(substitutePersonnelForCurrentLoginUser)) {
                            excludeUser.addAll(substitutePersonnelForCurrentLoginUser.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 owner                  创建人和责任人
         * @param approverByApproveLevel 当前层级下的审批人
         * @param outOfOfficeConfigs     当前层级下的审批人对应的补位人
         * @return true:推送至下一个层级, false:不推送
         * @author yyt
         * @date 2026/3/27
         **/
        private boolean checkPushNextIndex(Set<Integer> owner, List<Integer> approverByApproveLevel, List<AdjOutOfOfficeConfig> outOfOfficeConfigs) {
            // 判断创建人和责任人是审批人数量一致
            if (owner.size() != new HashSet<>(approverByApproveLevel).size()) {
                return false;
            }

            if(owner.containsAll(approverByApproveLevel)) return true;

            List<Set<Integer>> result = new ArrayList<>();
            Map<Integer, Integer> substitutePersonnelByApprover = outOfOfficeConfigs.stream().collect(toMap(AdjOutOfOfficeConfig::getUpdatedBy, AdjOutOfOfficeConfig::getSubstitutePersonnelId));
            for (Integer approver : approverByApproveLevel) {
                result.add(Sets.newHashSet(approver, substitutePersonnelByApprover.get(approver)));
            }

            // 看看创建人和责任人 是否是审批人
            for (Set<Integer> integers : result) {
                boolean flag = false;
                for (Integer item : owner) {
                    if (integers.contains(item)) {
                        flag = true;
                        break;
                    }
                }
                if (!flag) {
                    return false;
                }
            }
            return true;
        }

        /**
         * 登录用户作为补位审批人员获取对应的审批人
         *
         * @param adRequest                                   adj
         * @param loginUserId                                 登陆用于
         * @param substitutePersonnelForCurrentLoginUser      登陆用户对应的补位人员配置
         * @param substitutePersonnelForCurrentApprovalLevel  当前层级下所有审批人设置的补位人员
         * @return approverConfig
         * @author yyt
         * @date 2026/3/26
         **/
        private AdjApproveConfig getApproverConfigByLoginUser(AdRequest adRequest, int loginUserId,
                                                              List<AdjOutOfOfficeConfig> substitutePersonnelForCurrentLoginUser,
                                                              List<AdjOutOfOfficeConfig> substitutePersonnelForCurrentApprovalLevel) {
            AdjApproveConfig approverConfig = null;
            Integer propertyId = adRequest.getPropertyId();
            Integer requestType = adRequest.getRequestType();
            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)) {
                        approverConfig = adjApproveConfigs.get(0);
                        List<Integer> finalApproverIds = approverIds;
                        substitutePersonnelForCurrentLoginUser.addAll(outOfOfficeConfigs.stream()
                                .filter(item -> finalApproverIds.contains(item.getUpdatedBy()))
                                .collect(toList()));

                        // 当前层级下所有审批人设置的补位人员
                        List<AdjOutOfOfficeConfig> adjOutOfOfficeConfigsForCurrentApprovalLevel =
                                adjOutOfOfficeConfigService.listEffectiveConfigForApprover(propertyId, requestType, adjApproveConfigs.stream().map(AdjApproveConfig::getUserId).toList());
                        if (CollectionUtils.isNotEmpty(adjOutOfOfficeConfigsForCurrentApprovalLevel)) {
                            substitutePersonnelForCurrentApprovalLevel.addAll(adjOutOfOfficeConfigsForCurrentApprovalLevel);
                        }
                    }
                }
            }
            return approverConfig;
        }

        /**
         * 获取当前审批层级下所有审批人设置的补位人员
         *
         * @param approveConfigByUserId                      当前审批人
         * @param substitutePersonnelForCurrentApprovalLevel 当前层级下所有审批人设置的补位人员
         * @author yyt
         * @date 2026/3/27
         **/
        private void getCurrentApproveLevelSubstitutePersonnel(AdjApproveConfig approveConfigByUserId, List<AdjOutOfOfficeConfig> substitutePersonnelForCurrentApprovalLevel) {
            // 获取当前审批层级下所有审批人设置的补位人员
            Integer propertyId = approveConfigByUserId.getPropertyId();
            List<Integer> approver = approveConfigService.listApproverByLevelId(propertyId, approveConfigByUserId.getLevelId());
            if (CollectionUtils.isEmpty(approver)) return;

            List<AdjOutOfOfficeConfig> adjOutOfOfficeConfigsForCurrentApprovalLevel =
                    adjOutOfOfficeConfigService.listEffectiveConfigForApprover(propertyId, approveConfigByUserId.getType(), approver);
            if (CollectionUtils.isNotEmpty(adjOutOfOfficeConfigsForCurrentApprovalLevel)) {
                substitutePersonnelForCurrentApprovalLevel.addAll(adjOutOfOfficeConfigsForCurrentApprovalLevel);
            }
        }

        /**
         * 审核数据
         *
         * @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());
                adRequest.setApproveIndex(BaseConstant.ZERO);
                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;
        }
    }