From 36947052593d5add5a5ca1afdeaf44acf0c12421 Mon Sep 17 00:00:00 2001 From: LeeEunmi Date: Mon, 18 Aug 2025 01:02:13 +0900 Subject: [PATCH 01/13] =?UTF-8?q?=E2=9C=A8=20feat:=20=EB=82=B4=EB=B3=B4?= =?UTF-8?q?=EB=82=B4=EA=B8=B0=20controller?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ContractController.java | 68 +++++++++ .../controller/ContractControllerImpl.java | 131 ++++++++++++++++++ 2 files changed, 199 insertions(+) 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 b5d892f2..92f79a00 100644 --- a/src/main/java/org/scoula/domain/contract/controller/ContractController.java +++ b/src/main/java/org/scoula/domain/contract/controller/ContractController.java @@ -1,5 +1,7 @@ package org.scoula.domain.contract.controller; +import javax.servlet.http.HttpServletResponse; + import org.scoula.domain.contract.dto.*; import org.scoula.global.auth.dto.CustomUserDetails; import org.scoula.global.common.dto.ApiResponse; @@ -7,6 +9,8 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -114,4 +118,68 @@ ResponseEntity> updateSpecialContract( @PathVariable Long contractChatId, @AuthenticationPrincipal CustomUserDetails userDetails, @RequestBody SpecialContractUpdateDTO dto); + + // ================= + + // 내보내기 + // @ApiOperation( + // value = "[내보내기] 1 최종 계약서, 전자서명 테이블 초기 세팅", + // notes = "전자서명에 관련된 값들을 저장하기 위해 테이블 초기 세팅을 합니다.") + // ResponseEntity> finalContractInit( + // @PathVariable Long contractChatId, + // @AuthenticationPrincipal CustomUserDetails userDetails); + + @ApiOperation(value = "[내보내기] 1 최종 계약서 PDF로 만들기 -> AI", notes = "최종 계약서에 들어갈 항목들로 최종 계약서 만들기 ") + ResponseEntity> finalContractPDF( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails); + + @ApiOperation(value = "[내보내기] 2 받아온 전자서명 파일 암호화 후 S3에 저장", notes = "전자서명 png를 s3에 저장합니다.") + ResponseEntity> saveSignature( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails, + @RequestPart("dto") String dtoText, // JSON 파트 + @RequestPart("imgFiles") MultipartFile imgFiles) + throws Exception; + + @ApiOperation(value = "[내보내기] 3 최종 계약서 PDF S3에 저장", notes = "사용자에게 암호를 받아 암호화 후 S3에 저장하기") + ResponseEntity> saveFinalContract( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails, + @RequestBody ContractPasswordDTO dto); + + @ApiOperation(value = "[내보내기] 4 최종 계약서 PDF를 보여줍니다.", notes = "최종 계약서 PDF를 보여줍니다.") + ResponseEntity> selectContractPDF( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails) + throws Exception; + + // @ApiOperation(value = "최종 계약서 PDF 파일 받아와서 암호화 후 S3에 저장", notes = "최종 계약서 PDF를 암호화하여 S3에 + // 저장합니다.") + // ResponseEntity> saveContractPDF( + // @PathVariable Long contractChatId, + // @AuthenticationPrincipal CustomUserDetails userDetails, + // @RequestBody FinalContractDTO dto); + // + // @ApiOperation(value = "전자서명 다운로드", notes = "전자서명을 다운로드해서 복호화해서 프론트에 전송합니다.") + // ResponseEntity> selectSignaturePDF( + // @PathVariable Long contractChatId, + // @AuthenticationPrincipal CustomUserDetails userDetails, + // HttpServletResponse response); + + // 패스베리어블을 뭘로 받아올지 얘기해보기 : contract_id + @ApiOperation(value = "[내보내기] 5 계약서 PDF 파일 다운로드", notes = "계약서 PDF를 S3에서 꺼내 보내준다") + ResponseEntity> selectContractPDF( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails, + HttpServletResponse response, + @RequestBody FindContractDTO dto) + throws Exception; + + @ApiOperation(value = "[내보내기] 6 최종 계약서 PDF를 이메일로 전송", notes = "최종 계약서 PDF를 이메일로 전송합니다.") + ResponseEntity> sendContractPDF( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails, + @RequestBody FindContractDTO dto) + throws Exception; } 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 7c94e640..9a8373ce 100644 --- a/src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java +++ b/src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java @@ -1,5 +1,7 @@ package org.scoula.domain.contract.controller; +import javax.servlet.http.HttpServletResponse; + import org.scoula.domain.contract.dto.*; import org.scoula.domain.contract.service.ContractService; import org.scoula.global.auth.dto.CustomUserDetails; @@ -7,6 +9,9 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @@ -191,4 +196,130 @@ public ResponseEntity> sendStep4( return ResponseEntity.ok( ApiResponse.success(service.sendStep4(contractChatId, userDetails.getUserId()))); } + + @Override + @GetMapping("/finalContract") + public ResponseEntity> selectContractPDF( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails) { + return ResponseEntity.ok( + ApiResponse.success( + service.selectContractPDF(contractChatId, userDetails.getUserId()))); + } + + // ======================================== + + // @Override + // @PostMapping("/final_contract") + // public ResponseEntity> finalContractInit( + // @PathVariable Long contractChatId, + // @AuthenticationPrincipal CustomUserDetails userDetails) { + // return ResponseEntity.ok( + // ApiResponse.success( + // service.finalContractInit(contractChatId, + // userDetails.getUserId()))); + // } + + @Override + @GetMapping("/final_contract") + public ResponseEntity> finalContractPDF( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails) { + return ResponseEntity.ok( + ApiResponse.success( + service.finalContractPDF(contractChatId, userDetails.getUserId()))); + } + + @Override + @PostMapping("/signature/tax") + public ResponseEntity> saveSignature( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails, + // @RequestPart("dto") SaveSignatureDTO dto, // JSON 파트 + @RequestParam("dto") String dtoText, + @RequestPart("imgFiles") MultipartFile imgFiles) + throws Exception { + if (imgFiles == null || imgFiles.isEmpty()) { + throw new IllegalArgumentException("서명 이미지가 비어 있습니다."); + } + // 문자열 -> DTO (JSON도, 'TAX' 같은 단일 문자열도 허용) + SaveSignatureDTO dto; + try { + dto = + new ObjectMapper() + .readValue(dtoText, SaveSignatureDTO.class); // {"signedType":"TAX"} + } catch (Exception ignore) { + dto = + SaveSignatureDTO.builder() + .signedType( + org.scoula.domain.contract.enums.SignedType.valueOf( + dtoText.trim().toUpperCase())) + .build(); // TAX + } + return ResponseEntity.ok( + ApiResponse.success( + service.saveSignature( + contractChatId, userDetails.getUserId(), dto, imgFiles))); + } + + @Override + @PostMapping("/finalContract/p") + public ResponseEntity> saveFinalContract( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails, + @RequestBody ContractPasswordDTO dto) { + return ResponseEntity.ok( + ApiResponse.success( + service.saveFinalContract(contractChatId, userDetails.getUserId(), dto))); + } + + // @Override + // @PostMapping("/pdf") + // public ResponseEntity> saveContractPDF( + // @PathVariable Long contractChatId, + // @AuthenticationPrincipal CustomUserDetails userDetails, + // @RequestBody FinalContractDTO dto) { + // return ResponseEntity.ok( + // ApiResponse.success( + // service.saveContractPDF(contractChatId, userDetails.getUserId(), + // dto))); + // } + // + // @Override + // @GetMapping("/signature") + // public ResponseEntity> selectSignaturePDF( + // @PathVariable Long contractChatId, + // @AuthenticationPrincipal CustomUserDetails userDetails, + // HttpServletResponse response) { + // return ResponseEntity.ok( + // ApiResponse.success( + // service.selectSignaturePDF( + // contractChatId, userDetails.getUserId(), response))); + // } + + @Override + @PostMapping("/pdf") + public ResponseEntity> selectContractPDF( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails, + HttpServletResponse response, + @RequestBody FindContractDTO dto) + throws Exception { + return ResponseEntity.ok( + ApiResponse.success( + service.selectContractPDF( + contractChatId, userDetails.getUserId(), response, dto))); + } + + @Override + @PostMapping("/email") + public ResponseEntity> sendContractPDF( + @PathVariable Long contractChatId, + @AuthenticationPrincipal CustomUserDetails userDetails, + @RequestBody FindContractDTO dto) + throws Exception { + return ResponseEntity.ok( + ApiResponse.success( + service.sendContractPDF(contractChatId, userDetails.getUserId(), dto))); + } } From a6ff075eb9f7260f0ee1111dc73db1239c46ce32 Mon Sep 17 00:00:00 2001 From: LeeEunmi Date: Mon, 18 Aug 2025 01:02:27 +0900 Subject: [PATCH 02/13] =?UTF-8?q?=E2=9C=A8=20feat:=20=EB=82=B4=EB=B3=B4?= =?UTF-8?q?=EB=82=B4=EA=B8=B0=20mapper=20/=20xml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contract/mapper/ContractMapper.java | 44 ++++- .../domain/contract/mapper/ContractMapper.xml | 168 +++++++++++++----- 2 files changed, 163 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/scoula/domain/contract/mapper/ContractMapper.java b/src/main/java/org/scoula/domain/contract/mapper/ContractMapper.java index 86ba0dc1..758727ed 100644 --- a/src/main/java/org/scoula/domain/contract/mapper/ContractMapper.java +++ b/src/main/java/org/scoula/domain/contract/mapper/ContractMapper.java @@ -1,8 +1,14 @@ package org.scoula.domain.contract.mapper; +import java.util.List; + import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.scoula.domain.contract.dto.ContractDTO; +import org.scoula.domain.contract.dto.DBFinalContractDTO; +import org.scoula.domain.contract.enums.SignedType; +import org.scoula.domain.contract.vo.ElectronicSignature; +import org.scoula.domain.contract.vo.FinalContract; @Mapper public interface ContractMapper { @@ -19,14 +25,40 @@ public interface ContractMapper { Long selectFinalContractId(@Param("contractChatId") Long contractChatId); - int insertSignatureInit(@Param("contractChatId") Long contractChatId); + // ============== + + int insertFinalContractInit( + @Param("contractChatId") Long contractChatId, + @Param("depositPrice") int depositPrice, + @Param("monthlyRent") int monthlyRent, + @Param("maintenanceFee") int maintenanceFee); + + int insertContract(@Param("contractChatId") Long contractChatId, @Param("s3Key") String s3Key); + + DBFinalContractDTO selectFinalContractPDF(@Param("contractChatId") Long contractChatId); + + FinalContract selectFinalContract(@Param("contractChatId") Long contractChatId); + + int insertSignature( + @Param("contractChatId") Long contractChatId, + @Param("s3Key") String s3Key, + @Param("hashKey") String hashKey, + @Param("signedType") SignedType signedType, + @Param("userId") Long userId); + + List selectSignature( + @Param("contractChatId") Long contractChatId, @Param("userId") Long userId); + + int updateFinalContract( + @Param("contractChatId") Long contractChatId, + @Param("contractPdfKey") String contractPdfKey, + @Param("contractPdfHash") String contractPdfHash); + + String selectBirth(@Param("userId") Long userId); - int updateTaxSignature( - @Param("finalContractId") Long finalContractId, - @Param("url") String url, - @Param("hashKey") String hashKey); + String selectMail(@Param("userId") Long userId); - int insertFinalContract(@Param("contractChatId") Long contractChatId); + // ======== String selectOwnerTaxSignatureUrl(@Param("finalContractId") Long finalContractId); diff --git a/src/main/resources/org/scoula/domain/contract/mapper/ContractMapper.xml b/src/main/resources/org/scoula/domain/contract/mapper/ContractMapper.xml index 10aa8332..d8a69411 100644 --- a/src/main/resources/org/scoula/domain/contract/mapper/ContractMapper.xml +++ b/src/main/resources/org/scoula/domain/contract/mapper/ContractMapper.xml @@ -61,65 +61,147 @@ WHERE cc.contract_chat_id = #{contractChatId} - - INSERT INTO electronic_signature (contract_id, identity_verification_id, owner_id, buyer_id, created_at) - SELECT fc.contract_id, iv.identity_id, cc.owner_id, cc.buyer_id, NOW() + + + insert into final_contract (contract_id, home_id, owner_id, buyer_id, owner_identity_verified_at, buyer_identity_verified_at, deposit_price, monthly_rent, maintenance_fee, created_at) + select #{contractChatId}, cc.home_id, cc.owner_id, cc.buyer_id, oiv.identity_verified_at, biv.identity_verified_at, #{depositPrice}, #{monthlyRent}, #{maintenanceFee}, NOW() FROM contract_chat cc - INNER JOIN final_contract fc - ON cc.home_id = fc.home_id AND cc.owner_id = fc.owner_id - LEFT JOIN identity_verification iv - ON iv.contract_id = cc.contract_chat_id - WHERE cc.contract_chat_id = 4; + LEFT JOIN identity_verification oiv + ON oiv.user_id = cc.owner_id + AND oiv.contract_id = cc.contract_chat_id + LEFT JOIN identity_verification biv + ON biv.user_id = cc.buyer_id + AND biv.contract_id = cc.contract_chat_id + WHERE cc.contract_chat_id = #{contractChatId} - - UPDATE electronic_signature - SET owner_tax_signature_file_url = #{url}, owner_tax_file_hash = #{hashKey}, owner_tax_signed_at = NOW() - WHERE contract_id = #{finalContractId} + + + + update final_contract + set contract_pdf_key = #{s3Key} + WHERE contract_id = #{contractChatId} - - INSERT INTO final_contract ( + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into electronic_signature (contract_id, identity_verification_id, signature_file_key, signature_file_hash, signed_type, created_at) + select #{contractChatId}, iv.identity_id , #{s3Key}, #{hashKey}, #{signedType},NOW() + FROM contract_chat cc + JOIN identity_verification iv + ON iv.contract_id = cc.contract_chat_id + WHERE cc.contract_chat_id = #{contractChatId} AND iv.user_id= #{userId} + + + + UPDATE final_contract + SET contract_pdf_key = #{contractPdfKey}, contract_pdf_hash = #{contractPdfHash}, contract_date = NOW(), contract_expire_date = , + WHERE contractChatId = #{contractChatId} + + + + + + SELECT h.lease_type AS leaseType, + h.addr1 AS homeAddr1, + h.addr2 AS homeAddr2, hd.land_category AS landCategory, hd.area AS area, -- rc.purpose AS purpose, @@ -186,15 +188,45 @@ UPDATE final_contract - SET contract_pdf_key = #{contractPdfKey}, contract_pdf_hash = #{contractPdfHash}, contract_date = NOW(), contract_expire_date = , - WHERE contractChatId = #{contractChatId} + SET contract_pdf_key = #{contractPdfKey}, + contract_pdf_hash = #{contractPdfHash}, + contract_date = NOW(), + contract_expire_date = DATE_ADD(NOW(), INTERVAL 2 YEAR) + WHERE contract_id = #{contractChatId} + + + INSERT INTO final_contract (contract_id, home_id, owner_id, buyer_id, contract_pdf_key, contract_pdf_hash, contract_date, contract_expire_date) + SELECT + cc.contract_chat_id, + cc.home_id, + cc.owner_id, + cc.buyer_id, + #{contractPdfKey}, + #{contractPdfHash}, + NOW(), + DATE_ADD(NOW(), INTERVAL 2 YEAR) + FROM contract_chat cc + WHERE cc.contract_chat_id = #{contractChatId} + ON DUPLICATE KEY UPDATE + contract_pdf_key = VALUES(contract_pdf_key), + contract_pdf_hash = VALUES(contract_pdf_hash), + contract_date = VALUES(contract_date), + contract_expire_date = VALUES(contract_expire_date) + + +