diff --git a/src/main/java/org/umc/valuedi/domain/asset/service/AssetBalanceService.java b/src/main/java/org/umc/valuedi/domain/asset/service/AssetBalanceService.java deleted file mode 100644 index 588954e..0000000 --- a/src/main/java/org/umc/valuedi/domain/asset/service/AssetBalanceService.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.umc.valuedi.domain.asset.service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import org.umc.valuedi.domain.asset.dto.res.AssetResDTO; -import org.umc.valuedi.domain.asset.entity.BankAccount; -import org.umc.valuedi.domain.asset.entity.BankTransaction; -import org.umc.valuedi.domain.asset.repository.bank.bankAccount.BankAccountRepository; -import org.umc.valuedi.domain.asset.repository.bank.bankTransaction.BankTransactionRepository; -import org.umc.valuedi.domain.asset.service.command.AssetFetchService; -import org.umc.valuedi.domain.goal.exception.GoalException; -import org.umc.valuedi.domain.goal.exception.code.GoalErrorCode; -import org.umc.valuedi.domain.member.entity.Member; -import org.umc.valuedi.domain.member.exception.MemberException; -import org.umc.valuedi.domain.member.exception.code.MemberErrorCode; -import org.umc.valuedi.domain.member.repository.MemberRepository; - -@Slf4j -@Service -@RequiredArgsConstructor -public class AssetBalanceService { - - private final AssetFetchService assetFetchService; - private final MemberRepository memberRepository; - private final BankAccountRepository bankAccountRepository; - private final BankTransactionRepository bankTransactionRepository; - - @Transactional(propagation = Propagation.NOT_SUPPORTED) - public Long syncAndGetLatestBalance(Long memberId, Long accountId) { - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND)); - - try { - AssetResDTO.AssetSyncResult result = assetFetchService.fetchAndSaveLatestData(member); - - // 1. 동기화 결과 DTO에 방금 수집한 실시간 잔액이 있다면 DB 조회 없이 즉시 반환 (레이스 컨디션 방지) - if (result.hasLatestBalanceFor(accountId)) { - log.info("[AssetBalanceService] 실시간 동기화 데이터 사용. AccountID: {}", accountId); - return result.getLatestBalanceFor(accountId); - } - - } catch (Exception e) { - log.warn("[AssetBalanceService] 잔액 조회 중 자산 동기화 실패 (기존 DB 잔액 사용): {}", e.getMessage()); - } - - // 2. Fallback: 실시간 데이터가 없거나 동기화 실패 시 DB에서 최신 데이터 조회 - BankAccount account = bankAccountRepository.findByIdAndMemberId(accountId, memberId) - .orElseThrow(() -> new GoalException(GoalErrorCode.ACCOUNT_NOT_FOUND)); - - return bankTransactionRepository.findTopByBankAccountOrderByTrDatetimeDesc(account) - .map(BankTransaction::getAfterBalance) - .orElse(account.getBalanceAmount()); - } -} \ No newline at end of file diff --git a/src/main/java/org/umc/valuedi/domain/goal/controller/GoalController.java b/src/main/java/org/umc/valuedi/domain/goal/controller/GoalController.java index 868ffe2..685a18d 100644 --- a/src/main/java/org/umc/valuedi/domain/goal/controller/GoalController.java +++ b/src/main/java/org/umc/valuedi/domain/goal/controller/GoalController.java @@ -11,10 +11,10 @@ import org.umc.valuedi.domain.goal.enums.GoalStatus; import org.umc.valuedi.domain.goal.enums.GoalSort; import org.umc.valuedi.domain.goal.exception.code.GoalSuccessCode; -import org.umc.valuedi.domain.goal.service.GoalLedgerFacade; import org.umc.valuedi.domain.goal.service.command.GoalAccountCommandService; import org.umc.valuedi.domain.goal.service.command.GoalCommandService; import org.umc.valuedi.domain.goal.service.query.GoalAccountQueryService; +import org.umc.valuedi.domain.goal.service.query.GoalLedgerQueryService; import org.umc.valuedi.domain.goal.service.query.GoalListQueryService; import org.umc.valuedi.domain.goal.service.query.GoalQueryService; import org.umc.valuedi.domain.ledger.dto.response.LedgerListResponse; @@ -31,7 +31,7 @@ public class GoalController implements GoalControllerDocs{ private final GoalListQueryService goalListQueryService; private final GoalAccountQueryService goalAccountQueryService; private final GoalAccountCommandService goalAccountCommandService; - private final GoalLedgerFacade goalLedgerFacade; + private final GoalLedgerQueryService goalLedgerQueryService; // 목표 추가 @PostMapping @@ -158,7 +158,7 @@ public ApiResponse getGoalLedgers( ) { return ApiResponse.onSuccess( GoalSuccessCode.GOAL_LEDGER_LIST_FETCHED, - goalLedgerFacade.getGoalLedgerTransactions(memberId, goalId, page, size) + goalLedgerQueryService.getGoalLedgerTransactions(memberId, goalId, page, size) ); } } diff --git a/src/main/java/org/umc/valuedi/domain/goal/converter/GoalConverter.java b/src/main/java/org/umc/valuedi/domain/goal/converter/GoalConverter.java index 51e6175..621dead 100644 --- a/src/main/java/org/umc/valuedi/domain/goal/converter/GoalConverter.java +++ b/src/main/java/org/umc/valuedi/domain/goal/converter/GoalConverter.java @@ -18,7 +18,7 @@ public class GoalConverter { - public static Goal toEntity(Member member,BankAccount bankAccount, GoalCreateRequestDto req, Long startAmount) { + public static Goal toEntity(Member member,BankAccount bankAccount, GoalCreateRequestDto req) { return Goal.builder() .member(member) .bankAccount(bankAccount) @@ -26,7 +26,6 @@ public static Goal toEntity(Member member,BankAccount bankAccount, GoalCreateReq .startDate(req.startDate()) .endDate(req.endDate()) .targetAmount(req.targetAmount()) - .startAmount(startAmount) .status(GoalStatus.ACTIVE) .completedAt(null) .color(GoalStyleCatalog.normalizeColor(req.colorCode())) @@ -82,7 +81,6 @@ public static GoalCreateResponseDto toCreateDto(Goal goal) { goal.getId(), goal.getTitle(), goal.getTargetAmount(), - goal.getStartAmount(), goal.getStartDate(), goal.getEndDate(), remainingDays, @@ -93,7 +91,7 @@ public static GoalCreateResponseDto toCreateDto(Goal goal) { public static GoalListResponseDto.GoalSummaryDto toSummaryDto( Goal goal, - Long savedAmount, + Long currentBalance, int achievementRate ) { Long remainingDays = calcRemainingDays(goal.getEndDate()); @@ -101,7 +99,7 @@ public static GoalListResponseDto.GoalSummaryDto toSummaryDto( return new GoalListResponseDto.GoalSummaryDto( goal.getId(), goal.getTitle(), - savedAmount, + currentBalance, remainingDays, achievementRate, goal.getStatus(), @@ -110,7 +108,7 @@ public static GoalListResponseDto.GoalSummaryDto toSummaryDto( ); } - public static GoalDetailResponseDto toDetailDto(Goal goal, Long savedAmount, int achievementRate) { + public static GoalDetailResponseDto toDetailDto(Goal goal, Long currentBalance, int achievementRate) { long remainingDays = calcRemainingDays(goal.getEndDate()); GoalDetailResponseDto.AccountDto accountDto = null; @@ -122,7 +120,7 @@ public static GoalDetailResponseDto toDetailDto(Goal goal, Long savedAmount, int return new GoalDetailResponseDto( goal.getId(), goal.getTitle(), - savedAmount, + currentBalance, goal.getTargetAmount(), goal.getStartDate(), goal.getEndDate(), diff --git a/src/main/java/org/umc/valuedi/domain/goal/dto/response/GoalCreateResponseDto.java b/src/main/java/org/umc/valuedi/domain/goal/dto/response/GoalCreateResponseDto.java index d25040b..238389d 100644 --- a/src/main/java/org/umc/valuedi/domain/goal/dto/response/GoalCreateResponseDto.java +++ b/src/main/java/org/umc/valuedi/domain/goal/dto/response/GoalCreateResponseDto.java @@ -16,9 +16,6 @@ public record GoalCreateResponseDto( @Schema(description = "목표 금액", example = "1000000") Long targetAmount, - @Schema(description = "시작 금액", example = "1000") - Long startAmount, - @Schema(description = "시작일", example = "2026-01-01") LocalDate startDate, diff --git a/src/main/java/org/umc/valuedi/domain/goal/dto/response/GoalDetailResponseDto.java b/src/main/java/org/umc/valuedi/domain/goal/dto/response/GoalDetailResponseDto.java index 7644225..2c66914 100644 --- a/src/main/java/org/umc/valuedi/domain/goal/dto/response/GoalDetailResponseDto.java +++ b/src/main/java/org/umc/valuedi/domain/goal/dto/response/GoalDetailResponseDto.java @@ -14,8 +14,8 @@ public record GoalDetailResponseDto( @Schema(description = "목표 제목", example = "여행 자금 모으기") String title, - @Schema(description = "현재까지 모은 금액", example = "300000") - Long savedAmount, + @Schema(description = "현재 계좌 잔액", example = "300000") + Long currentBalance, @Schema(description = "목표 금액", example = "1000000") Long targetAmount, diff --git a/src/main/java/org/umc/valuedi/domain/goal/dto/response/GoalListResponseDto.java b/src/main/java/org/umc/valuedi/domain/goal/dto/response/GoalListResponseDto.java index 8bcefa5..8a4cbc2 100644 --- a/src/main/java/org/umc/valuedi/domain/goal/dto/response/GoalListResponseDto.java +++ b/src/main/java/org/umc/valuedi/domain/goal/dto/response/GoalListResponseDto.java @@ -21,8 +21,8 @@ public record GoalSummaryDto( @Schema(description = "목표 제목", example = "여행 자금 모으기") String title, - @Schema(description = "모은 금액", example = "700000") - Long savedAmount, + @Schema(description = "현재 계좌 잔액", example = "700000") + Long currentBalance, @Schema(description = "남은 일수", example = "30") Long remainingDays, diff --git a/src/main/java/org/umc/valuedi/domain/goal/entity/Goal.java b/src/main/java/org/umc/valuedi/domain/goal/entity/Goal.java index 0d6c569..4de34f0 100644 --- a/src/main/java/org/umc/valuedi/domain/goal/entity/Goal.java +++ b/src/main/java/org/umc/valuedi/domain/goal/entity/Goal.java @@ -46,9 +46,6 @@ public class Goal extends BaseEntity { @Column(name = "target_amount", nullable = false) private Long targetAmount; - @Column(name = "start_amount", nullable = false) - private Long startAmount; - @Enumerated(EnumType.STRING) @Column(name = "status", nullable = false, length = 20) private GoalStatus status; @@ -80,7 +77,6 @@ public void changeEndDate(LocalDate endDate) { public void changeTargetAmount(Long targetAmount) { this.targetAmount = targetAmount; } - public void changeStartAmount(Long startAmount) { this.startAmount = startAmount; } public void changeColor(String color) { this.color = color; } diff --git a/src/main/java/org/umc/valuedi/domain/goal/service/GoalAchievementRateService.java b/src/main/java/org/umc/valuedi/domain/goal/service/GoalAchievementRateService.java index 3ae01fe..5302f28 100644 --- a/src/main/java/org/umc/valuedi/domain/goal/service/GoalAchievementRateService.java +++ b/src/main/java/org/umc/valuedi/domain/goal/service/GoalAchievementRateService.java @@ -5,14 +5,11 @@ @Service public class GoalAchievementRateService { - /** - * @return 0~100 (정수) - */ - public int calculateRate(Long savedAmount, Long targetAmount) { + public int calculateRate(Long currentBalance, Long targetAmount) { if (targetAmount <= 0) return 0; - if (savedAmount <= 0) return 0; + if (currentBalance <= 0) return 0; - double rate = (savedAmount * 100.0) / targetAmount; + double rate = (currentBalance * 100.0) / targetAmount; if (rate < 0) rate = 0; if (rate > 100) rate = 100; return (int) Math.floor(rate); diff --git a/src/main/java/org/umc/valuedi/domain/goal/service/GoalLedgerFacade.java b/src/main/java/org/umc/valuedi/domain/goal/service/GoalLedgerFacade.java deleted file mode 100644 index 09611c7..0000000 --- a/src/main/java/org/umc/valuedi/domain/goal/service/GoalLedgerFacade.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.umc.valuedi.domain.goal.service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.umc.valuedi.domain.asset.dto.res.AssetResDTO; -import org.umc.valuedi.domain.asset.service.command.AssetFetchService; -import org.umc.valuedi.domain.goal.entity.Goal; -import org.umc.valuedi.domain.goal.exception.GoalException; -import org.umc.valuedi.domain.goal.exception.code.GoalErrorCode; -import org.umc.valuedi.domain.goal.repository.GoalRepository; -import org.umc.valuedi.domain.goal.service.query.GoalLedgerQueryService; -import org.umc.valuedi.domain.ledger.dto.response.LedgerListResponse; -import org.umc.valuedi.domain.ledger.service.command.LedgerSyncService; - -import java.time.LocalDate; - -@Slf4j -@Service -@RequiredArgsConstructor -public class GoalLedgerFacade { - - private final GoalRepository goalRepository; - private final AssetFetchService assetFetchService; - private final LedgerSyncService ledgerSyncService; - private final GoalLedgerQueryService goalLedgerQueryService; - - /** - * 목표 거래내역 조회 (동기화 포함) - * 트랜잭션을 분리하여 최신 데이터를 조회할 수 있도록 함 - */ - public LedgerListResponse getGoalLedgerTransactions(Long memberId, Long goalId, int page, int size) { - // 1. 목표 정보 조회 (검증용) - Goal goal = goalRepository.findById(goalId) - .orElseThrow(() -> new GoalException(GoalErrorCode.GOAL_NOT_FOUND)); - - if (!goal.getMember().getId().equals(memberId)) { - throw new GoalException(GoalErrorCode.GOAL_FORBIDDEN); - } - - // 2. 자산 및 가계부 동기화 (각각 별도의 트랜잭션으로 실행됨) - try { - // 자산 동기화 (REQUIRES_NEW) - // 여기서 최신 거래내역을 DB에 저장함 - assetFetchService.fetchAndSaveLatestData(goal.getMember()); - - // 가계부 동기화 (REQUIRES_NEW) - // 목표 기간 내의 데이터가 누락되지 않도록, 목표 시작일 ~ 오늘(또는 종료일)까지 동기화 수행 - LocalDate fromDate = goal.getStartDate(); - LocalDate toDate = LocalDate.now(); - - // 목표 종료일이 오늘보다 과거라면 종료일까지, 아니면 오늘까지 - if (goal.getEndDate().isBefore(toDate)) { - toDate = goal.getEndDate(); - } - - // 시작일이 종료일보다 늦으면(미래 목표 등) 동기화 수행 안 함 - if (!fromDate.isAfter(toDate)) { - ledgerSyncService.syncTransactionsAndUpdateMember(memberId, fromDate, toDate); - } - - } catch (Exception e) { - log.warn("목표 거래내역 조회 중 동기화 실패 (기존 데이터로 조회): {}", e.getMessage()); - } - - // 3. 최종 조회 (새로운 트랜잭션) - return goalLedgerQueryService.getGoalLedgerTransactions(memberId, goalId, page, size); - } -} diff --git a/src/main/java/org/umc/valuedi/domain/goal/service/GoalStatusChangeService.java b/src/main/java/org/umc/valuedi/domain/goal/service/GoalStatusChangeService.java index 94b4a2f..cca2468 100644 --- a/src/main/java/org/umc/valuedi/domain/goal/service/GoalStatusChangeService.java +++ b/src/main/java/org/umc/valuedi/domain/goal/service/GoalStatusChangeService.java @@ -1,11 +1,10 @@ package org.umc.valuedi.domain.goal.service; -import jakarta.transaction.Transactional; +import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.umc.valuedi.domain.asset.entity.BankAccount; -import org.umc.valuedi.domain.asset.service.AssetBalanceService; import org.umc.valuedi.domain.goal.entity.Goal; import org.umc.valuedi.domain.goal.enums.GoalStatus; import org.umc.valuedi.domain.goal.repository.GoalRepository; @@ -20,7 +19,6 @@ public class GoalStatusChangeService { private final GoalRepository goalRepository; - private final AssetBalanceService assetBalanceService; // 전체 목표 상태 갱신 (스케줄러용) public void refreshGoalStatuses() { @@ -32,11 +30,10 @@ public void refreshGoalStatuses() { if (account == null || !account.getIsActive()) { continue; } - // 최신 잔액 조회 (동기화 포함) - Long currentBalance = assetBalanceService.syncAndGetLatestBalance(goal.getMember().getId(), account.getId()); - long savedAmount = currentBalance - goal.getStartAmount(); + // DB에 저장된 계좌 잔액 사용 + Long currentBalance = account.getBalanceAmount(); - checkAndUpdateStatus(goal, savedAmount); + checkAndUpdateStatus(goal, currentBalance); } catch (Exception e) { log.error("목표 상태 갱신 중 오류 발생. Goal ID: {}", goal.getId(), e); } @@ -44,12 +41,12 @@ public void refreshGoalStatuses() { } // 단일 목표 상태 갱신 (조회 시 호출용) - public void checkAndUpdateStatus(Goal goal, long savedAmount) { + public void checkAndUpdateStatus(Goal goal, long currentBalance) { if (goal.getStatus() != GoalStatus.ACTIVE) { return; } - boolean isTargetReached = savedAmount >= goal.getTargetAmount(); + boolean isTargetReached = currentBalance >= goal.getTargetAmount(); // 목표 종료일이 오늘보다 이전이면 만료된 것으로 판단 (종료일 당일까지는 진행 중) boolean isExpired = goal.getEndDate().isBefore(LocalDate.now()); diff --git a/src/main/java/org/umc/valuedi/domain/goal/service/command/GoalCommandService.java b/src/main/java/org/umc/valuedi/domain/goal/service/command/GoalCommandService.java index 7c22971..0ef5698 100644 --- a/src/main/java/org/umc/valuedi/domain/goal/service/command/GoalCommandService.java +++ b/src/main/java/org/umc/valuedi/domain/goal/service/command/GoalCommandService.java @@ -6,7 +6,6 @@ import org.springframework.transaction.annotation.Transactional; import org.umc.valuedi.domain.asset.entity.BankAccount; import org.umc.valuedi.domain.asset.repository.bank.bankAccount.BankAccountRepository; -import org.umc.valuedi.domain.asset.service.AssetBalanceService; import org.umc.valuedi.domain.goal.converter.GoalConverter; import org.umc.valuedi.domain.goal.dto.request.GoalCreateRequestDto; import org.umc.valuedi.domain.goal.dto.request.GoalUpdateRequestDto; @@ -31,7 +30,6 @@ public class GoalCommandService { private final GoalRepository goalRepository; private final MemberRepository memberRepository; private final BankAccountRepository bankAccountRepository; - private final AssetBalanceService assetBalanceService; // 목표 생성 public GoalCreateResponseDto createGoal(Long memberId, GoalCreateRequestDto req) { @@ -51,11 +49,8 @@ public GoalCreateResponseDto createGoal(Long memberId, GoalCreateRequestDto req) throw new GoalException(GoalErrorCode.ACCOUNT_ALREADY_LINKED_TO_GOAL); } - // 동기화 후 최신 잔액 가져오기 - Long startAmount = assetBalanceService.syncAndGetLatestBalance(memberId, req.bankAccountId()); - // Goal 엔티티 생성 시 bankAccount 포함 - Goal goal = GoalConverter.toEntity(member, account, req, startAmount); + Goal goal = GoalConverter.toEntity(member, account, req); Goal saved = goalRepository.save(goal); return GoalConverter.toCreateDto(saved); diff --git a/src/main/java/org/umc/valuedi/domain/goal/service/query/GoalLedgerQueryService.java b/src/main/java/org/umc/valuedi/domain/goal/service/query/GoalLedgerQueryService.java index e71d3cc..04e53bc 100644 --- a/src/main/java/org/umc/valuedi/domain/goal/service/query/GoalLedgerQueryService.java +++ b/src/main/java/org/umc/valuedi/domain/goal/service/query/GoalLedgerQueryService.java @@ -33,12 +33,10 @@ public LedgerListResponse getGoalLedgerTransactions(Long memberId, Long goalId, throw new GoalException(GoalErrorCode.GOAL_FORBIDDEN); } - // 시작 시간: 목표 생성 시각 (createdAt) - LocalDateTime from = goal.getCreatedAt(); - if (goal.getStartDate().atStartOfDay().isAfter(from)) { - from = goal.getStartDate().atStartOfDay(); - } - + // 시작 시간: 목표의 시작일(startDate) + LocalDateTime from = goal.getStartDate().atStartOfDay(); + + // 종료 시간: 목표의 종료일(endDate) LocalDateTime to = goal.getEndDate().atTime(LocalTime.MAX); Page result = ledgerQueryRepository.searchByPeriodLatest( diff --git a/src/main/java/org/umc/valuedi/domain/goal/service/query/GoalListQueryService.java b/src/main/java/org/umc/valuedi/domain/goal/service/query/GoalListQueryService.java index f1ac959..f3af4c9 100644 --- a/src/main/java/org/umc/valuedi/domain/goal/service/query/GoalListQueryService.java +++ b/src/main/java/org/umc/valuedi/domain/goal/service/query/GoalListQueryService.java @@ -7,7 +7,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.umc.valuedi.domain.asset.entity.BankAccount; -import org.umc.valuedi.domain.asset.service.AssetBalanceService; import org.umc.valuedi.domain.goal.converter.GoalConverter; import org.umc.valuedi.domain.goal.dto.response.GoalListResponseDto; import org.umc.valuedi.domain.goal.entity.Goal; @@ -24,6 +23,7 @@ import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -33,7 +33,6 @@ public class GoalListQueryService { private final GoalRepository goalRepository; private final MemberRepository memberRepository; private final GoalAchievementRateService achievementRateService; - private final AssetBalanceService assetBalanceService; private final GoalStatusChangeService goalStatusChangeService; private void validateListStatus(GoalStatus status) { @@ -46,77 +45,63 @@ private void validateListStatus(GoalStatus status) { private List findGoals(Long memberId, GoalStatus status, Pageable pageable) { validateListStatus(status); - return switch (status) { - case ACTIVE -> - goalRepository.findAllByMember_IdAndStatus(memberId, GoalStatus.ACTIVE, pageable); - case COMPLETE -> - goalRepository.findAllByMember_IdAndStatusIn( - memberId, - List.of(GoalStatus.COMPLETE, GoalStatus.FAILED), - pageable - ); - default -> throw new GoalException(GoalErrorCode.INVALID_GOAL_LIST_STATUS); - }; + if (status == GoalStatus.ACTIVE) { + return goalRepository.findAllByMember_IdAndStatus(memberId, GoalStatus.ACTIVE, pageable); + } else { + // COMPLETE + return goalRepository.findAllByMember_IdAndStatusIn( + memberId, + List.of(GoalStatus.COMPLETE, GoalStatus.FAILED), + pageable + ); + } } // limit 없을 때 private List findGoals(Long memberId, GoalStatus status) { validateListStatus(status); - return switch (status) { - case ACTIVE -> - goalRepository.findAllByMember_IdAndStatus(memberId, GoalStatus.ACTIVE); - case COMPLETE -> - goalRepository.findAllByMember_IdAndStatusIn( - memberId, - List.of(GoalStatus.COMPLETE, GoalStatus.FAILED) - ); - default -> throw new GoalException(GoalErrorCode.INVALID_GOAL_LIST_STATUS); - }; + if (status == GoalStatus.ACTIVE) { + return goalRepository.findAllByMember_IdAndStatus(memberId, GoalStatus.ACTIVE); + } else { + // COMPLETE + return goalRepository.findAllByMember_IdAndStatusIn( + memberId, + List.of(GoalStatus.COMPLETE, GoalStatus.FAILED) + ); + } } private List toSummaryDtos(List goals, Long memberId) { return goals.stream() .map(g -> { - BankAccount account = g.getBankAccount(); - long savedAmount = 0L; + long currentBalance = 0L; - // ACTIVE 상태일 때만 계좌가 연결되어 있고, 잔액 계산 및 상태 갱신이 필요함 if (g.getStatus() == GoalStatus.ACTIVE) { + BankAccount account = g.getBankAccount(); if (account == null || !account.getIsActive()) { throw new GoalException(GoalErrorCode.GOAL_ACCOUNT_INACTIVE); } - // 동기화 후 최신 잔액 가져오기 - Long currentBalance = assetBalanceService.syncAndGetLatestBalance(memberId, account.getId()); - - // 현재 잔액 - 시작 잔액 - savedAmount = currentBalance - g.getStartAmount(); - - // 음수일 경우 0으로 처리 - if (savedAmount < 0) { - savedAmount = 0; - } + // DB에 저장된 현재 계좌 잔액 사용 + currentBalance = account.getBalanceAmount(); // 목표 달성 여부 체크 및 상태 업데이트 (공통 로직 사용) - goalStatusChangeService.checkAndUpdateStatus(g, savedAmount); + goalStatusChangeService.checkAndUpdateStatus(g, currentBalance); + } else if (g.getStatus() == GoalStatus.COMPLETE) { + currentBalance = g.getTargetAmount(); // 성공했으므로 목표 달성으로 간주 } else { - - if (g.getStatus() == GoalStatus.COMPLETE) { - savedAmount = g.getTargetAmount(); // 성공했으므로 목표 달성으로 간주 - } else { - // FAILED인 경우 달성률을 저장하지 않으므로 그냥 0으로 설정 - savedAmount = 0L; - } + // FAILED인 경우 달성률을 저장하지 않으므로 그냥 0으로 설정 + currentBalance = 0L; } - int rate = achievementRateService.calculateRate(savedAmount, g.getTargetAmount()); + int rate = achievementRateService.calculateRate(currentBalance, g.getTargetAmount()); - return GoalConverter.toSummaryDto(g, savedAmount, rate); + return GoalConverter.toSummaryDto(g, currentBalance, rate); }) - .toList(); + .collect(Collectors.toList()); } private String resolveTimeSortField(GoalStatus status) { @@ -143,20 +128,22 @@ public GoalListResponseDto getGoals(Long memberId, GoalStatus status, GoalSort s } GoalStatus listStatus = (status == null) ? GoalStatus.ACTIVE : status; - validateListStatus(listStatus); - + + // sort가 null이면 기본값 설정 GoalSort sortType = (sort == null) ? GoalSort.TIME_DESC : sort; + Integer size = (limit == null) ? null : Math.max(3, limit); // COMPLETE + PROGRESS_DESC => 성공한 목표만 + 달성(완료)된 순 if (listStatus == GoalStatus.COMPLETE && sortType == GoalSort.PROGRESS_DESC) { + // COMPLETE 상태인 것만 가져와서 정렬 List goals = goalRepository.findAllByMember_IdAndStatus(memberId, GoalStatus.COMPLETE).stream() .sorted(Comparator.comparing( Goal::getCompletedAt, Comparator.nullsLast(Comparator.naturalOrder()) ) .reversed()) - .toList(); + .collect(Collectors.toList()); if (size != null && goals.size() > size) { goals = goals.subList(0, size); @@ -176,7 +163,7 @@ public GoalListResponseDto getGoals(Long memberId, GoalStatus status, GoalSort s } else { goals = findGoals(memberId, listStatus).stream() .sorted(resolveTimeComparator(listStatus)) - .toList(); + .collect(Collectors.toList()); } return new GoalListResponseDto(toSummaryDtos(goals, memberId)); @@ -184,13 +171,16 @@ public GoalListResponseDto getGoals(Long memberId, GoalStatus status, GoalSort s // ACTIVE + PROGRESS_DESC => 달성률 높은 진행 중인 목표만 List goals = findGoals(memberId, listStatus); - var dtos = toSummaryDtos(goals, memberId).stream() - .sorted(Comparator.comparingInt(GoalListResponseDto.GoalSummaryDto::achievementRate).reversed()); - - if (size != null) { - dtos = dtos.limit(size); + + // DTO 변환 후 정렬 + List dtos = toSummaryDtos(goals, memberId).stream() + .sorted(Comparator.comparingInt(GoalListResponseDto.GoalSummaryDto::achievementRate).reversed()) + .collect(Collectors.toList()); + + if (size != null && dtos.size() > size) { + dtos = dtos.subList(0, size); } - return new GoalListResponseDto(dtos.toList()); + return new GoalListResponseDto(dtos); } } diff --git a/src/main/java/org/umc/valuedi/domain/goal/service/query/GoalQueryService.java b/src/main/java/org/umc/valuedi/domain/goal/service/query/GoalQueryService.java index 9670bc2..dcccd34 100644 --- a/src/main/java/org/umc/valuedi/domain/goal/service/query/GoalQueryService.java +++ b/src/main/java/org/umc/valuedi/domain/goal/service/query/GoalQueryService.java @@ -4,7 +4,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.umc.valuedi.domain.asset.entity.BankAccount; -import org.umc.valuedi.domain.asset.service.AssetBalanceService; import org.umc.valuedi.domain.goal.converter.GoalConverter; import org.umc.valuedi.domain.goal.dto.response.GoalActiveCountResponseDto; import org.umc.valuedi.domain.goal.dto.response.GoalDetailResponseDto; @@ -30,7 +29,6 @@ public class GoalQueryService { private final GoalRepository goalRepository; private final MemberRepository memberRepository; private final GoalAchievementRateService achievementRateService; - private final AssetBalanceService assetBalanceService; private final GoalStatusChangeService goalStatusChangeService; // 목표 상세 조회 @@ -41,27 +39,19 @@ public GoalDetailResponseDto getGoalDetail(Long memberId, Long goalId) { BankAccount account = goal.getBankAccount(); - if (!account.getIsActive()) { + if (account == null || !account.getIsActive()) { throw new GoalException(GoalErrorCode.GOAL_ACCOUNT_INACTIVE); } - // 동기화 후 최신 잔액 가져오기 - Long currentBalance = assetBalanceService.syncAndGetLatestBalance(memberId, account.getId()); + // DB에 저장된 현재 계좌 잔액 사용 + Long currentBalance = account.getBalanceAmount(); - // 현재 잔액 - 시작 잔액 - long savedAmount = currentBalance - goal.getStartAmount(); + // 목표 달성 여부 체크 및 상태 업데이트 + goalStatusChangeService.checkAndUpdateStatus(goal, currentBalance); - // 음수일 경우 0으로 처리 - if (savedAmount < 0) { - savedAmount = 0; - } - - // 목표 달성 여부 체크 및 상태 업데이트 (공통 로직 사용) - goalStatusChangeService.checkAndUpdateStatus(goal, savedAmount); - - int rate = achievementRateService.calculateRate(savedAmount, goal.getTargetAmount()); + int rate = achievementRateService.calculateRate(currentBalance, goal.getTargetAmount()); - return GoalConverter.toDetailDto(goal, savedAmount, rate); + return GoalConverter.toDetailDto(goal, currentBalance, rate); } // 목표 개수 조회