From 2cce171c935ad8962619cbaf773709a01f0b6c36 Mon Sep 17 00:00:00 2001 From: rizingblare Date: Thu, 19 Oct 2023 22:41:21 +0900 Subject: [PATCH 1/5] feat: Division getPortfolioById by grade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 포트포리오 상세 조회 시, 접근한 유저의 등급에 따른 거래내역 조회 차별화 기능 구현 --- .../portfolio/PortfolioDTOConverter.java | 21 +++++++++--- .../portfolio/PortfolioRestController.java | 5 +-- .../portfolio/PortfolioService.java | 34 +++++++++++++------ 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioDTOConverter.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioDTOConverter.java index 47df0ed3..4ac74b2f 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioDTOConverter.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioDTOConverter.java @@ -14,16 +14,27 @@ public static PortfolioResponse.FindByIdDTO FindByIdDTOConvertor(Planner planner Portfolio portfolio, List images, List priceItems, List matches, List quotations) { + // 가격 항목 DTO 변환 List priceItemDTOS = PriceItemDTOConvertor(priceItems); - Long totalPrice = PriceCalculator.calculatePortfolioPrice(priceItemDTOS); PortfolioResponse.PriceDTO priceDTO = new PortfolioResponse.PriceDTO(totalPrice, priceItemDTOS); // 거래 내역 - List paymentDTOS = PaymentDTOConvertor(matches, quotations); - PortfolioResponse.PaymentHistoryDTO paymentHistoryDTO = - new PortfolioResponse.PaymentHistoryDTO(portfolio.getAvgPrice(), portfolio.getMinPrice(), - portfolio.getMaxPrice(), paymentDTOS); + // 일반 회원의 경우 거래내역으로 null 반환 + PortfolioResponse.PaymentHistoryDTO paymentHistoryDTO = null; + + // 프리미엄 회원일 경우 paymentHistory 반환 + if (!matches.isEmpty() && !quotations.isEmpty()) { + List paymentDTOS = PaymentDTOConvertor(matches, quotations); + paymentHistoryDTO = + new PortfolioResponse.PaymentHistoryDTO( + portfolio.getAvgPrice(), + portfolio.getMinPrice(), + portfolio.getMaxPrice(), + paymentDTOS + ); + } + return FindByIdDTOConvertor(planner, portfolio, images, priceDTO, paymentHistoryDTO); } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioRestController.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioRestController.java index 4eaedc42..4ab3aaa1 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioRestController.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioRestController.java @@ -48,8 +48,9 @@ public ResponseEntity getPortfolios(@RequestParam @Min(-2) Long cursor, } @GetMapping("/{id}") - public ResponseEntity getPortfolioInDetail(@PathVariable @Min(1) Long id) { - PortfolioResponse.FindByIdDTO portfolio = portfolioService.getPortfolioById(id); + public ResponseEntity getPortfolioInDetail(@PathVariable @Min(1) Long id, + @AuthenticationPrincipal CustomUserDetails userDetails) { + PortfolioResponse.FindByIdDTO portfolio = portfolioService.getPortfolioById(id, userDetails.getUser().getId()); return ResponseEntity.ok().body(ApiUtils.success(portfolio)); } diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java index 3d499029..4ddd663a 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java @@ -18,6 +18,9 @@ import com.kakao.sunsuwedding.portfolio.price.PriceItem; import com.kakao.sunsuwedding.portfolio.price.PriceItemJDBCRepository; import com.kakao.sunsuwedding.portfolio.price.PriceItemJPARepository; +import com.kakao.sunsuwedding.user.base_user.User; +import com.kakao.sunsuwedding.user.base_user.UserJPARepository; +import com.kakao.sunsuwedding.user.constant.Grade; import com.kakao.sunsuwedding.user.constant.Role; import com.kakao.sunsuwedding.user.planner.Planner; import com.kakao.sunsuwedding.user.planner.PlannerJPARepository; @@ -43,6 +46,7 @@ public class PortfolioService { private final MatchJPARepository matchJPARepository; private final QuotationJPARepository quotationJPARepository; private final PlannerJPARepository plannerJPARepository; + private final UserJPARepository userJPARepository; public Pair addPortfolio(PortfolioRequest.AddDTO request, Long plannerId) { // 요청한 플래너 탐색 @@ -160,30 +164,40 @@ private List getFilteredPortfoliosByCursor(CursorRequest request, Pag return portfolioJPARepository.findAll(specification, pageable).getContent(); } - public PortfolioResponse.FindByIdDTO getPortfolioById(Long id) { - List imageItems = imageItemJPARepository.findByPortfolioId(id); + public PortfolioResponse.FindByIdDTO getPortfolioById(Long portfolioId, Long userId) { + // 요청한 유저의 등급을 확인 + User user = userJPARepository.findById(userId) + .orElseThrow(() -> new NotFoundException(BaseException.USER_NOT_FOUND)); + Grade userGrade = user.getGrade(); + + List imageItems = imageItemJPARepository.findByPortfolioId(portfolioId); if (imageItems.isEmpty()) { throw new NotFoundException(BaseException.PORTFOLIO_NOT_FOUND); } Portfolio portfolio = imageItems.get(0).getPortfolio(); Planner planner = imageItems.get(0).getPortfolio().getPlanner(); + + // 플래너 탈퇴 시 조회 X - if (planner == null) { - throw new NotFoundException(BaseException.PLANNER_NOT_FOUND); - } + if (planner == null) { throw new NotFoundException(BaseException.PLANNER_NOT_FOUND); } List images = imageItems .stream() .map(ImageEncoder::encode) .toList(); + List priceItems = priceItemJPARepository.findAllByPortfolioId(portfolioId); - List priceItems = priceItemJPARepository.findAllByPortfolioId(id); + // 기본적으로 매칭 내역과 견적서에는 빈 배열 할당 + List matches = new ArrayList<>(); + List quotations = new ArrayList<>(); - // 거래 내역 조회를 위한 매칭 내역, 견적서 가져오기 - List matches = matchJPARepository.findLatestTenByPlanner(planner); - List matchIds = matches.stream().map(Match::getId).toList(); - List quotations = quotationJPARepository.findAllByMatchIds(matchIds); + // 프리미엄 등급 유저일 경우 최근 거래 내역 조회를 위한 매칭 내역, 견적서 가져오기 + if (userGrade == Grade.PREMIUM) { + matches = matchJPARepository.findLatestTenByPlanner(planner); + List matchIds = matches.stream().map(Match::getId).toList(); + quotations = quotationJPARepository.findAllByMatchIds(matchIds); + } return PortfolioDTOConverter.FindByIdDTOConvertor(planner, portfolio, images, priceItems, matches, quotations); } From c3e5cb86427bdd4c08a0232bf8d86de0a063fc15 Mon Sep 17 00:00:00 2001 From: rizingblare Date: Thu, 19 Oct 2023 22:46:47 +0900 Subject: [PATCH 2/5] fix: Add transactional annotation in addPortfolio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 포트폴리오 추가 기능에 @Transactional 어노테이션 추가 --- .../java/com/kakao/sunsuwedding/portfolio/PortfolioService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java index 4ddd663a..1927fa57 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java @@ -48,6 +48,7 @@ public class PortfolioService { private final PlannerJPARepository plannerJPARepository; private final UserJPARepository userJPARepository; + @Transactional public Pair addPortfolio(PortfolioRequest.AddDTO request, Long plannerId) { // 요청한 플래너 탐색 Planner planner = plannerJPARepository.findById(plannerId) From 02afecef058ba9770fe3911a56c29992b76bcf97 Mon Sep 17 00:00:00 2001 From: rizingblare Date: Thu, 19 Oct 2023 22:56:02 +0900 Subject: [PATCH 3/5] fix: Delete unnecessary check logic in portfolio update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존의 포트폴리오 업데이트 객체 생성 시 변경 확인 로직 삭제 --- .../sunsuwedding/portfolio/PortfolioService.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java index cff72b61..6bccfd7c 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java @@ -223,15 +223,15 @@ public Pair updatePortfolio(PortfolioRequest.UpdateDTO reques .mapToLong(PortfolioRequest.UpdateDTO.ItemDTO::getItemPrice) .sum(); - // 불변 객체 패턴을 고려한 포트폴리오 변경사항 업데이트 + // 포트폴리오 변경사항 업데이트 객체 생성 Portfolio updatedPortfolio = Portfolio.builder() .id(portfolio.getId()) .planner(planner) - .title(request.getTitle() != null ? request.getTitle() : portfolio.getTitle()) - .description(request.getDescription() != null ? request.getDescription() : portfolio.getDescription()) - .location(request.getLocation() != null ? request.getLocation() : portfolio.getLocation()) - .career(request.getCareer() != null ? request.getCareer() : portfolio.getCareer()) - .partnerCompany(request.getPartnerCompany() != null ? request.getPartnerCompany() : portfolio.getPartnerCompany()) + .title(request.getTitle()) + .description(request.getDescription()) + .location(request.getLocation()) + .career(request.getCareer()) + .partnerCompany(request.getPartnerCompany()) .totalPrice(totalPrice) .contractCount(portfolio.getContractCount()) .avgPrice(portfolio.getAvgPrice()) From 15baf03a942040d948704688ca374cc3826a31f1 Mon Sep 17 00:00:00 2001 From: rizingblare Date: Thu, 19 Oct 2023 23:47:09 +0900 Subject: [PATCH 4/5] feat: Change portfolio priceitem update logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Batch Update에서 Delete 후 Batch Insert로 변경 --- .../portfolio/PortfolioService.java | 29 ++++++++++--------- .../price/PriceItemJPARepository.java | 6 ++++ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java index 6bccfd7c..00080db7 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/PortfolioService.java @@ -223,7 +223,7 @@ public Pair updatePortfolio(PortfolioRequest.UpdateDTO reques .mapToLong(PortfolioRequest.UpdateDTO.ItemDTO::getItemPrice) .sum(); - // 포트폴리오 변경사항 업데이트 객체 생성 + // 포트폴리오 변경사항 업데이트 객체 생성 (업데이트 쿼리가 마지막 함수 종료될 때 날아가긴 함) Portfolio updatedPortfolio = Portfolio.builder() .id(portfolio.getId()) .planner(planner) @@ -240,29 +240,30 @@ public Pair updatePortfolio(PortfolioRequest.UpdateDTO reques .build(); portfolioJPARepository.save(updatedPortfolio); - // 해당하는 가격 아이템 탐색 & 업데이트 - List existPriceItems = priceItemJPARepository.findByPortfolioId(portfolio.getId()); - List updatedPriceItems = new ArrayList<>(); - for (int i = 0; i < 3; i++) { - PriceItem priceItem = existPriceItems.get(i); - PortfolioRequest.UpdateDTO.ItemDTO item = request.getItems().get(i); + // 기존의 포트폴리오 가격 항목 일괄 삭제 + // 특이사항: JPQL 안 쓰니까 DELETE Query N개씩 날아감 + priceItemJPARepository.deleteAllByPortfolioId(portfolio.getId()); - PriceItem updatedPriceItem = PriceItem.builder() - .id(priceItem.getId()) + // 업데이트 가격 항목 새로 저장 + List updatedPriceItems = new ArrayList<>(); + for (PortfolioRequest.UpdateDTO.ItemDTO item : request.getItems()) { + PriceItem priceItem = PriceItem.builder() .portfolio(portfolio) - .itemTitle(item.getItemTitle() != null ? item.getItemTitle() : priceItem.getItemTitle()) - .itemPrice(item.getItemPrice() != null ? item.getItemPrice() : priceItem.getItemPrice()) + .itemTitle(item.getItemTitle()) + .itemPrice(item.getItemPrice()) .build(); - updatedPriceItems.add(updatedPriceItem); + updatedPriceItems.add(priceItem); } - priceItemJDBCRepository.batchUpdatePriceItems(updatedPriceItems); + priceItemJDBCRepository.batchInsertPriceItems(updatedPriceItems); + + // 삭제 후 삽입 로직으로 변경 ㅠㅠ 열심히 만든건데 못쓰게 되버림 엉엉 + // priceItemJDBCRepository.batchUpdatePriceItems(updatedPriceItems); // 이미지 처리 로직에 활용하기 위해 포트폴리오 객체 리턴 return Pair.of(updatedPortfolio, planner); } - @Transactional public void updateConfirmedPrices(Planner planner) { List matches = matchJPARepository.findAllByPlanner(planner); Optional portfolioPS = portfolioJPARepository.findByPlanner(planner); diff --git a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/price/PriceItemJPARepository.java b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/price/PriceItemJPARepository.java index b1132dc3..72cdbffd 100644 --- a/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/price/PriceItemJPARepository.java +++ b/sunsu-wedding/src/main/java/com/kakao/sunsuwedding/portfolio/price/PriceItemJPARepository.java @@ -2,6 +2,7 @@ import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -16,5 +17,10 @@ public interface PriceItemJPARepository extends JpaRepository { @EntityGraph("PriceItemWithPortfolioAndPlanner") List findAllByPortfolioId(Long id); + void deleteAllByPortfolioPlannerId(Long id); + + @Modifying + @Query("delete from PriceItem p where p.portfolio.id = :portfolioId") + void deleteAllByPortfolioId(@Param("portfolioId") Long portfolioId); } From 6e74588b5f9db5f8f93d2100f0300dd6c0c53b99 Mon Sep 17 00:00:00 2001 From: rizingblare Date: Fri, 20 Oct 2023 13:54:12 +0900 Subject: [PATCH 5/5] feat: Modifying test code as change getPortfolioById MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 유저 등급별 상세 조회 로직 변화에 따른 테스트 코드 수정 --- .../portfolio/PortfolioControllerTest.java | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/portfolio/PortfolioControllerTest.java b/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/portfolio/PortfolioControllerTest.java index 8434fd60..3e2a489d 100644 --- a/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/portfolio/PortfolioControllerTest.java +++ b/sunsu-wedding/src/test/java/com/kakao/sunsuwedding/portfolio/PortfolioControllerTest.java @@ -228,9 +228,10 @@ public void get_portfolios_success_test_last_page() throws Exception { // ============ 포트폴리오 상세 조회 테스트 ============ - @DisplayName("포트폴리오 상세 조회 성공 테스트") + @DisplayName("포트폴리오 상세 조회 성공 테스트 - 예비부부 (PREMIUM 등급)") @Test - public void get_portfolio_by_id_success_test() throws Exception { + @WithUserDetails("couple2@gmail.com") + public void get_portfolio_by_id_premium_success_test() throws Exception { //given Long id = 1L; // when @@ -252,8 +253,33 @@ public void get_portfolio_by_id_success_test() throws Exception { result.andExpect(MockMvcResultMatchers.jsonPath("$.response.paymentsHistory.payments[0].confirmedAt").value("2023-10")); } + @DisplayName("포트폴리오 상세 조회 성공 테스트 - 플래너 (NORMAL 등급)") + @Test + @WithUserDetails("planner0@gmail.com") + public void get_portfolio_by_id_normal_success_test() throws Exception { + //given + Long id = 1L; + // when + ResultActions result = mockMvc.perform( + MockMvcRequestBuilders + .get("/portfolios/{id}", id) + ); + + String responseBody = result.andReturn().getResponse().getContentAsString(); + logger.debug("테스트 : " + responseBody); + + // then + result.andExpect(MockMvcResultMatchers.jsonPath("$.success").value("true")); + result.andExpect(MockMvcResultMatchers.jsonPath("$.response.id").value(1)); + result.andExpect(MockMvcResultMatchers.jsonPath("$.response.userId").value(2)); + result.andExpect(MockMvcResultMatchers.jsonPath("$.response.priceInfo.items[0].itemTitle").value("스튜디오1")); + result.andExpect(MockMvcResultMatchers.jsonPath("$.response.priceInfo.items[0].itemPrice").value(500000)); + result.andExpect(MockMvcResultMatchers.jsonPath("$.response.paymentsHistory").isEmpty()); + } + @DisplayName("포트폴리오 상세 조회 실패 테스트 1 - 존재하지 않는 포트폴리오") @Test + @WithUserDetails("couple2@gmail.com") public void get_portfolio_by_id_fail_test_portfolio_not_found() throws Exception { //given Long id = 30L; @@ -274,6 +300,7 @@ public void get_portfolio_by_id_fail_test_portfolio_not_found() throws Exception @DisplayName("포트폴리오 상세 조회 실패 테스트 2 - 탈퇴한 플래너") @Test + @WithUserDetails("couple2@gmail.com") public void get_portfolio_by_id_fail_test_planner_not_found() throws Exception { //given Long id = 15L; // 탈퇴한 플래너의 포트폴리오 id