diff --git a/src/main/java/org/scoula/domain/contract/controller/ContractController.java b/src/main/java/org/scoula/domain/contract/controller/ContractController.java index f93aa7af..b5d892f2 100644 --- a/src/main/java/org/scoula/domain/contract/controller/ContractController.java +++ b/src/main/java/org/scoula/domain/contract/controller/ContractController.java @@ -14,71 +14,104 @@ @Api(tags = "계약서 API", description = "계약서 : 정보확인 / 금액 조율 / 적법성 확인") public interface ContractController { - // step 1 (init) - @ApiOperation(value = "[계약전_임차인] 계약서를 몽고DB에 저장", notes = "계약서에 필요한 항목들을 가져와서 몽고 DB에 계약서 만들기") + // 대기 + @ApiOperation( + value = "[대기] 계약전_임차인 | 계약서를 몽고DB에 저장", + notes = "계약서에 필요한 항목들을 가져와서 몽고 DB에 계약서 만들기") ResponseEntity> saveContractMongo( @PathVariable Long contractChatId, @AuthenticationPrincipal CustomUserDetails userDetails); - // step 1 : start - @ApiOperation(value = "[계약서 _ 정보 조회 1] 계약서 전체 조회", notes = "계약서 가져오기") + // 정보 조회 + @ApiOperation(value = "[정보 조회] 계약서 1 | 계약서 전체 조회", notes = "계약서 가져오기") ResponseEntity> getContract( @PathVariable Long contractChatId, @AuthenticationPrincipal CustomUserDetails userDetails); - @ApiOperation(value = "[채팅 _ 정보 조회 1] 정보 조회 시작", notes = "정보조회 마지막 단계에서 다음 단계로 넘어가기 Message") + @ApiOperation(value = "[정보 조회] 채팅 1 | 정보 조회 시작", notes = "정보조회 마지막 단계에서 다음 단계로 넘어가기 Message") ResponseEntity> getContractNext( @PathVariable Long contractChatId, @AuthenticationPrincipal CustomUserDetails userDetails); - // step 1 : finish @ApiOperation( - value = "[채팅 _ 정보 조회 2] 정보 조회에서 다음단계로 가기", + value = "[정보 조회] 채팅 2 | 정보 조회에서 다음단계로 가기", notes = "다음 단계 여부(true/false)를 받아서 다음 단계로 넘어가기") ResponseEntity> nextStep( @PathVariable Long contractChatId, @AuthenticationPrincipal CustomUserDetails userDetails, @RequestBody NextStepDTO dto); - @ApiOperation(value = "[채팅 _ 금액 조회 1]", notes = "금액을 조율하기 위해 금액을 조회") + // 금액 조정 + @ApiOperation(value = "[금액 조정] 채팅 1 | 금액 조회", notes = "금액을 조율하기 위해 금액을 조회") ResponseEntity> getDepositPrice( @PathVariable Long contractChatId, @AuthenticationPrincipal CustomUserDetails userDetails); - @ApiOperation(value = "[채팅 _ 금액 요청 2]", notes = "임대인이 금액을 요청") + @ApiOperation(value = "[금액 조정] 채팅 2 | 금액 요청 ", notes = "임대인이 금액을 요청") ResponseEntity> saveDepositPrice( @PathVariable Long contractChatId, @AuthenticationPrincipal CustomUserDetails userDetails, @RequestBody PaymentDTO dto); - @ApiOperation(value = "[채팅 _ 금액 거절 3]", notes = "임차인이 금액을 거절") + @ApiOperation(value = "[금액 조정] 채팅 3 | 금액 거절", notes = "임차인이 금액을 거절") ResponseEntity> deleteDepositPrice( @PathVariable Long contractChatId, @AuthenticationPrincipal CustomUserDetails userDetails); - @ApiOperation(value = "[채팅 _ 금액 수락 4]", notes = "임대인과 임차인 모두 동의") + @ApiOperation(value = "[금액 조정] 채팅 4 | 금액 수락", notes = "임대인과 임차인 모두 동의") ResponseEntity> updateDepositPrice( @PathVariable Long contractChatId, @AuthenticationPrincipal CustomUserDetails userDetails); - @ApiOperation(value = "AI 적법성 확인 from 몽고DB", notes = "몽고DB에 있는 계약서를 AI로 보내고, 적법성 받기") + // // 적법성 검사 + // @ApiOperation(value = "??? 몽고 디비랑 합친것 [적법성 검사] 계약서 1 | 계약서 전체 조회", notes = "계약서 가져오기") + // ResponseEntity> getContracts( + // @PathVariable Long contractChatId, + // @AuthenticationPrincipal CustomUserDetails userDetails); + + @ApiOperation(value = "[적법성 검사] 계약서 1 몽고DB에 특약 저장 ", notes = "몽고DB에 특약 저장하기 ") + ResponseEntity> saveSpecialContract( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails); + + @ApiOperation( + value = "[적법성 검사] 채팅 1 | AI 적법성 확인 from 몽고DB ", + notes = "몽고DB에 있는 계약서를 AI로 보내고, 적법성 받기") ResponseEntity> getLegality( @PathVariable Long contractChatId, @AuthenticationPrincipal CustomUserDetails userDetails); - @ApiOperation(value = "특약을 계약서 DB에 저장 FROM 몽고DB", notes = "특약 테이블에 있는걸 계약서로 가져오기") - ResponseEntity> saveSpecialContract( + @ApiOperation(value = "[적법성 검사] 채팅 2 | 임대인 수정", notes = "임대인 : 적법성 검사 수정") + ResponseEntity> updateOwnerLegality( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails, + @RequestBody UpdateLegalityDTO dto); + + @ApiOperation(value = "[적법성 검사] 채팅 3 | 임대인 삭제", notes = "임대인 : 적법성 검사 삭제") + ResponseEntity> deleteOwnerLegality( @PathVariable Long contractChatId, @AuthenticationPrincipal CustomUserDetails userDetails); - @ApiOperation(value = "바뀐 특약 수정 from 몽고DB", notes = "적법성 검사 후 수정된 특약으로 변경") - ResponseEntity> updateSpecialContract( + @ApiOperation(value = "[적법성 검사] 채팅 4 | 임차인 수정", notes = "임차인 : 적법성 검사 수정 완료") + ResponseEntity> updateBuyerLegality( @PathVariable Long contractChatId, @AuthenticationPrincipal CustomUserDetails userDetails, @RequestBody SpecialContractUpdateDTO dto); - @ApiOperation(value = "적법성 검사 후 다음단계로 넘어가기", notes = "적법성 검사 후 AI 메세지를 보낸다") + @ApiOperation(value = "[적법성 검사] 채팅 5 | 임차인 거절", notes = "임차인 : 적법성 검사 거절") + ResponseEntity> rejectBuyerLegality( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails); + + @ApiOperation(value = "[적법성 검사} 채팅 6 | 적법성 검사 후 다음단계로 넘어가기", notes = "내보내기 단계로 넘어가기") ResponseEntity> sendStep4( @PathVariable Long contractChatId, @AuthenticationPrincipal CustomUserDetails userDetails); + + // 메서드만 만들어서 써도 될 듯 + @ApiOperation(value = "바뀐 특약 수정 from 몽고DB", notes = "적법성 검사 후 수정된 특약으로 변경") + ResponseEntity> updateSpecialContract( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails, + @RequestBody SpecialContractUpdateDTO dto); } diff --git a/src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java b/src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java index e9ba5a1d..7c94e640 100644 --- a/src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java +++ b/src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java @@ -100,6 +100,26 @@ public ResponseEntity> updateDepositPrice( service.updateDepositPrice(contractChatId, userDetails.getUserId()))); } + // @Override + // @PostMapping("/getContracts") + // public ResponseEntity> getContracts( + // @PathVariable Long contractChatId, + // @AuthenticationPrincipal CustomUserDetails userDetails) { + // return ResponseEntity.ok( + // ApiResponse.success(service.getContract(contractChatId, + // userDetails.getUserId()))); + // } + + @Override + @PostMapping("/save/special-contract") + public ResponseEntity> saveSpecialContract( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails) { + return ResponseEntity.ok( + ApiResponse.success( + service.saveSpecialContract(contractChatId, userDetails.getUserId()))); + } + @Override @PostMapping("/legality") public ResponseEntity> getLegality( @@ -110,13 +130,45 @@ public ResponseEntity> getLegality( } @Override - @PostMapping("/specialContract") - public ResponseEntity> saveSpecialContract( + @DeleteMapping("/delete/legality") + public ResponseEntity> deleteOwnerLegality( @PathVariable Long contractChatId, @AuthenticationPrincipal CustomUserDetails userDetails) { return ResponseEntity.ok( ApiResponse.success( - service.saveSpecialContract(contractChatId, userDetails.getUserId()))); + service.deleteOwnerLegality(contractChatId, userDetails.getUserId()))); + } + + @Override + @PostMapping("/suggest/legality") + public ResponseEntity> updateOwnerLegality( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails, + @RequestBody UpdateLegalityDTO dto) { + return ResponseEntity.ok( + ApiResponse.success( + service.updateOwnerLegality(contractChatId, userDetails.getUserId(), dto))); + } + + @Override + @PostMapping("/update/legality") + public ResponseEntity> updateBuyerLegality( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails, + @RequestBody SpecialContractUpdateDTO dto) { + return ResponseEntity.ok( + ApiResponse.success( + service.updateBuyerLegality(contractChatId, userDetails.getUserId(), dto))); + } + + @Override + @GetMapping("/reject/legality") + public ResponseEntity> rejectBuyerLegality( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails) { + return ResponseEntity.ok( + ApiResponse.success( + service.rejectBuyerLegality(contractChatId, userDetails.getUserId()))); } @Override diff --git a/src/main/java/org/scoula/domain/contract/dto/ContractDTO.java b/src/main/java/org/scoula/domain/contract/dto/ContractDTO.java index 92598f4d..68e6f281 100644 --- a/src/main/java/org/scoula/domain/contract/dto/ContractDTO.java +++ b/src/main/java/org/scoula/domain/contract/dto/ContractDTO.java @@ -5,6 +5,7 @@ import java.util.stream.Collectors; import org.scoula.domain.contract.document.ContractMongoDocument; +import org.scoula.domain.precontract.vo.IdentityVerificationInfoVO; import org.scoula.global.common.constant.Constants; import com.fasterxml.jackson.annotation.JsonFormat; @@ -63,15 +64,18 @@ public static class SpecialContractDTO { private String content; } - public static ContractDTO toDTO(ContractMongoDocument document) { + public static ContractDTO toDTO( + ContractMongoDocument document, + IdentityVerificationInfoVO ownerVO, + IdentityVerificationInfoVO buyerVO) { return ContractDTO.builder() - .contractChatId(document.getContractChatId()) + .contractChatId(ownerVO.getContractId()) .ownerName(document.getOwnerName()) - .ownerAddr(document.getOwnerAddr()) - .ownerPhoneNum(document.getOwnerPhoneNum()) + .ownerAddr(ownerVO.getAddr1() + " " + ownerVO.getAddr2()) + .ownerPhoneNum(ownerVO.getPhoneNumber()) .buyerName(document.getBuyerName()) - .buyerAddr(document.getBuyerAddr()) - .buyerPhoneNum(document.getBuyerPhoneNum()) + .buyerAddr(buyerVO.getAddr1() + " " + buyerVO.getAddr2()) + .buyerPhoneNum(buyerVO.getPhoneNumber()) .homeAddr1(document.getHomeAddr1()) .homeAddr2(document.getHomeAddr2()) .residenceType(document.getResidenceType()) diff --git a/src/main/java/org/scoula/domain/contract/dto/LegalityRequestDTO.java b/src/main/java/org/scoula/domain/contract/dto/LegalityRequestDTO.java new file mode 100644 index 00000000..ec2ed78d --- /dev/null +++ b/src/main/java/org/scoula/domain/contract/dto/LegalityRequestDTO.java @@ -0,0 +1,14 @@ +package org.scoula.domain.contract.dto; + +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class LegalityRequestDTO { + private String legalBasis; + private Long requestId; + private String createdAt; +} diff --git a/src/main/java/org/scoula/domain/contract/dto/UpdateLegalityDTO.java b/src/main/java/org/scoula/domain/contract/dto/UpdateLegalityDTO.java new file mode 100644 index 00000000..8967fa23 --- /dev/null +++ b/src/main/java/org/scoula/domain/contract/dto/UpdateLegalityDTO.java @@ -0,0 +1,12 @@ +package org.scoula.domain.contract.dto; + +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class UpdateLegalityDTO { + private String legalBasis; +} diff --git a/src/main/java/org/scoula/domain/contract/service/ContractService.java b/src/main/java/org/scoula/domain/contract/service/ContractService.java index 4dab7963..e875661d 100644 --- a/src/main/java/org/scoula/domain/contract/service/ContractService.java +++ b/src/main/java/org/scoula/domain/contract/service/ContractService.java @@ -72,6 +72,16 @@ public interface ContractService { */ Void updateDepositPrice(Long contractChatId, Long userId); + // /** + // * @param contractChatId 채팅방 아이디 + // * @param userId 유저 아이디 + // * @return 계약서 내용을 보내기 + // */ + // ContractDTO getContracts(Long contractChatId, Long userId); + + /** * step4 start 특약을 개약 테이블에 저장하기 * * @param contractChatId 채팅방 아이디 * @param userId 유저 아이디 */ + Void saveSpecialContract(Long contractChatId, Long userId); + /** * step4 (init) 계약서를 AI로 보내고, 적법성 받기 * @@ -82,20 +92,48 @@ public interface ContractService { LegalityDTO getLegality(Long contractChatId, Long userId); /** - * step4 start 특약을 개약 테이블에 저장하기 + * step4 적법성 검사 후 수정된 특약으로 변경 + * + * @param contractChatId 채팅방 아이디 + * @param userId 유저 아이디 @Param dto 변경된 특약 + */ + Void updateSpecialContract(Long contractChatId, Long userId, SpecialContractUpdateDTO dto); + + /** + * step4 (init) 계약서를 AI로 보내고, 적법성 받기 * * @param contractChatId 채팅방 아이디 * @param userId 유저 아이디 + * @return AI가 계약서를 보고 주는 적법성을 리턴값으로 보내기 */ - Void saveSpecialContract(Long contractChatId, Long userId); + String deleteOwnerLegality(Long contractChatId, Long userId); /** - * step4 적법성 검사 후 수정된 특약으로 변경 + * step4 (init) 계약서를 AI로 보내고, 적법성 받기 * * @param contractChatId 채팅방 아이디 - * @param userId 유저 아이디 @Param dto 변경된 특약 + * @param userId 유저 아이디 + * @return AI가 계약서를 보고 주는 적법성을 리턴값으로 보내기 */ - Void updateSpecialContract(Long contractChatId, Long userId, SpecialContractUpdateDTO dto); + Void updateOwnerLegality(Long contractChatId, Long userId, UpdateLegalityDTO dto); + + /** + * step4 (init) 계약서를 AI로 보내고, 적법성 받기 + * + * @param contractChatId 채팅방 아이디 + * @param userId 유저 아이디 + * @return AI가 계약서를 보고 주는 적법성을 리턴값으로 보내기 + */ + Void updateBuyerLegality(Long contractChatId, Long userId, SpecialContractUpdateDTO dto); + + /** + * step4 (init) 계약서를 AI로 보내고, 적법성 받기 + * + * @param contractChatId 채팅방 아이디 + * @param userId 유저 아이디 + * @return AI가 계약서를 보고 주는 적법성을 리턴값으로 보내기 + */ + String rejectBuyerLegality(Long contractChatId, Long userId); /** * step4 finish 적법성 검사 후 다음단계로 넘어가기 diff --git a/src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java b/src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java index fc687798..0b025deb 100644 --- a/src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java +++ b/src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java @@ -15,6 +15,9 @@ import org.scoula.domain.precontract.enums.ContractDuration; import org.scoula.domain.precontract.exception.PreContractErrorCode; import org.scoula.domain.precontract.mapper.TenantPreContractMapper; +import org.scoula.domain.precontract.service.IdentityVerificationService; +import org.scoula.domain.precontract.service.IdentityVerificationServiceImpl; +import org.scoula.domain.precontract.vo.IdentityVerificationInfoVO; import org.scoula.global.common.exception.BusinessException; import org.scoula.global.email.service.EmailServiceImpl; import org.scoula.global.file.service.S3ServiceImpl; @@ -22,9 +25,12 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.*; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.RestTemplate; +import java.time.Duration; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; @Service @@ -39,6 +45,8 @@ public class ContractServiceImpl implements ContractService { private final RestTemplate restTemplate; private final TenantPreContractMapper tenantMapper; private final ContractChatMapper contractChatMapper; + private final IdentityVerificationService identityVerificationService; + private final ObjectMapper objectMapper = new ObjectMapper(); private final RedisTemplate stringRedisTemplate; private final S3ServiceImpl s3Service; @@ -104,8 +112,14 @@ public ContractDTO getContract(Long contractChatId, Long userId) { throw new BusinessException(ContractException.CONTRACT_GET); } + Long ownerContractId = contractMapper.getOwnerId(contractChatId); + Long buyerContractId = contractMapper.getBuyerId(contractChatId); + + IdentityVerificationInfoVO ownerVO = identityVerificationService.getDecryptedVerificationInfo(contractChatId, ownerContractId); + IdentityVerificationInfoVO buyerVO = identityVerificationService.getDecryptedVerificationInfo(contractChatId, buyerContractId); + // 찾은 값을 Dto에 넣고 반환하기 - ContractDTO dto = ContractDTO.toDTO(document); + ContractDTO dto = ContractDTO.toDTO(document, ownerVO, buyerVO); return dto; } @@ -179,8 +193,8 @@ public Boolean nextStep(Long contractChatId, Long userId, NextStepDTO dto) { // 다음 단계 메세지 보내기 contractChatService.AiMessage(contractChatId, "이번 단계는 '금액 조율' 단계입니다"); } - // 스텝 변경 - contractChatMapper.updateStatus(contractChatId, ContractChat.ContractStatus.STEP1); +// // 스텝 변경 +// contractChatMapper.updateStatus(contractChatId, ContractChat.ContractStatus.STEP1); } return nextSteps; @@ -321,7 +335,64 @@ public Void updateDepositPrice(Long contractChatId, Long userId) { return null; } + + // 적법성 검사 +// @Override +// public ContractDTO getContracts (Long contractChatId, Long userId){ +// // userId 검증 +// validateUserId(contractChatId, userId); +// +// ContractDTO dto; +// +// // 몽고 DB에서 특약부분을 받아서 저장한다. +//// try { +// repository.saveSpecialContract(contractChatId); +// +// ContractMongoDocument document = repository.getContract(contractChatId); +// if (document == null) { +// throw new BusinessException(ContractException.CONTRACT_GET); +// } +// Long ownerContractId = contractMapper.getOwnerId(contractChatId); +// Long buyerContractId = contractMapper.getBuyerId(contractChatId); +// IdentityVerificationInfoVO ownerVO = identityVerificationService.getDecryptedVerificationInfo(contractChatId, ownerContractId); +// IdentityVerificationInfoVO buyerVO = identityVerificationService.getDecryptedVerificationInfo(contractChatId, buyerContractId); +// +// +// // 찾은 값을 Dto에 넣고 반환하기 +// dto = ContractDTO.toDTO(document, ownerVO, buyerVO); +//// } catch (Exception e) { +//// // 예외 로그 기록 및 사용자에게 전달할 메시지 등 처리 +//// log.error("특약사항 저장 실패 ❌", e); +//// throw new BusinessException(ContractException.CONTRACT_INSERT, e); +//// } +// +//// ContractMongoDocument document = repository.getContract(contractChatId); +//// if (document == null) { +//// throw new BusinessException(ContractException.CONTRACT_GET); +//// } +//// +//// // 찾은 값을 Dto에 넣고 반환하기 +//// ContractDTO dto = ContractDTO.toDTO(document); +// +//// ContractDTO dto = getContract(contractChatId, userId); +// +// return dto; +// } + + @Override + public Void saveSpecialContract(Long contractChatId, Long userId) { + // userId 검증 + validateUserId(contractChatId, userId); + // 몽고 DB에서 특약부분을 받아서 저장한다. + try { repository.saveSpecialContract(contractChatId); } catch (Exception e) { + // 예외 로그 기록 및 사용자에게 전달할 메시지 등 처리 + log.error("특약사항 저장 실패 ❌", e); + throw new BusinessException(ContractException.CONTRACT_INSERT, e); } + return null; + } + /** {@inheritDoc} */ + // ai로 적법성 검사하기 -> 암호화 풀어서 보내기 @Override public LegalityDTO getLegality(Long contractChatId, Long userId) { // userId 검증 @@ -332,7 +403,13 @@ public LegalityDTO getLegality(Long contractChatId, Long userId) { if (document == null) { throw new BusinessException(ContractException.CONTRACT_GET); } - ContractDTO dto = ContractDTO.toDTO(document); + + Long ownerContractId = contractMapper.getOwnerId(contractChatId); + Long buyerContractId = contractMapper.getBuyerId(contractChatId); + IdentityVerificationInfoVO ownerVO = identityVerificationService.getDecryptedVerificationInfo(contractChatId, ownerContractId); + IdentityVerificationInfoVO buyerVO = identityVerificationService.getDecryptedVerificationInfo(contractChatId, buyerContractId); + + ContractDTO dto = ContractDTO.toDTO(document, ownerVO, buyerVO); // AI try { @@ -374,25 +451,144 @@ public LegalityDTO getLegality(Long contractChatId, Long userId) { } } - /** {@inheritDoc} */ - @Override - public Void saveSpecialContract(Long contractChatId, Long userId) { + // 임대인 삭제 + @Override + @Transactional + public String deleteOwnerLegality(Long contractChatId, Long userId) { + // userId 검증 + validateUserId(contractChatId, userId); + + Long ownerContractId = contractMapper.getOwnerId(contractChatId); + + String redisKey = + "final-contract:legality:" + contractChatId + ":" + ownerContractId; + String valueDataJson = stringRedisTemplate.opsForValue().get(redisKey); + if (valueDataJson == null) { + throw new IllegalArgumentException("대기중인 수정 요청이 없습니다."); + } + + try { + stringRedisTemplate.delete(redisKey); +// contractChatService.AiMessage(contractChatId, "임대인"); + } catch (Exception e) { + log.error("수정 요청 응답 처리 실패", e); + throw new RuntimeException("응답 처리 중 오류가 발생했습니다."); + } + + + return "임대인이 적법성 검사를 삭제했습니다."; + } + + // 임대인 수정요청 + @Override + @Transactional + public Void updateOwnerLegality(Long contractChatId, Long userId, UpdateLegalityDTO updateLegalityDTO) { + // userId 검증 validateUserId(contractChatId, userId); + Long ownerContractId = contractMapper.getOwnerId(contractChatId); - // 몽고 DB에서 특약부분을 받아서 저장한다. + String redisKey = "final-contract:legality:" + contractChatId + ":" + ownerContractId; + + String existingRequest = stringRedisTemplate.opsForValue().get(redisKey); + if (existingRequest != null) { + throw new IllegalArgumentException("해당 조항에 대한 수정 요청이 이미 대기중입니다."); + } + + LegalityRequestDTO requestData = + LegalityRequestDTO.builder() + .legalBasis(updateLegalityDTO.getLegalBasis()) + .requestId(userId) + .createdAt(LocalDateTime.now().toString()) + .build(); try { - repository.saveSpecialContract(contractChatId); - } catch (Exception e) { - // 예외 로그 기록 및 사용자에게 전달할 메시지 등 처리 - log.error("특약사항 저장 실패 ❌", e); - throw new BusinessException(ContractException.CONTRACT_INSERT, e); + String jsonData = objectMapper.writeValueAsString(requestData); + // Store as valid JSON for correct parsing later + String valueData = String.format("{\"requestData\":%s}", jsonData); + stringRedisTemplate.opsForValue().set(redisKey, valueData); + + contractChatService.AiMessage(contractChatId, "임대인이 적법성 검사 수정을 요청합니다."); + } catch (Exception e) { + log.error("수정 요청 저장 실패", e); + throw new RuntimeException("수정 요청 저장 중 오류가 발생했습니다."); } return null; } + // 임차인 수정 + @Override + @Transactional + public Void updateBuyerLegality(Long contractChatId, Long userId, SpecialContractUpdateDTO dto) { + + // userId 검증 + validateUserId(contractChatId, userId); + + Long ownerContractId = contractMapper.getOwnerId(contractChatId); + + String redisKey = + "final-contract:legality:" + contractChatId + ":" + ownerContractId; + String valueDataJson = stringRedisTemplate.opsForValue().get(redisKey); + if (valueDataJson == null) { + throw new IllegalArgumentException("대기중인 수정 요청이 없습니다."); + } + + try{ + // JSON에서 clauseOrder와 requestData 추출 + com.fasterxml.jackson.databind.JsonNode rootNode = objectMapper.readTree(valueDataJson); + String requestDataJson = rootNode.get("requestData").toString(); + + LegalityRequestDTO requestData = + objectMapper.readValue(requestDataJson, LegalityRequestDTO.class); + + // 수정하는 로직 짜기 + updateSpecialContract(contractChatId, userId, dto); + String resultMessage; + resultMessage = + String.format("적법성 수정 후 레디스에서 삭제되었습니다."); + + stringRedisTemplate.delete(redisKey); + contractChatService.AiMessage(contractChatId, resultMessage); + + }catch (Exception e) { + log.error("수정 요청 응답 처리 실패", e); + throw new RuntimeException("응답 처리 중 오류가 발생했습니다."); + } + + + return null; + } + + // 임차인 거절 + @Override + @Transactional + public String rejectBuyerLegality(Long contractChatId, Long userId) { + + // userId 검증 + validateUserId(contractChatId, userId); + + Long ownerContractId = contractMapper.getOwnerId(contractChatId); + + String redisKey = + "final-contract:legality:" + contractChatId + ":" + ownerContractId; + String valueDataJson = stringRedisTemplate.opsForValue().get(redisKey); + if (valueDataJson == null) { + throw new IllegalArgumentException("대기중인 수정 요청이 없습니다."); + } + + try { + stringRedisTemplate.delete(redisKey); +// contractChatService.AiMessage(contractChatId, "임대인이 적법성 수정을 거절했습니다."); + } catch (Exception e) { + log.error("수정 요청 응답 처리 실패", e); + throw new RuntimeException("응답 처리 중 오류가 발생했습니다."); + } + + return "임대인이 적법성 수정을 거절했습니다."; + } + /** {@inheritDoc} */ + // 수정 확정 @Override public Void updateSpecialContract(Long contractChatId, Long userId, SpecialContractUpdateDTO dto) { // userId 검증 @@ -410,9 +606,16 @@ public Void updateSpecialContract(Long contractChatId, Long userId, SpecialContr /** {@inheritDoc} */ @Override public Void sendStep4(Long contractChatId, Long userId) { + + // userId 검증 + validateUserId(contractChatId, userId); + + contractChatService.AiMessage(contractChatId, "계약서 작성이 완료되었습니다."); + return null; } + // --------------------------------------- // Userid 검증 public void validateUserId(Long contractChatId, Long userId) { @@ -475,6 +678,8 @@ public Boolean nextSteps(Long contractChatId, Long userId, NextStepDTO dto) { // 3) 두 사람이 모두 true면 -> 키 삭제하고 true 반환 if (state.isOwner() && state.isBuyer()) { stringRedisTemplate.delete(redisKey); + // 스텝 변경 + contractChatMapper.updateStatus(contractChatId, ContractChat.ContractStatus.STEP1); return true; }