# AdjRequestController.java

    package cloud.wconnect.frontoffice.controller.adjustment;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.config.jxlscommand.StyleCommand;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.CommonFieldI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.exception.InternalServerErrorException;
    import cloud.wconnect.commonlib.pojo.InternalPropertyTrxCodeQuery;
    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.jsonview.Views;
    import cloud.wconnect.commonlib.pojo.dto.result.ResultDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.PropertyInternalService;
    import cloud.wconnect.commonlib.service.RoleInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.GuestNameUtil;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.frontoffice.cons.I18nConstant;
    import cloud.wconnect.frontoffice.cons.adjustment.*;
    import cloud.wconnect.frontoffice.entity.adjustment.AdRequest;
    import cloud.wconnect.frontoffice.entity.adjustment.AdRequestComment;
    import cloud.wconnect.frontoffice.pojo.dto.adjustment.*;
    import cloud.wconnect.frontoffice.pojo.group.General;
    import cloud.wconnect.frontoffice.pojo.group.Other;
    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.vo.adjustment.*;
    import cloud.wconnect.frontoffice.service.adjustment.IAdRequestCommentService;
    import cloud.wconnect.frontoffice.service.adjustment.IAdRequestService;
    import cloud.wconnect.frontoffice.service.adjustment.IAdjApproveConfigService;
    import cloud.wconnect.frontoffice.service.adjustment.IAdjApproveLogService;
    import com.fasterxml.jackson.annotation.JsonView;
    import com.google.common.collect.ImmutableMap;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Sets;
    import jakarta.servlet.http.HttpServletResponse;
    import jakarta.validation.Valid;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.jxls.builder.xls.XlsCommentAreaBuilder;
    import org.jxls.transform.poi.JxlsPoiTemplateFillerBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.i18n.LocaleContextHolder;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.util.Assert;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;

    import java.io.IOException;
    import java.io.InputStream;
    import java.math.BigDecimal;
    import java.nio.charset.StandardCharsets;
    import java.text.MessageFormat;
    import java.time.LocalDate;
    import java.time.temporal.ChronoUnit;
    import java.util.*;

    /**
     * <p>
     * 前端控制器
     * </p>
     *
     * @author lichen
     * @since 2023-03-30
     */
    @RestController
    @RequestMapping("/adjustment")
    public class AdjRequestController {

        @Autowired
        private IAdRequestService adRequestService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private IAdRequestCommentService adRequestCommentService;
        @Autowired
        private IAdjApproveLogService approveLogService;
        @Autowired
        private SessionContext sessionContext;
        @Autowired
        private RoleInternalService roleInternalService;
        @Autowired
        private IAdjApproveConfigService approveConfigService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;
        @Autowired
        private PropertyInternalService propertyInternalService;

        /**
         * 分页查询调账数据
         *
         * @param property       资产标识
         * @param adRequestQuery 调账数据查询对象
         * @return cloud.wconnect.commonlib.pojo.dto.ListPagedDTO<cloud.wconnect.frontoffice.pojo.vo.adjustment.AdRequestVO>
         * @author lichen
         * @date 2024/1/30
         */
        @ApiId(239)
        @GetMapping
        @JsonView({Views.ListView.class})
        public ListPagedDTO<AdRequestVO> page(String property, @Validated({General.class}) AdRequestQuery adRequestQuery) {
            int propertyId = sessionContext.getPropertyId(property);
            Integer tenantId = sessionContext.getTenantSymbolBO().getId();

            // 校验业务日期区间是否合法
            String businessDateBegin = adRequestQuery.getBusinessDateBegin();
            String businessDateEnd = adRequestQuery.getBusinessDateEnd();
            checkDateRange(businessDateBegin, businessDateEnd, BaseConstant.DATE_FORMAT_YYYYMMDD, null);

            // 校验入住日期区间是否合法
            String checkInDateBegin = adRequestQuery.getCheckInDateBegin();
            String checkInDateEnd = adRequestQuery.getCheckInDateEnd();
            checkDateRange(checkInDateBegin, checkInDateEnd, BaseConstant.DATE_FORMAT_YYYYMMDD, null);

            // 校验创建时间区间是否合法
            String createTimeBegin = adRequestQuery.getCreateTimeBegin();
            String createTimeEnd = adRequestQuery.getCreateTimeEnd();
            checkDateRange(createTimeBegin, createTimeEnd, BaseConstant.DATE_FORMAT_YYYYMMDD, null);

            return adRequestService.pageList(tenantId, propertyId, adRequestQuery);
        }

        /**
         * 待分配责任人的数据
         *
         * @param property        资产标识
         * @param needAssignQuery 查询条件
         * @return cloud.wconnect.commonlib.pojo.dto.ListPagedDTO<cloud.wconnect.frontoffice.pojo.vo.adjustment.AdRequestVO>
         * @author lichen
         * @date 2024/1/29
         */
        @ApiId(420)
        @GetMapping("/need-assign")
        @JsonView({Views.ListView.class})
        public ListPagedDTO<AdRequestVO> queryNeedAssign(String property, @Valid NeedAssignQuery needAssignQuery) {
            int propertyId = sessionContext.getPropertyId(property);

            // 校验日期区间是否合法
            String createTimeBegin = needAssignQuery.getCreateTimeBegin();
            String createTimeEnd = needAssignQuery.getCreateTimeEnd();
            checkDateRange(createTimeBegin, createTimeEnd, BaseConstant.DATE_FORMAT_YYYYMMDD, null);

            return adRequestService.pageNeedAssign(propertyId, needAssignQuery);
        }

        /**
         * 查询调账数据详情
         *
         * @param id       调账数据主键id
         * @param property 资产标识
         * @return cloud.wconnect.frontoffice.pojo.vo.adjustment.AdRequestVO
         * @author lichen
         * @date 2024/1/30
         */
        @ApiId(240)
        @GetMapping("/detail/{id:\\d+}")
        @JsonView({Views.DetailsView.class})
        public AdRequestVO getDetail(@PathVariable long id, String property) {
            int propertyId = sessionContext.getPropertyId(property);
            Integer tenantId = sessionContext.getTenantSymbolBO().getId();
            return adRequestService.details(tenantId, propertyId, id);
        }

        /**
         * 查询审批记录
         *
         * @param id       调账数据主键id
         * @param property 资产标识
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.vo.adjustment.AdjApproveLevelVO>
         * @author lichen
         * @date 2024/1/30
         */
        @ApiId(284)
        @GetMapping("/approveLog/{id:\\d+}")
        public List<AdjApproveLevelVO> listApproveLog(@PathVariable("id") long id, String property) {
            int propertyId = sessionContext.getPropertyId(property);
            //校验adj是否存在且归属于当前资产
            AdRequest adRequest = checkExistAndBelongProperty(id, propertyId);

            //校验requestType是否合法
            checkRequestTypeValid(adRequest);

            //查询资产
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            //查询审批记录
            return approveLogService.listApproveRecord(adRequest, propertyDTO);
        }

        /**
         * 保存调账数据
         *
         * @param id           主键id
         * @param property     资产标识
         * @param adRequestDTO 调账数据DTO
         * @return java.util.Map
         * @author lichen
         * @date 2024/1/30
         */
        @ApiId(241)
        @PostMapping("/{id:\\d+}")
        public Map<String, Long> save(@PathVariable("id") long id, String property,
                                      @Valid @RequestBody AdRequestDTO adRequestDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            int loginUserId = sessionContext.getLoginUserId();

            //Guest Name去除空格
            String guestName = adRequestDTO.getGuestName();
            adRequestDTO.setGuestName(GuestNameUtil.removeSpaces(guestName));

            //房间号去除空格
            String roomNumber = adRequestDTO.getRoomNumber();
            adRequestDTO.setRoomNumber(StringUtils.isNotBlank(roomNumber) ? roomNumber.trim() : "");

            //ConfNumber去除空格
            String confNumber = adRequestDTO.getConfNumber();
            adRequestDTO.setConfNumber(StringUtils.isNotBlank(confNumber) ? confNumber.trim() : "");

            if (id > 0) {
                AdRequest adRequest = checkExistAndBelongProperty(id, propertyId);

                //只有流程状态为Draft的数据可以修改
                if (adRequest.getFlowStatus() != FlowStatusEnum.DRAFT.getFlowStatus()) {
                    throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_STATE_CAN_OPERATE,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(FlowStatusEnum.DRAFT.getI18nCode(), AdjFieldI18nConstant.OPERATE_EDIT),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "Only draft data can be modified");
                }
                //只有状态非作废的数据可以修改
                if (adRequest.getStatus() == AdjustmentStatusEnum.CANCEL.getStatus()) {
                    throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_STATE_CANNOT_OPERATE,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(AdjustmentStatusEnum.CANCEL.getI18nCode(), AdjFieldI18nConstant.OPERATE_EDIT),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "cancel data cannot edit");
                }
                //只有当前创建人、责任人可以修改数据
                ArrayList<Integer> canUpdUsers = Lists.newArrayList(adRequest.getCreateBy(), adRequest.getDutyUser());
                //只有创建人、责任人、审批人可以修改数据
                if (!canUpdUsers.contains(loginUserId) && !approveConfigService.checkSpecificTypeApprover(propertyId, loginUserId, adRequest)) {
                    throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_USER_CAN_OPERATE,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(
                                    List.of(AdjFieldI18nConstant.CREATOR, AdjFieldI18nConstant.DUTY_USER, AdjFieldI18nConstant.APPROVAL_USER),
                                    AdjFieldI18nConstant.OPERATE_EDIT),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "Only the [creator/dutyUser/Have approver-edit permission] can modify the data");
                }

                // 校验已配对的数据，配对字段不可更改
                if(adRequest.getBusinessDate() > 0){
                    checkCannotModify(adRequest, adRequestDTO);
                }

                //非自动创建的数据才需要校验trxCode
                if (!adRequest.getAutoCreated()) {
                    checkTrxCode(propertyId, adRequestDTO.getTrxCode());
                }

                adRequestDTO.setId(id);
                adRequestDTO.setPropertyId(propertyId);

                adRequestService.update(adRequest, adRequestDTO, loginUserId);
                return ImmutableMap.of("id", id);
            } else {
                checkTrxCode(propertyId, adRequestDTO.getTrxCode());
                //新增
                adRequestDTO.setPropertyId(propertyId);
                return ImmutableMap.of("id", adRequestService.save(adRequestDTO, loginUserId));
            }
        }

        /**
         * Guest Name, Room Number, Transaction Code, Adjusted Amount不能修改
         *
         * @param adRequest    修改前的调账数据
         * @param adRequestDTO 修改后的调账数据
         * @author lichen
         * @date 2025/12/4
         */
        private void checkCannotModify(AdRequest adRequest, AdRequestDTO adRequestDTO) {
            Integer matchType = adRequest.getMatchType();
            if (AdRequest.MATCH_BY_GUEST_NAME_ROOM_NUMBER_TRX_CODE_ADJUSTED_AMOUNT == matchType) {
                if (!AdRequest.formatGuestName(adRequest.getGuestName()).equals(AdRequest.formatGuestName(adRequestDTO.getGuestName()))) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_MODIFIED,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("Guest Name"),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The [Guest Name] field cannot be modified");
                }
                if (!adRequest.getRoomNumber().equals(adRequestDTO.getRoomNumber())) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_MODIFIED,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("Room Number"),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The [Room Number] field cannot be modified");
                }
                if (!adRequest.getTrxCode().equals(adRequestDTO.getTrxCode())) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_MODIFIED,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("Transaction Code"),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The [Transaction Code] field cannot be modified");
                }
                if (adRequest.getAdjustedDollar().compareTo(adRequestDTO.getAdjustedDollar()) != 0) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_MODIFIED,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("Adjusted Dollar"),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The [Adjusted Dollar] field cannot be modified");
                }
            } else {
                if (!adRequest.getConfNumber().equals(adRequestDTO.getConfNumber())) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_MODIFIED,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("Room Number"),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The [Room Number] field cannot be modified");
                }
                if (!adRequest.getTrxCode().equals(adRequestDTO.getTrxCode())) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_MODIFIED,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("Transaction Code"),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The [Transaction Code] field cannot be modified");
                }
                if (adRequest.getAdjustedDollar().compareTo(adRequestDTO.getAdjustedDollar()) != 0) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_MODIFIED,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("Adjusted Dollar"),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The [Adjusted Dollar] field cannot be modified");
                }
            }
        }

        /**
         * 查询adjustment金额总和
         *
         * @param property       资产标识
         * @param adRequestQuery 查询条件
         * @return java.util.Map<java.lang.String, java.math.BigDecimal>
         * @author lichen
         * @date 2024/1/30
         */
        @ApiId(242)
        @GetMapping("/summary")
        public Map<String, BigDecimal> summary(String property, @Validated({Other.class}) AdRequestQuery adRequestQuery) {
            int propertyId = sessionContext.getPropertyId(property);

            // 校验业务日期区间是否合法
            String businessDateBegin = adRequestQuery.getBusinessDateBegin();
            String businessDateEnd = adRequestQuery.getBusinessDateEnd();
            checkDateRange(businessDateBegin, businessDateEnd, BaseConstant.DATE_FORMAT_YYYYMMDD, null);

            // 校验入住日期区间是否合法
            String checkInDateBegin = adRequestQuery.getCheckInDateBegin();
            String checkInDateEnd = adRequestQuery.getCheckInDateEnd();
            checkDateRange(checkInDateBegin, checkInDateEnd, BaseConstant.DATE_FORMAT_YYYYMMDD, null);

            // 校验创建时间区间是否合法
            String createTimeBegin = adRequestQuery.getCreateTimeBegin();
            String createTimeEnd = adRequestQuery.getCreateTimeEnd();
            checkDateRange(createTimeBegin, createTimeEnd, BaseConstant.DATE_FORMAT_YYYYMMDD, null);

            return adRequestService.summary(propertyId, adRequestQuery);
        }

        /**
         * 查询处理人角色的用户
         *
         * @param property 资产标识
         * @return java.util.List<cloud.wconnect.commonlib.pojo.dto.UserDTO>
         * @author lichen
         * @date 2023/5/31
         */
        @ApiId(243)
        @GetMapping("/requestBy")
        public List<UserDTO> getRequestBy(String property) {
            int propertyId = sessionContext.getPropertyId(property);
            Integer tenantId = sessionContext.getTenantSymbolBO().getId();
            return roleInternalService.getUsers(tenantId, propertyId, BaseConstant.ADJUSTMENT_HANDLE_ROLE);
        }

        /**
         * 查询comment信息
         *
         * @param id       调账数据主键Id
         * @param property 资产标识
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.dto.adjustment.AdRequestCommentDTO>
         * @author lichen
         * @date 2024/1/30
         */
        @ApiId(244)
        @GetMapping("/comment/{id:\\d+}")
        public List<AdRequestCommentVO> getComment(@PathVariable("id") long id, String property) {
            int propertyId = sessionContext.getPropertyId(property);
            Integer tenantId = sessionContext.getTenantSymbolBO().getId();
            //校验adj是否存在且归属于当前资产
            checkExistAndBelongProperty(id, propertyId);

            return adRequestCommentService.listVOByAdjustmentId(tenantId, propertyId, id);
        }

        /**
         * 保存评论数据
         *
         * @param adjId               调账数据主键id
         * @param property            资产标识
         * @param adRequestCommentDTO 备注DTO
         * @return java.util.Map
         * @author lichen
         * @date 2024/1/30
         */
        @ApiId(245)
        @PostMapping("/comment/{id:\\d+}")
        public Map saveComment(@PathVariable("id") long adjId, String property,
                               @Valid @RequestBody AdRequestCommentDTO adRequestCommentDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            //校验adj是否存在且归属于当前资产
            AdRequest adRequest = checkExistAndBelongProperty(adjId, propertyId);
            //校验requestType是否合法
            checkRequestTypeValid(adRequest);

            String attachments = adRequestCommentDTO.getAttachments();
            if (StringUtils.isBlank(attachments)) adRequestCommentDTO.setAttachments("[]");

            if (adRequestCommentDTO.getId() > 0) {
                //校验备注
                AdRequestComment oirComment = adRequestCommentService.checkExist(adRequestCommentDTO.getId());
                checkCommentBelongAdj(oirComment, adRequest.getId());
                //校验只有创建人可以修改数据
                if (oirComment.getCreatedBy() != sessionContext.getLoginUserId()) {
                    throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                            BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(), "Only the creator can modify the data");
                }
                //修改备注
                adRequestCommentService.update(oirComment, adRequestCommentDTO);
            } else {//新增
                adRequestCommentService.save(propertyId, adjId, adRequestCommentDTO, sessionContext.getLoginUserId());
            }
            return Collections.emptyMap();
        }

        /**
         * 删除备注
         *
         * @param id       adRequest id
         * @param cid      comment id
         * @param property 资产标识
         * @return {}
         * @author yyt
         * @date 2023/6/1
         */
        @ApiId(338)
        @DeleteMapping("/{id:\\d+}/comment/{cid:\\d+}")
        public Map deleteComment(@PathVariable("id") long id, @PathVariable("cid") long cid, String property) {
            int propertyId = sessionContext.getPropertyId(property);
            //校验adj是否存在且归属于当前资产
            AdRequest adRequest = checkExistAndBelongProperty(id, propertyId);

            //校验requestType是否合法
            checkRequestTypeValid(adRequest);

            //校验备注
            AdRequestComment comment = adRequestCommentService.checkExist(cid);
            checkCommentBelongAdj(comment, adRequest.getId());

            //校验只有创建人可以删除数据
            if (comment.getCreatedBy() != sessionContext.getLoginUserId()) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(), "Only the creator can delete the data");
            }

            // 删除备注
            adRequestCommentService.deleteById(comment);

            return Collections.emptyMap();
        }

        /**
         * 获取相同Conf.Num 编号的调账数据
         *
         * @param property   资产标识
         * @param confNumber
         * @return cloud.wconnect.frontoffice.pojo.dto.adjustment.SameConfAdjDTO
         * @author lichen
         * @date 2024/1/30
         */
        @ApiId(246)
        @GetMapping("/sameConfNum")
        public SameConfAdjDTO getSameConfNum(String property, @RequestParam(name = "confNumber") String confNumber) {
            int propertyId = sessionContext.getPropertyId(property);

            return adRequestService.getSameConfNum(propertyId, confNumber);
        }

        /**
         * 撤回的数据(责任人或创建人可以撤回审批中的数据)
         *
         * @param id       调账数据Id
         * @param property 资产标识
         * @param opinion  意见
         * @return java.util.Map
         * @author lichen
         * @date 2024/1/30
         */
        @ApiId(247)
        @PutMapping("/withdrawn/{id:\\d+}")
        public Map withdrawn(@PathVariable("id") long id, String property,
                             @RequestParam(name = "opinion", required = false) String opinion) {
            int propertyId = sessionContext.getPropertyId(property);
            int loginUserId = sessionContext.getLoginUserId();
            //校验adj是否存在且归属于当前资产
            AdRequest adRequest = checkExistAndBelongProperty(id, propertyId);

            //校验requestType是否合法
            checkRequestTypeValid(adRequest);

            //流程状态等于审批中时，才能撤回
            if (adRequest.getFlowStatus() != FlowStatusEnum.APPROVAING.getFlowStatus()) {
                throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_STATE_CAN_OPERATE,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(FlowStatusEnum.APPROVAING.getI18nCode(), AdjFieldI18nConstant.OPERATE_WITHDRAW),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "only approving data cann withdrawn");
            }

            List<Integer> permissionUser = Lists.newArrayList(adRequest.getCreateBy(), adRequest.getDutyUser());
            //只有创建人，责任人，拥有审批人编辑权限的人可以撤回数据
            if (!permissionUser.contains(loginUserId) && !approveConfigService.checkSpecificTypeApprover(propertyId, loginUserId, adRequest)) {
                throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_USER_CAN_OPERATE,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(
                                List.of(AdjFieldI18nConstant.CREATOR, AdjFieldI18nConstant.DUTY_USER, AdjFieldI18nConstant.APPROVAL_USER),
                                AdjFieldI18nConstant.OPERATE_WITHDRAW),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        MessageFormat.format("only [creator/dutyUser/Have approver-edit permission] can withdrawn, currentLoginUserId:{0}, permissionUserId:{1}, adjId:{2}",
                                loginUserId, permissionUser.toString(), id));
            }
            //撤回数据
            adRequestService.withdrawn(propertyId, adRequest, opinion, loginUserId);
            return Collections.EMPTY_MAP;
        }

        /**
         * 送审数据
         *
         * @param id       调账数据主键id
         * @param property 资产标识
         * @return java.util.Map
         * @author lichen
         * @date 2024/1/30
         */
        @ApiId(267)
        @PutMapping("/toApprove/{id:\\d+}")
        public Map<String, String> toApprove(@PathVariable("id") long id, String property) {
            int propertyId = sessionContext.getPropertyId(property);
            int loginUserId = sessionContext.getLoginUserId();
            //校验adj是否存在且归属于当前资产
            AdRequest adRequest = checkExistAndBelongProperty(id, propertyId);

            //chargeback 和 错误类型的adjustment不能送审
            if (AdjTypeEnum.CHARGEBACK.getAdjType() == adRequest.getRequestType()
                    || AdjTypeEnum.MISPOSTING.getAdjType() == adRequest.getRequestType()) {
                List<String> typeI18nCode = List.of(AdjTypeEnum.SALES.getI18nCode(), AdjTypeEnum.FRONT_DESK.getI18nCode(), AdjTypeEnum.ACCOUNTING.getI18nCode());
                throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.TYPE, typeI18nCode),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Adjustment Request Type illegal");
            }

            //只有流程状态等于draft的数据可以送审
            if (adRequest.getFlowStatus() != FlowStatusEnum.DRAFT.getFlowStatus()) {
                throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_STATE_CAN_OPERATE,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(FlowStatusEnum.DRAFT.getI18nCode(), AdjFieldI18nConstant.OPERATE_SUBMIT),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "only Draft data can to Approve");

            }
            //只有状态非作废的数据可以送审
            if (adRequest.getStatus() == AdjustmentStatusEnum.CANCEL.getStatus()) {
                throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_STATE_CANNOT_OPERATE,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(AdjustmentStatusEnum.CANCEL.getI18nCode(), AdjFieldI18nConstant.OPERATE_SUBMIT),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "cancel data cannot toApprove");
            }
            //只有创建人，责任人，拥有审批人编辑权限的人 可以送审
            List<Integer> canUsers = Arrays.asList(adRequest.getCreateBy(), adRequest.getDutyUser());
            if (!canUsers.contains(loginUserId) && !approveConfigService.checkSpecificTypeApprover(propertyId, loginUserId, adRequest)) {
                throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_USER_CAN_OPERATE,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(
                                List.of(AdjFieldI18nConstant.CREATOR, AdjFieldI18nConstant.DUTY_USER, AdjFieldI18nConstant.APPROVAL_USER),
                                AdjFieldI18nConstant.OPERATE_SUBMIT),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Only [creator/dutyUser/Have approver-edit permission] can submit the data");
            }
            //送审前校验必填项不能为空
            checkAdjustmentForSubmit(adRequest);
            //送审
            adRequestService.toApprove(adRequest, propertyId, loginUserId);
            return Collections.emptyMap();
        }

        /**
         * 审核调账数据(审批操作：通过，拒绝，退回)
         *
         * @param id         调账数据Id
         * @param property   资产标识
         * @param approveDTO 审批DTO
         * @return java.util.Map
         * @author lichen
         * @date 2024/1/30
         */
        @ApiId(268)
        @PutMapping("/approve/{id:\\d+}")
        public Map approve(@PathVariable("id") long id, String property,
                           @Validated @RequestBody AdjApproveDTO approveDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            //校验adj是否存在且归属于当前资产
            AdRequest adRequest = checkExistAndBelongProperty(id, propertyId);

            //校验requestType是否合法
            checkRequestTypeValid(adRequest);

            if (adRequest.getFlowStatus() != FlowStatusEnum.APPROVAING.getFlowStatus()) {
                throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_STATE_CAN_OPERATE,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(FlowStatusEnum.APPROVAING.getI18nCode(), AdjFieldI18nConstant.OPERATE_APPROVAL),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "only Approving data can Approve");
            }
            //审核数据
            adRequestService.approve(adRequest, approveDTO, propertyId);
            return Collections.EMPTY_MAP;
        }

        /**
         * 更新归档状态
         * （9：归档 10：取消归档）
         *
         * @param id         调账数据主键Id
         * @param property   资产
         * @param operateDTO 操作DTO
         * @return java.util.Map
         * @author lichen
         * @date 2024/1/29
         */
        @ApiId(269)
        @PutMapping("/{id:\\d+}")
        public Map updateArchiveStatus(@PathVariable("id") long id, String property,
                                       @RequestBody @Valid OperateDTO operateDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            //校验adj是否存在且归属于当前资产
            AdRequest adRequest = checkExistAndBelongProperty(id, propertyId);

            //校验requestType是否合法
            checkRequestTypeValid(adRequest);

            if (adRequest.getFlowStatus() != FlowStatusEnum.APPROVAL_COMPLETED.getFlowStatus()) {
                throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_STATE_CAN_OPERATE,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(
                                FlowStatusEnum.APPROVAL_COMPLETED.getI18nCode(),
                                List.of(AdjFieldI18nConstant.OPERATE_ARCHIVE, AdjFieldI18nConstant.OPERATE_CANCEL_ARCHIVE)),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "only approval completed data can archived or unarchived");
            }

            if (OperateTypeEnum.ARCHIVED.getOperateType() == operateDTO.getType()) {
                if (adRequest.getIsArchive()) {
                    throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_STATE_CAN_OPERATE,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(I18nConstant.UNARCHIVED, AdjFieldI18nConstant.OPERATE_ARCHIVE),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "only unarchived data can archived");
                }
            } else {//取消归档
                if (!adRequest.getIsArchive()) {
                    throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_STATE_CAN_OPERATE,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(I18nConstant.ARCHIVED, AdjFieldI18nConstant.OPERATE_CANCEL_ARCHIVE),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "only archived data can unarchived");
                }
            }
            adRequestService.updateArchiveStatus(adRequest, operateDTO, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 物理删除
         *
         * @param property 资产
         * @param ids      调账数据主键Id集合
         * @return java.util.Map
         * @author lichen
         * @date 2024/1/29
         */
        @ApiId(1020)
        @DeleteMapping("/cancel")
        public Map remove(String property, @RequestParam List<Long> ids) {
            int propertyId = sessionContext.getPropertyId(property);

            Assert.isTrue(CollectionUtils.isNotEmpty(ids), "ids can not be empty");
            //校验ids是否重复
            if (Sets.newHashSet(ids).size() != ids.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("ids"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "ids cannot be repeat");
            }
            //校验是否存在
            List<AdRequest> adRequests = adRequestService.listByIds(ids);
            if (adRequests.size() != ids.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The adj does not exist, ids = " + ids);
            }
            //校验chargeback类型的adj不能删除
            if (adRequests.stream().anyMatch(adj -> AdjTypeEnum.CHARGEBACK.getAdjType() == adj.getRequestType())) {
                ArrayList<String> typeList = Lists.newArrayList(AdjTypeEnum.SALES.getI18nCode(),
                        AdjTypeEnum.FRONT_DESK.getI18nCode(), AdjTypeEnum.ACCOUNTING.getI18nCode(),
                        AdjTypeEnum.MISPOSTING.getI18nCode());
                throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.TYPE, typeList),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Adjustment Request Type illegal");

            }
            //校验审批中的不能删除
            if (adRequests.stream().anyMatch(adj -> FlowStatusEnum.APPROVAING.getFlowStatus() == adj.getFlowStatus())) {
                throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_STATE_CANNOT_OPERATE,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(FlowStatusEnum.APPROVAING.getI18nCode(), AdjFieldI18nConstant.OPERATE_DELETE),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "approving data cannot cancel");

            }
            //校验是否归属于当前资产
            if (adRequests.stream().anyMatch(adj -> !adj.getPropertyId().equals(propertyId))) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        MessageFormat.format("The adj not belong currentProperty, propertyId:{0}, ids:{1}", propertyId, ids));
            }
            //删除adjustment
            adRequestService.remove(adRequests, propertyId);
            return Collections.emptyMap();
        }

        /**
         * 导出调账数据
         *
         * @param property        资产标识
         * @param createTimeBegin 开始时间
         * @param createTimeEnd   结束时间
         * @param response        响应对象
         * @author lichen
         * @date 2024/1/30
         */
        @ApiId(270)
        @GetMapping("/export")
        public void export(String property, @RequestParam String createTimeBegin,
                           @RequestParam String createTimeEnd, HttpServletResponse response) {
            int propertyId = sessionContext.getPropertyId(property);
            //查询资产信息
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            // 校验日期区间
            checkDateRange(createTimeBegin, createTimeEnd, BaseConstant.DATE_FORMAT_YYYYMMDD, 31);
            //当前上下文语言
            Locale currentThreadLocale = LocaleContextHolder.getLocale();
            //查询数据
            ExportVO exportVO = adRequestService.export(propertyDTO, currentThreadLocale, createTimeBegin, createTimeEnd);
            //模板名称
            String templateFileName = StringUtils.join("tmpl-xls/adjustment_tmpl_", LocaleContextHolder.getLocale(), ".xlsx");
            //导出Excel
            try (InputStream is = new ClassPathResource(templateFileName).getInputStream()) {
                HashMap<String, Object> data = new HashMap<>();
                data.put("totalAmount", exportVO.getTotalAmount());
                data.put("adjs", exportVO.getAdjList());

                String fileName = property + "_Adjustments_" + createTimeBegin + "-" + createTimeEnd + ".xlsx";
                fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
                response.setContentType("application/vnd.ms-excel");
                response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
                response.setHeader("Content-Disposition", String.join("", "attachment; filename=", fileName));

                XlsCommentAreaBuilder xlsCommentAreaBuilder = new XlsCommentAreaBuilder();
                xlsCommentAreaBuilder.addCommandMapping(StyleCommand.NAME, StyleCommand.class);

                JxlsPoiTemplateFillerBuilder.newInstance()
                        .withAreaBuilder(xlsCommentAreaBuilder)
                        .withTemplate(is)
                        .buildAndFill(data, response::getOutputStream);
            } catch (IOException e) {
                throw new InternalServerErrorException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(), BaseErrorCodeEnum.SYSTEM_ERROR.getCode(),
                        "IO exception occured when exporting adj excel file. property Id: " + propertyDTO.getId() + ", createTimeBegin: " + createTimeBegin + ", createTimeEnd: " + createTimeEnd, e);
            }
        }

        /**
         * Draft数据分配责任人
         *
         * @param property      资产标识
         * @param editRequestBy 编辑责任人的DTO
         * @return java.util.Map
         * @author lichen
         * @date 2024/1/30
         */
        @ApiId(335)
        @PostMapping("/assign")
        public Map assign(String property, @Valid @RequestBody EditRequestBy editRequestBy) {
            int propertyId = sessionContext.getPropertyId(property);
            //adjustment主键id集合
            List<Long> ids = editRequestBy.getIds();
            //查询adj数据
            List<AdRequest> adRequests = adRequestService.listByIds(ids);
            //判断adj数据是否存在
            if (CollectionUtils.isEmpty(adRequests)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "Adjustment Requests not found, ids = " + ids);
            }
            //判断adj是否归属与当前资产
            if (adRequests.stream().anyMatch(adj -> !adj.getPropertyId().equals(propertyId))) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        "The adjustment is not affiliation to property:" + propertyId);
            }
            //只有Draft的数据可以分配责任人
            if (adRequests.stream().anyMatch(adj -> adj.getFlowStatus() != FlowStatusEnum.DRAFT.getFlowStatus())) {
                throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_STATE_CAN_OPERATE,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(FlowStatusEnum.DRAFT.getI18nCode(), AdjFieldI18nConstant.OPERATE_ASSIGN),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Only draft data can edit the [request By]");
            }
            //只有状态非作废的数据可以分配
            if (adRequests.stream().anyMatch(adj -> adj.getStatus() == AdjustmentStatusEnum.CANCEL.getStatus())) {
                throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_STATE_CANNOT_OPERATE,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(AdjustmentStatusEnum.CANCEL.getI18nCode(), AdjFieldI18nConstant.OPERATE_ASSIGN),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "cancel data cannot assign");
            }
            //更新requestBy
            adRequestService.updateRequestBy(propertyId, adRequests, editRequestBy, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 查询adjustment操作日志
         *
         * @param property 资产标识
         * @param id       adjustment主键Id
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.vo.adjustment.OperateLogVO>
         * @author lichen
         * @date 2024/1/30
         */
        @ApiId(1021)
        @GetMapping("/{id:\\d+}/operate-log")
        public List<OperateLogVO> listOperateLog(String property, @PathVariable Long id) {
            int propertyId = sessionContext.getPropertyId(property);
            //校验调账数据是否存在 且 归属于当前资产
            AdRequest adRequest = checkExistAndBelongProperty(id, propertyId);
            //校验requestType是否合法
            checkRequestTypeValid(adRequest);
            return adRequestService.listOperateLog(propertyId, adRequest);
        }

        /**
         * 查询匹配的数据
         *
         * @param property               资产标识
         * @param matchedAdjustmentQuery 查询条件
         * @return cloud.wconnect.commonlib.pojo.dto.result.ResultDTO<java.util.List < cloud.wconnect.frontoffice.pojo.vo.adjustment.AdRequestVO>>
         * @author lichen
         * @date 2025/12/4
         */
        @ApiId(1659)
        @GetMapping("/matched")
        public ResultDTO<List<AdRequestVO>> listAdjustmentByMatchedField(String property, @Valid MatchedAdjustmentQuery matchedAdjustmentQuery) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            // 客人姓名去除空格
            String guestName = matchedAdjustmentQuery.getGuestName();
            matchedAdjustmentQuery.setGuestName(GuestNameUtil.removeSpaces(guestName));

            // 房间号去除空格
            String roomNumber = matchedAdjustmentQuery.getRoomNumber();
            matchedAdjustmentQuery.setRoomNumber(StringUtils.isBlank(roomNumber) ? "" : roomNumber.trim());

            // 交易码去除空格
            matchedAdjustmentQuery.setTrxCode(matchedAdjustmentQuery.getTrxCode().trim());

            // 确认编号去除空格
            String confNumber = matchedAdjustmentQuery.getConfNumber();
            matchedAdjustmentQuery.setConfNumber(StringUtils.isBlank(confNumber) ? "" : confNumber.trim());

            List<AdRequestVO> voList = adRequestService.listAdjustmentByMatchedField(propertyDTO, matchedAdjustmentQuery);
            return ResultDTO.success(voList);
        }

        /**
         * 校验调账数据是否存在 且 归属于当前资产
         *
         * @param id         主键id
         * @param propertyId 资产id
         * @return cloud.wconnect.frontoffice.entity.adjustment.AdRequest
         * @author lichen
         * @date 2024/1/30
         */
        private AdRequest checkExistAndBelongProperty(Long id, Integer propertyId) {
            AdRequest adRequest = adRequestService.checkExist(id);
            adRequestService.checkBelong(adRequest, propertyId);
            return adRequest;
        }

        /**
         * 送审前校验必填项不能为空
         *
         * @param adRequest 调账数据
         * @author lichen
         * @date 2024/1/30
         */
        private void checkAdjustmentForSubmit(AdRequest adRequest) {
            /*
             * 使用trxCode、adjustedDollar、confNumber配对时，校验GuestName 和 RoomNumber不能为空
             */
            if(adRequest.getMatchType() == AdRequest.MATCH_BY_TRX_CODE_ADJUSTED_AMOUNT_CONF_NUMBER){
                if (StringUtils.isBlank(adRequest.getGuestName())) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(AdjColumnEnum.GUEST_NAME.getI18nCode()),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "adjustment, guest name cannot be null");
                }
                if (StringUtils.isBlank(adRequest.getRoomNumber())) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(AdjColumnEnum.ROOM_NUMBER.getI18nCode()),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "adjustment, room number cannot be null");
                }
            }

            if (Objects.isNull(adRequest.getDutyUser()) || adRequest.getDutyUser() == BaseConstant.ZERO) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(AdjColumnEnum.REQUESTED_BY.getI18nCode()),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "adjustment, request by cannot be null");
            }
            if (Objects.isNull(adRequest.getCheckInDate()) || adRequest.getCheckInDate() == BaseConstant.ZERO) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(AdjColumnEnum.CHECK_IN_DATE.getI18nCode()),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "adjustment, check-in date cannot be null");
            }
            if (Objects.isNull(adRequest.getCheckOutDate()) || adRequest.getCheckOutDate() == BaseConstant.ZERO) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(AdjColumnEnum.CHECK_OUT_DATE.getI18nCode()),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "adjustment, check-out date cannot be null");
            }
            if (Objects.nonNull(adRequest.getMistakeType()) && adRequest.getMistakeType() != MistakeTypeEnum.NULL.getType()) {
                if (StringUtils.isBlank(adRequest.getMistakeBy())) {
                    if (adRequest.getMistakeType() == MistakeTypeEnum.NA.getType()) {
                        throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                                i18nMessageUtils.getErrorParamMapNeedTranslate(AdjColumnEnum.PLEASE_EXPLAIN.getI18nCode()),
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "adjustment, please explain cannot be null");
                    } else {
                        throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                                i18nMessageUtils.getErrorParamMapNeedTranslate(AdjColumnEnum.NAME.getI18nCode()),
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "adjustment, name cannot be null");
                    }
                }
            }
            if (Objects.isNull(adRequest.getAdjustedDollar())) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(AdjColumnEnum.ADJUSTED_DOLLAR.getI18nCode()),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "adjustment, Adjusted amount (before-tax) cannot be null");
            }
            if (Objects.isNull(adRequest.getRevenueLost())) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(AdjColumnEnum.REVENUE_LOSS.getI18nCode()),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "adjustment, Revenue loss cannot be null");
            }
            if (StringUtils.isBlank(adRequest.getTrxCode())) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(AdjColumnEnum.TRANSACTION_CODE.getI18nCode()),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "adjustment, Transaction code cannot be null");
            }
        }

        /**
         * 校验调账类型是否合法
         *
         * @param adRequest 调账数据
         * @author lichen
         * @date 2023/9/26
         */
        private void checkRequestTypeValid(AdRequest adRequest) {
            ArrayList<String> typeList = Lists.newArrayList(AdjTypeEnum.SALES.getI18nCode(),
                    AdjTypeEnum.FRONT_DESK.getI18nCode(), AdjTypeEnum.ACCOUNTING.getI18nCode(),
                    AdjTypeEnum.MISPOSTING.getI18nCode());
            if (AdjTypeEnum.CHARGEBACK.getAdjType() == adRequest.getRequestType()) {
                throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.TYPE, typeList),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Adjustment Request Type illegal");
            }
        }

        /**
         * 校验备注是否属于当前调账数据
         *
         * @param comment 备注
         * @param adjId   调账数据Id
         * @author lichen
         * @date 2023/9/27
         */
        private void checkCommentBelongAdj(AdRequestComment comment, Long adjId) {
            if (!comment.getArId().equals(adjId)) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        MessageFormat.format("the comment is not belong to the current adRequest,commentId:{0},commentAdRequestId:{1},adRequestId:{2}",
                                comment.getId(), comment.getArId(), adjId));
            }
        }

        /**
         * 校验交易代码是否合法
         *
         * @param propertyId 资产id
         * @param trxCode    交易代码
         * @author lichen
         * @date 2023/9/27
         */
        private void checkTrxCode(int propertyId, String trxCode) {
            Boolean exist = propertyInternalService.checkTrxCodeExist(new InternalPropertyTrxCodeQuery(propertyId, List.of(trxCode)));
            if (!exist) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_INVALID,
                        i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.TRX_CODE),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Trx code is invalid.");
            }
        }

        /**
         * 校验日期区间是否合法
         *
         * @param startDate   开始日期
         * @param endDate     结束日期
         * @param format      时间格式
         * @param maxInterval 最大时间间隔
         * @author lichen
         * @date 2025/12/4
         */
        private void checkDateRange(String startDate, String endDate, String format, Integer maxInterval) {
            if (StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate)) {
                LocalDate startLocalDate = null;
                try {
                    startLocalDate = DateTimeUtils.strToLocalDate(startDate, format);
                } catch (Exception e) {
                    throw new BadRequestException(CommonErrorI18nConstant.FORMAT_ERROR,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.START_DATE),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "start date is invalid.");
                }

                LocalDate endLocalDate = null;
                try {
                    endLocalDate = DateTimeUtils.strToLocalDate(endDate, format);
                } catch (Exception e) {
                    throw new BadRequestException(CommonErrorI18nConstant.FORMAT_ERROR,
                            i18nMessageUtils.getErrorParamMapNeedTranslate(CommonFieldI18nConstant.END_DATE),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "end date is invalid.");
                }

                if (startLocalDate.isAfter(endLocalDate)) {
                    throw new BadRequestException(CommonErrorI18nConstant.START_DATE_CANNOT_GREATER_THAN_END_DATE,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The start date cannot be later than the end date");
                }

                if (maxInterval != null && ChronoUnit.DAYS.between(startLocalDate, endLocalDate) > maxInterval) {
                    throw new BadRequestException(CommonErrorI18nConstant.SELECT_DATE_LIMIT,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate(maxInterval),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The selected date exceeds the limit");
                }
            }
        }

    }