From 69cb5293bab7329c12a18f4376351540a2a59166 Mon Sep 17 00:00:00 2001 From: yonghwankim-dev Date: Tue, 9 Apr 2024 15:38:23 +0900 Subject: [PATCH] =?UTF-8?q?#292=20fix:=20=EB=AA=A9=ED=91=9C=EC=88=98?= =?UTF-8?q?=EC=9D=B5=EB=A5=A0=20=EC=95=8C=EB=A6=BC=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 평가금액을 기준으로 수정 --- .../fineants/domain/fcm_token/FcmToken.java | 2 +- .../fineants/domain/portfolio/Portfolio.java | 40 ++------- .../event/PortfolioEventListener.java | 7 +- .../service/NotificationService.java | 2 - .../service/NotificationServiceTest.java | 82 ++++++++++++++----- 5 files changed, 77 insertions(+), 56 deletions(-) diff --git a/src/main/java/codesquad/fineants/domain/fcm_token/FcmToken.java b/src/main/java/codesquad/fineants/domain/fcm_token/FcmToken.java index e6acad22b..b8da2e348 100644 --- a/src/main/java/codesquad/fineants/domain/fcm_token/FcmToken.java +++ b/src/main/java/codesquad/fineants/domain/fcm_token/FcmToken.java @@ -22,7 +22,7 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -@ToString +@ToString(exclude = "member") @Table(name = "fcm_token", uniqueConstraints = { @UniqueConstraint(name = "token_member_id_unique", columnNames = {"token", "member_id"}) }) diff --git a/src/main/java/codesquad/fineants/domain/portfolio/Portfolio.java b/src/main/java/codesquad/fineants/domain/portfolio/Portfolio.java index 497c861d9..1ea76f4a5 100644 --- a/src/main/java/codesquad/fineants/domain/portfolio/Portfolio.java +++ b/src/main/java/codesquad/fineants/domain/portfolio/Portfolio.java @@ -1,9 +1,7 @@ package codesquad.fineants.domain.portfolio; -import java.math.BigInteger; import java.time.LocalDate; import java.util.ArrayList; -import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -37,7 +35,6 @@ import codesquad.fineants.domain.notification.type.NotificationType; import codesquad.fineants.domain.portfolio_gain_history.PortfolioGainHistory; import codesquad.fineants.domain.portfolio_holding.PortfolioHolding; -import codesquad.fineants.domain.purchase_history.PurchaseHistory; import codesquad.fineants.spring.api.kis.manager.CurrentPriceManager; import codesquad.fineants.spring.api.notification.manager.NotificationSentManager; import codesquad.fineants.spring.api.notification.response.NotifyMessage; @@ -133,8 +130,7 @@ public double calculateTotalGainRate() { if (totalInvestmentAmount.isZero()) { return 0; } - Money rate = calculateTotalGain().divide(totalInvestmentAmount).multiply(BigInteger.valueOf(100L)); - return rate.getAmount().doubleValue(); + return calculateTotalGain().divide(totalInvestmentAmount).toPercentage(); } // 포트폴리오 총 손익 = 모든 종목 총 손익의 합계 @@ -270,37 +266,17 @@ public void changeMaximumLossNotification(Boolean isActive) { this.maximumLossIsActive = isActive; } + // 포트폴리오가 목표수익금액에 도달했는지 검사 (평가금액이 목표수익금액보다 같거나 큰 경우) public boolean reachedTargetGain() { - List histories = portfolioHoldings.stream() - .map(PortfolioHolding::getPurchaseHistory) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - return reachedTargetGain(histories); - } - - // 포트폴리오가 목표수익금액에 도달했는지 검사 - public boolean reachedTargetGain(List histories) { - Money totalGain = histories.stream() - .map(PurchaseHistory::calculateGain) - .reduce(Money.zero(), Money::add); - log.debug("reachedTargetGain.totalGain : {}", totalGain); - return budget.add(totalGain).compareTo(targetGain) >= 0; + Money currentValuation = calculateTotalCurrentValuation(); + log.debug("reachedTargetGain currentValuation={}, targetGain={}", currentValuation, targetGain); + return currentValuation.compareTo(targetGain) >= 0; } - // 포트폴리오가 최대손실금액에 도달했는지 검사 + // 포트폴리오가 최대손실금액에 도달했는지 검사 (예산 + 총손익이 최대손실금액보다 작은 경우) public boolean reachedMaximumLoss() { - return reachedMaximumLoss(portfolioHoldings.stream() - .map(PortfolioHolding::getPurchaseHistory) - .flatMap(Collection::stream) - .collect(Collectors.toList())); - } - - // 포트폴리오가 최대손실금액에 도달했는지 검사 - public boolean reachedMaximumLoss(List histories) { - Money totalGain = histories.stream() - .map(PurchaseHistory::calculateGain) - .reduce(Money.zero(), Money::add); - log.debug("totalGain : {}", totalGain); + Money totalGain = calculateTotalGain(); + log.debug("reachedTargetGain totalGain={}", totalGain); return budget.add(totalGain).compareTo(maximumLoss) <= 0; } diff --git a/src/main/java/codesquad/fineants/spring/api/notification/event/PortfolioEventListener.java b/src/main/java/codesquad/fineants/spring/api/notification/event/PortfolioEventListener.java index a65b598a6..56a0a9bf3 100644 --- a/src/main/java/codesquad/fineants/spring/api/notification/event/PortfolioEventListener.java +++ b/src/main/java/codesquad/fineants/spring/api/notification/event/PortfolioEventListener.java @@ -4,6 +4,7 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; +import codesquad.fineants.spring.api.notification.response.PortfolioNotifyMessagesResponse; import codesquad.fineants.spring.api.notification.service.NotificationService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -19,13 +20,15 @@ public class PortfolioEventListener { @Async @EventListener public void notifyTargetGain(CurrentPriceEvent event) { - notificationService.notifyTargetGain(); + PortfolioNotifyMessagesResponse response = notificationService.notifyTargetGain(); + log.debug("목표수익률 알림 전송 결과 : {}", response); } // 현재가 변경 이벤트가 발생하면 포트폴리오 최대손실율에 도달하면 푸시 알림 @Async @EventListener public void notifyPortfolioMaxLossMessages(CurrentPriceEvent event) { - notificationService.notifyMaxLoss(); + PortfolioNotifyMessagesResponse response = notificationService.notifyMaxLoss(); + log.debug("최대손실율 알림 전송 결과 : {}", response); } } diff --git a/src/main/java/codesquad/fineants/spring/api/notification/service/NotificationService.java b/src/main/java/codesquad/fineants/spring/api/notification/service/NotificationService.java index 7bcfee12c..ecd807c90 100644 --- a/src/main/java/codesquad/fineants/spring/api/notification/service/NotificationService.java +++ b/src/main/java/codesquad/fineants/spring/api/notification/service/NotificationService.java @@ -103,7 +103,6 @@ public PortfolioNotifyMessagesResponse notifyTargetGain() { List portfolios = portfolioRepository.findAllWithAll().stream() .peek(portfolio -> portfolio.applyCurrentPriceAllHoldingsBy(currentPriceManager)) .collect(Collectors.toList()); - Function> sentFunction = item -> CompletableFuture.supplyAsync( () -> { sentManager.addTargetGainSendHistory(Long.valueOf(item.getReferenceId())); @@ -119,7 +118,6 @@ public PortfolioNotifyMessagesResponse notifyTargetGainBy(Long portfolioId) { .peek(p -> p.applyCurrentPriceAllHoldingsBy(currentPriceManager)) .findFirst() .orElseThrow(() -> new FineAntsException(PortfolioErrorCode.NOT_FOUND_PORTFOLIO)); - Function> sentFunction = item -> CompletableFuture.supplyAsync( () -> { sentManager.addTargetGainSendHistory(Long.valueOf(item.getReferenceId())); diff --git a/src/test/java/codesquad/fineants/spring/api/notification/service/NotificationServiceTest.java b/src/test/java/codesquad/fineants/spring/api/notification/service/NotificationServiceTest.java index 150dbb7d3..4dbd5dd4a 100644 --- a/src/test/java/codesquad/fineants/spring/api/notification/service/NotificationServiceTest.java +++ b/src/test/java/codesquad/fineants/spring/api/notification/service/NotificationServiceTest.java @@ -126,13 +126,7 @@ void tearDown() { void notifyTargetGainBy() { // given Member member = memberRepository.save(createMember()); - notificationPreferenceRepository.save(NotificationPreference.builder() - .browserNotify(true) - .targetGainNotify(true) - .maxLossNotify(true) - .targetPriceNotify(true) - .member(member) - .build()); + notificationPreferenceRepository.save(createNotificationPreference(member)); Portfolio portfolio = portfolioRepository.save(createPortfolio(member)); Stock stock = stockRepository.save(createStock()); PortfolioHolding portfolioHolding = portfolioHoldingRepository.save(createPortfolioHolding(portfolio, stock)); @@ -140,8 +134,7 @@ void notifyTargetGainBy() { fcmRepository.save(FcmToken.builder() .latestActivationTime(LocalDateTime.now()) - .token( - "fahY76rRwq8HGy0m1lwckx:APA91bEovbLJyqdSRq8MWDbsIN8sbk90JiNHbIBs6rDoiOKeC-aa5P1QydiRa6okGrIZELrxx_cYieWUN44iX-AD6jma-cYRUR7e3bTMXwkqZFLRZh5s7-bcksGniB7Y2DkoONHtSjos") + .token("fcmToken") .member(member) .build()); @@ -160,6 +153,48 @@ void notifyTargetGainBy() { ); } + @DisplayName("목표수익률에 도달하지 않아서 알림을 보내지 않는다") + @Test + void notifyTargetGainBy_whenNoTargetGain_thenNotSendNotification() { + // given + Member member = memberRepository.save(createMember()); + notificationPreferenceRepository.save(createNotificationPreference(member)); + Portfolio portfolio = portfolioRepository.save( + createPortfolio(member, Money.from(1000000L), Money.from(1100000L), Money.from(900000L))); + Stock samsung = stockRepository.save(createStock()); + Stock ccs = stockRepository.save( + createStack("씨씨에스충북방송", "066790", "KOREA CABLE T.V CHUNG-BUK SYSTEM CO.,LTD.", "KR7066790007", "방송서비스", + Market.KOSDAQ)); + + PortfolioHolding sumsungHolding = portfolioHoldingRepository.save(createPortfolioHolding(portfolio, samsung)); + purchaseHistoryRepository.save(createPurchaseHistory(sumsungHolding, 12L, 60000.0)); + + PortfolioHolding ccsHolding = portfolioHoldingRepository.save(createPortfolioHolding(portfolio, ccs)); + purchaseHistoryRepository.save(createPurchaseHistory(ccsHolding, 15L, 2000.0)); + + fcmRepository.save(FcmToken.builder() + .latestActivationTime(LocalDateTime.now()) + .token("fcmToken") + .member(member) + .build()); + + given(firebaseMessagingService.send(any(Message.class))) + .willReturn(Optional.of("projects/fineants-404407/messages/4754d355-5d5d-4f14-a642-75fecdb91fa5")); + given(manager.getCurrentPrice(samsung.getTickerSymbol())) + .willReturn(Optional.of(Money.from(83300L))); + given(manager.getCurrentPrice(ccs.getTickerSymbol())) + .willReturn(Optional.of(Money.from(3750L))); + + // when + PortfolioNotifyMessagesResponse response = service.notifyTargetGainBy(portfolio.getId()); + + // then + assertAll( + () -> assertThat(response.getNotifications()).hasSize(0), + () -> assertThat(notificationRepository.findAllByMemberId(member.getId())).hasSize(0) + ); + } + @DisplayName("토큰이 유효하지 않아서 목표 수익률 알림을 보낼수 없다") @Test void notifyTargetGainBy_whenInvalidFcmToken_thenDeleteFcmToken() { @@ -295,7 +330,7 @@ void notifyMaxLoss_whenInvalidFcmToken_thenDeleteFcmToken() throws FirebaseMessa Portfolio portfolio = portfolioRepository.save(createPortfolio(member)); Stock stock = stockRepository.save(createStock()); PortfolioHolding portfolioHolding = portfolioHoldingRepository.save(createPortfolioHolding(portfolio, stock)); - purchaseHistoryRepository.save(createPurchaseHistory(portfolioHolding, 50L, 60000.0)); + purchaseHistoryRepository.save(createPurchaseHistory(portfolioHolding, 10L, 60000.0)); FcmToken fcmToken = fcmRepository.save(FcmToken.builder() .latestActivationTime(LocalDateTime.now()) @@ -614,12 +649,16 @@ private Member createMember(String nickname, String email) { } private Portfolio createPortfolio(Member member) { + return createPortfolio(member, Money.from(1000000L), Money.from(1500000L), Money.from(900000L)); + } + + private Portfolio createPortfolio(Member member, Money budget, Money targetGain, Money maxLoss) { return Portfolio.builder() .name("내꿈은 워렌버핏") .securitiesFirm("토스") - .budget(Money.from(1000000L)) - .targetGain(Money.from(1500000L)) - .maximumLoss(Money.from(900000L)) + .budget(budget) + .targetGain(targetGain) + .maximumLoss(maxLoss) .member(member) .targetGainIsActive(true) .maximumLossIsActive(true) @@ -627,13 +666,18 @@ private Portfolio createPortfolio(Member member) { } private Stock createStock() { + return createStack("삼성전자보통주", "005930", "SamsungElectronics", "KR7005930003", "전기전자", Market.KOSPI); + } + + private Stock createStack(String companyName, String tickerSymbol, String companyNameEng, String stockCode, + String sector, Market market) { return Stock.builder() - .companyName("삼성전자보통주") - .tickerSymbol("005930") - .companyNameEng("SamsungElectronics") - .stockCode("KR7005930003") - .sector("전기전자") - .market(Market.KOSPI) + .companyName(companyName) + .tickerSymbol(tickerSymbol) + .companyNameEng(companyNameEng) + .stockCode(stockCode) + .sector(sector) + .market(market) .build(); }