# CbCaseActionResultController.java

    package cloud.wconnect.finance.controller.chargeback;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.SystemPlatformEnum;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.result.ResultDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.GuestNameUtil;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.finance.entity.chargeback.CbAdj;
    import cloud.wconnect.finance.entity.chargeback.CbAdjComment;
    import cloud.wconnect.finance.entity.chargeback.CbCase;
    import cloud.wconnect.finance.entity.chargeback.CbCaseIwcAdRequest;
    import cloud.wconnect.finance.pojo.bo.chargeback.AdjDisputeAmountBO;
    import cloud.wconnect.finance.pojo.dto.chargeback.*;
    import cloud.wconnect.finance.pojo.query.chargeback.ExistSameAdjQuery;
    import cloud.wconnect.finance.service.chargeback.*;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.ImmutableMap;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Sets;
    import jakarta.validation.Valid;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;

    import java.math.BigDecimal;
    import java.util.*;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * 控制层: Case Action Result
     *
     * @author yyt
     * 2023/9/1
     */
    @Validated
    @RestController
    @RequestMapping("/charge-back")
    public class CbCaseActionResultController {

        @Lazy
        @Autowired
        private ICbCaseService cbCaseService;
        @Autowired
        private SessionContext sessionContext;
        @Autowired
        private ICbCaseActionService cbCaseActionService;
        @Autowired
        private ICbAdjService cbAdjService;
        @Autowired
        private ICbAdjCommentService cbAdjCommentService;
        @Autowired
        private ICbCaseResultService cbCaseResultService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;
        @Autowired
        private ICbCaseIwcAdRequestService cbCaseIwcAdRequestService;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;

        /**
         * 酒店 / 财务选择 action
         *
         * @param id                case id
         * @param property          资产标识
         * @param saveCaseActionDTO case action
         * @return {}
         * @author yyt
         * @date 2023/9/1
         */
        @ApiId(571)
        @PostMapping("/{id:\\d+}/action")
        public Map chooseAction(@PathVariable long id, String property, @Valid @RequestBody SaveCaseActionDTO saveCaseActionDTO) {
            CbCase cs = cbCaseService.checkExist(id);
            checkBelong(cs, sessionContext.getPropertyId(property));

            cbCaseActionService.save(cs, saveCaseActionDTO);

            return Collections.emptyMap();
        }

        /**
         * 新增 adj
         *
         * @param id          case id
         * @param property    资产标识
         * @param saveAdjDTOS adj 信息
         * @return {}
         * @author yyt
         * @date 2023/9/1
         */
        @ApiId(572)
        @PostMapping("/{id:\\d+}/adj")
        public Map saveAdj(@PathVariable long id, String property, @RequestBody List<@Valid SaveAdjDTO> saveAdjDTOS) {
            Integer propertyId = sessionContext.getPropertyId(property);
            CbCase cs = cbCaseService.checkExist(id);
            checkBelong(cs, propertyId);

            // 校验adj信息，不能为空
            if (CollectionUtils.isEmpty(saveAdjDTOS)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("adjs"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The adjs cannot be null");
            }

            // 校验adj信息的trxCode和disputeAmount是否和Case一致
            checkTrxCodeAndDisputeAmount(cs, saveAdjDTOS);

            // 绑定的Adj Number编号
            List<String> bindAdjNumbers = saveAdjDTOS.stream().filter(item -> item.getType() == CbAdj.TYPE_BIND)
                    .map(SaveAdjDTO::getAdjNumber).collect(Collectors.toList());

            // 校验不能重复
            if (bindAdjNumbers.size() != Sets.newHashSet(bindAdjNumbers).size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("bindAdjNumbers"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The bindAdjNumbers cannot be repeat");
            }

            // 校验是否存在
            List<CbCaseIwcAdRequest> cbCaseIwcAdRequests = cbCaseIwcAdRequestService.checkExist(bindAdjNumbers);
            // 校验是否归属于当前资产
            cbCaseIwcAdRequestService.checkBelong(cbCaseIwcAdRequests, propertyId);
            // 校验是否已经绑定Chargeback case
            cbCaseIwcAdRequestService.checkAlreadyBind(cbCaseIwcAdRequests, cs.getCaseNumber());

            cbAdjService.save(cs, saveAdjDTOS, cbCaseIwcAdRequests, sessionContext.getLoginUserId());

            return Collections.emptyMap();
        }

        /**
         * 校验adj信息的trxCode和disputeAmount是否和Case一致
         *
         * @param cs          case
         * @param saveAdjDTOS adj 信息集合
         * @return void
         * @author lichen
         * @date 2026/2/10
         */
        private void checkTrxCodeAndDisputeAmount(CbCase cs, List<SaveAdjDTO> saveAdjDTOS) {
            // 获取case的trxCode和纠纷金额信息
            List<AdjDisputeAmountBO> adjDisputeAmountBOS = JsonUtils.convertStringToList(objectMapper, cs.getDisputeAmountItems(), AdjDisputeAmountBO.class);
            ArrayList<String> trxCodeAndDisputeAmountsByCs = Lists.newArrayListWithCapacity(adjDisputeAmountBOS.size());
            for (AdjDisputeAmountBO adjDisputeAmountBO : adjDisputeAmountBOS) {
                BigDecimal bigDecimal = adjDisputeAmountBO.getAmount().setScale(BaseConstant.DATA_PRECISION, BigDecimal.ROUND_HALF_UP);
                trxCodeAndDisputeAmountsByCs.add(adjDisputeAmountBO.getTrxCode() + "_" + bigDecimal.toString());
            }

            // 获取adj的trxCode和纠纷金额信息
            List<String> trxCodeAndDisputeAmountsByDTO = saveAdjDTOS.stream().map(item ->
                            item.getTrxCode() + "_" + item.getDisputeAmount().setScale(BaseConstant.DATA_PRECISION, BigDecimal.ROUND_HALF_UP).toString())
                    .toList();

            if (!CollectionUtils.isEqualCollection(trxCodeAndDisputeAmountsByCs, trxCodeAndDisputeAmountsByDTO)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_MODIFIED,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("trxCodeAndDisputeAmount"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The trxCodeAndDisputeAmount cannot be modify");
            }
        }

        /**
         * 修改 adj
         *
         * @param id           case id
         * @param property     资产标识
         * @param updateAdjDTO adj 信息集合
         * @return {}
         * @author yyt
         * @date 2023/9/1
         */
        @ApiId(1696)
        @PutMapping("/{id:\\d+}/adj")
        public ResultDTO<Void> updateAdj(@PathVariable long id, String property, @Valid @RequestBody UpdateAdjDTO updateAdjDTO) {
            List<SaveAdjDTO> saveCbAdjs = updateAdjDTO.getSaveCbAdjs();
            // 校验cbAdjId不能为null
            if(saveCbAdjs.stream().anyMatch(item -> Objects.isNull(item.getCbAdjId()))){
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("cbAdjId"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The cbAdjId cannot be null");
            }

            Integer propertyId = sessionContext.getPropertyId(property);
            CbCase cs = cbCaseService.checkExist(id);
            checkBelong(cs, propertyId);

            // 校验adj信息的trxCode和disputeAmount是否和Case一致
            checkTrxCodeAndDisputeAmount(cs, saveCbAdjs);

            List<SaveAdjDTO> updateAdjDTOS = saveCbAdjs.stream().filter(item -> item.getCbAdjId() > 0).collect(Collectors.toList());
            Set<Long> updateCbAdjIds = updateAdjDTOS.stream().map(SaveAdjDTO::getCbAdjId).collect(Collectors.toSet());
            // 校验修改的CbAdj不能重复
            if (updateCbAdjIds.size() != updateAdjDTOS.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("updateCbAdjIds"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The updateCbAdjIds cannot be repeat");
            }

            ArrayList<Long> allCbAdjIds = Lists.newArrayList(updateCbAdjIds);
            allCbAdjIds.addAll(updateAdjDTO.getDelCbAdjIds());

            // 校验CbAdj是否存在
            List<CbAdj> cbAdjs = cbAdjService.checkExist(allCbAdjIds);
            // 校验修改的CbAdj是否归属于当前资产
            cbAdjService.checkBelong(propertyId, cbAdjs);
            // 校验修改的CbAdj是否归属于当前Case
            cbAdjService.checkBelongCase(cs.getId(), cbAdjs);

            Map<Long, CbAdj> cbAdjId2CbAdj = cbAdjs.stream().collect(Collectors.toMap(CbAdj::getId, Function.identity()));
            Map<Long, SaveAdjDTO> cbAdjId2DTO = updateAdjDTOS.stream().collect(Collectors.toMap(SaveAdjDTO::getCbAdjId, Function.identity()));
            for (Long updateCbAdjId : updateCbAdjIds) {
                CbAdj before = cbAdjId2CbAdj.get(updateCbAdjId);
                SaveAdjDTO after = cbAdjId2DTO.get(updateCbAdjId);
                // 校验AdjNumber不能修改
                if (!before.getAdjNumber().equals(after.getAdjNumber())) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_MODIFIED,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("adjNumber"),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The adjNumber cannot be modify");
                }
            }

            // 绑定的Adj Number编号
            List<SaveAdjDTO> bindAdjDTOS = saveCbAdjs.stream()
                    .filter(item -> item.getType() == CbAdj.TYPE_BIND)
                    .collect(Collectors.toList());
            List<String> bindAdjNumbers = bindAdjDTOS.stream().map(SaveAdjDTO::getAdjNumber).collect(Collectors.toList());
            // 校验不能重复
            if (bindAdjNumbers.size() != Sets.newHashSet(bindAdjNumbers).size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("bindAdjNumbers"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The bindAdjNumbers cannot be repeat");
            }
            // 校验是否存在
            List<CbCaseIwcAdRequest> cbCaseIwcAdRequests = cbCaseIwcAdRequestService.checkExist(bindAdjNumbers);
            // 校验是否归属于当前资产
            cbCaseIwcAdRequestService.checkBelong(cbCaseIwcAdRequests, propertyId);
            // 校验是否已经绑定Chargeback case
            cbCaseIwcAdRequestService.checkAlreadyBind(cbCaseIwcAdRequests,cs.getCaseNumber());

            // 修改
            cbAdjService.update(cs, updateAdjDTO, cbAdjs, cbCaseIwcAdRequests, sessionContext.getLoginUserId());

            return ResultDTO.success();
        }

        /**
         * 查看 adj 详情
         *
         * @param id       case id
         * @param property 资产标识
         * @return adj 详情
         * @author yyt
         * @date 2023/9/2
         */
        @ApiId(573)
        @GetMapping("/{id:\\d+}/adj")
        public List<CbAdjDTO> getAdjDetail(@PathVariable long id, String property) {
            int propertyId = sessionContext.getPropertyId(property);
            CbCase cs = cbCaseService.checkExist(id);
            checkBelong(cs, propertyId);

            return cbAdjService.getDetail(propertyId, cs.getId());
        }

        /**
         * 删除 adj
         *
         * @param id       case id
         * @param property 资产标识
         * @return {}
         * @author yyt
         * @date 2023/9/4
         */
        @ApiId(574)
        @DeleteMapping("/{id:\\d+}/adj")
        public Map deleteAdj(@PathVariable long id, String property) {
            Integer propertyId = sessionContext.getPropertyId(property);
            CbCase cs = cbCaseService.checkExist(id);
            checkBelong(cs, propertyId);

            cbAdjService.deleteAdj(cs);

            return Collections.emptyMap();
        }

        /**
         * 新增、修改评论
         *
         * @param id                case id
         * @param adjId             adj id
         * @param commentId         comment id
         * @param saveAdjCommentDTO 评论内容
         * @return {}
         * @author yyt
         * @date 2023/9/4
         */
        @ApiId(633)
        @PostMapping("/{id}/adj/{adjId}/comment/{commentId}")
        public Map saveAdjComment(@PathVariable long id, @PathVariable long adjId, @PathVariable long commentId,
                                  String property, @Valid @RequestBody SaveAdjCommentDTO saveAdjCommentDTO) {
            Integer propertyId = sessionContext.getPropertyId(property);
            CbCase cs = cbCaseService.checkExist(id);
            checkBelong(cs, propertyId);

            CbAdj cbAdj = cbAdjService.checkExist(adjId);
            checkBelong(cbAdj, propertyId);

            int userId = sessionContext.getLoginUserId();
            if (commentId == BaseConstant.ZERO) {
                Long insertId = cbAdjCommentService.save(propertyId, cbAdj, saveAdjCommentDTO, userId);
                return ImmutableMap.of("id", insertId);
            } else {
                CbAdjComment comment = cbAdjCommentService.checkExist(commentId);
                checkBelong(comment, propertyId);
                if (comment.getCreatedBy() != userId) {
                    throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                            BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(), "update cbAdjComment fail the login user is not the createdBy;" +
                            "id:" + id + ",createdBy:" + comment.getCreatedBy() + ",loginUser:" + userId);
                }
                cbAdjCommentService.update(comment, saveAdjCommentDTO, cbAdj);
                return ImmutableMap.of("id", commentId);
            }
        }

        /**
         * 删除 adj 评论
         *
         * @param id        case id
         * @param adjId     adj id
         * @param commentId comment id
         * @return {}
         * @author yyt
         * @date 2023/9/16
         */
        @ApiId(685)
        @DeleteMapping("/{id}/adj/{adjId}/comment/{commentId}")
        public Map deleteAdjComment(@PathVariable long id, @PathVariable long adjId, @PathVariable long commentId, String property) {
            Integer propertyId = sessionContext.getPropertyId(property);
            CbCase cs = cbCaseService.checkExist(id);
            checkBelong(cs, propertyId);

            CbAdj cbAdj = cbAdjService.checkExist(adjId);
            checkBelong(cbAdj, propertyId);

            CbAdjComment comment = cbAdjCommentService.checkExist(commentId);
            checkBelong(comment, propertyId);

            int userId = sessionContext.getLoginUserId();
            if (comment.getCreatedBy() != userId) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(), "delete cbAdjComment fail the login user is not the createdBy;" +
                        "id:" + id + ",createdBy:" + comment.getCreatedBy() + ",loginUser:" + userId);
            }

            cbAdjCommentService.delete(comment, cbAdj);
            return Collections.emptyMap();
        }

        /**
         * 根据关键字段匹配已经存在的相同adj
         *
         * @param property          资产标识
         * @param existSameAdjQuery 查询条件
         * @return cloud.wconnect.commonlib.pojo.dto.result.ResultDTO<java.util.List < cloud.wconnect.finance.pojo.dto.chargeback.CbCaseIwcAdRequestDTO>>
         * @author lichen
         * @date 2026/2/10
         */
        @ApiId(1697)
        @GetMapping("/exist-same-adj")
        public ResultDTO<List<CbCaseIwcAdRequestDTO>> listExistSameAdj(String property, ExistSameAdjQuery existSameAdjQuery) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            String pmsType = propertyDTO.getPmsType();
            if (SystemPlatformEnum.OPERA.getDesc().equals(pmsType)) {
                if (StringUtils.isBlank(existSameAdjQuery.getGuestName())) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("guestName"),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The guestName cannot be empty");
                }
                if (StringUtils.isBlank(existSameAdjQuery.getRoomNumber())) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("roomNumber"),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The roomNumber cannot be empty");
                }
            } else {
                if (StringUtils.isBlank(existSameAdjQuery.getConfNumber())) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("confNumber"),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The confNumber cannot be empty");
                }
            }

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

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

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

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

            return cbCaseIwcAdRequestService.listExistSameAdj(propertyDTO, existSameAdjQuery);
        }

        /**
         * 财务选择 result
         *
         * @param id                case id
         * @param property          资产标识
         * @param saveCaseResultDTO result 信息
         * @return {}
         * @author yyt
         * @date 2023/9/6
         */
        @ApiId(576)
        @PostMapping("/{id:\\d+}/result")
        public Map chooseResult(@PathVariable long id, String property, @Valid @RequestBody SaveCaseResultDTO saveCaseResultDTO) {
            CbCase cs = cbCaseService.checkExist(id);
            checkBelong(cs, sessionContext.getPropertyId(property));
            cbCaseResultService.save(cs, saveCaseResultDTO, sessionContext.getLoginUserId());

            return Collections.emptyMap();
        }

        /**
         * action 为 accept 时 case 填写 Bank Receiving/Write-off Date
         *
         * @param id                     case id
         * @param property               资产描述
         * @param saveResultFinalDateDTO Bank Receiving/Write-off Date
         * @return {}
         * @author yyt
         * @date 2023/9/7
         */
        @ApiId(577)
        @PostMapping("/{id:\\d+}/accept/result")
        public Map chooseResultForAccept(@PathVariable long id, String property,
                                         @Valid @RequestBody SaveResultFinalDateDTO saveResultFinalDateDTO) {
            CbCase cs = cbCaseService.checkExist(id);
            checkBelong(cs, sessionContext.getPropertyId(property));
            cbCaseResultService.chooseResultForAccept(cs, saveResultFinalDateDTO, sessionContext.getLoginUserId());

            return Collections.emptyMap();
        }

        /**
         * 校验数据归属
         *
         * @param cs         case
         * @param propertyId 资产 id
         * @author yyt
         * @date 2023/9/1
         */
        private void checkBelong(CbCase cs, Integer propertyId) {
            if (!cs.getPropertyId().equals(propertyId)) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(), "the property id parameter is different from " +
                        "the property id of the cbCase;propertyId:" + propertyId + ",cbCasePropertyId:" + cs.getPropertyId() +
                        ",cbCaseId:" + cs.getId());
            }
        }

        /**
         * 校验数据归属
         *
         * @param cbAdj      cb adj
         * @param propertyId 资产 id
         * @author yyt
         * @date 2023/9/1
         */
        private void checkBelong(CbAdj cbAdj, Integer propertyId) {
            if (!cbAdj.getPropertyId().equals(propertyId)) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(), "the property id parameter is different from " +
                        "the property id of the cbAdj;propertyId:" + propertyId + ",cbAdjPropertyId:" + cbAdj.getPropertyId() +
                        ",cbAdjId:" + cbAdj.getId());
            }
        }

        /**
         * 校验数据归属
         *
         * @param comment    cb adj comment
         * @param propertyId 资产 id
         * @author yyt
         * @date 2023/9/4
         */
        private void checkBelong(CbAdjComment comment, Integer propertyId) {
            if (!comment.getPropertyId().equals(propertyId)) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(), "the property id parameter is different from " +
                        "the property id of the cbAdjComment;propertyId:" + propertyId + ",cbAdjCommentPropertyId:" +
                        comment.getPropertyId() + ",cbAdjCommentId:" + comment.getId());
            }
        }

    }