Skip to content

Commit 57490e7

Browse files
authored
[FIX/#79] 목표 거래내역 동기화 및 응답 추가 (#87)
## 🔗 Related Issue <!-- 이슈 번호를 작성하여 종료시켜주세요 --> - Closes #79 ## 📝 Summary <!-- 작업한 기능을 설명해주세요 --> ### 1. 목표 생성 및 조회 시 자산 동기화 로직 개선 문제점: - 목표 생성 시점의 계좌 잔액이 실제 은행 잔액과 달라 목표 달성률이 부정확한 문제 발생. 또한, 목표 거래내역 조회 시 최신 거래내역이 반영되지 않는 문제 발생. 해결: - AssetBalanceService 도입: 자산 동기화와 최신 잔액 조회를 담당하는 별도 서비스를 분리하여 재사용성 확보. - 최신 잔액 조회 로직 강화: BankAccount의 잔액 필드뿐만 아니라, BankTransaction의 가장 최신 거래내역(afterBalance)을 조회하여 더 정확한 잔액을 가져오도록 개선. - 목표 생성(GoalCommandService): 목표 생성 직전에 자산 동기화를 수행하여 정확한 시작 잔액(startAmount)으로 목표를 생성하도록 변경. - 목표 조회(GoalQueryService, GoalListQueryService): 상세/목록 조회 시마다 자산 동기화를 수행하여 실시간 잔액을 반영한 달성률(achievementRate)과 모은 금액(savedAmount)을 제공. ### 2. 목표 거래내역 조회 동기화 문제 해결 (GoalLedgerFacade 도입) 문제점: - 트랜잭션 격리 수준 문제로 인해, 자산 동기화 직후에 조회 API를 호출해도 최신 내역이 보이지 않는 현상 발생. 또한, 자산 데이터는 수집되었으나 가계부 데이터로 변환되지 않는 누락 구간 발생. 해결: - GoalLedgerFacade 패턴 적용: 트랜잭션을 분리하여 [자산 동기화(TX1) -> 가계부 동기화(TX2) -> 조회(TX3)] 순서로 실행되도록 구조 변경. - 가계부 동기화 로직 보완: 새 데이터 유무와 관계없이 목표 기간(시작일~오늘)에 대해 강제로 가계부 동기화를 수행하도록 하여 데이터 누락 방지. - 조회 시작 시점 변경: 목표 생성 시점(createdAt) 이후의 거래내역만 조회하도록 필터링 조건 수정. - 응답 필드 추가: 거래내역 응답(LedgerListResponse)에 거래 후 잔액(afterBalance) 필드 추가. ### 3. 목표 상태 관리 및 계좌 연결 로직 고도화 상태 변경 로직 (GoalStatusChangeService): - 성공 조건: (현재 잔액 - 시작 잔액) >= 목표 금액 일 때 COMPLETE 처리. - 실패 조건: 목표 기한(endDate)이 지났는데 미달성 시 FAILED 처리. - 실시간 상태 반영: 스케줄러뿐만 아니라 목표 조회 시점에도 상태 체크 로직을 수행하여 즉시 반영되도록 개선. 계좌 연결 해제: - 목표가 COMPLETE 또는 FAILED 상태로 종료될 때, 연결된 계좌(bankAccount)를 NULL로 설정하여 즉시 연결 해제되도록 수정. - 완료된 목표 조회 시 예외 처리 추가. ### 4. 트랜잭션 처리 개선 외부 API 호출이 포함된 자산 동기화 로직(AssetFetchService)을 REQUIRES_NEW로 분리하여, 동기화 실패가 전체 비즈니스 로직(목표 조회 등)에 영향을 주지 않도록 격리. ## 🔄 Changes <!-- 구체적으로 어떤 파일/로직이 변경되었는지 체크해주세요 --> - [x] API 변경 (추가/수정) - [ ] 데이터 및 도메인 변경 (DB, 비즈니스 로직) - [ ] 설정 또는 인프라 관련 변경 - [x] 리팩토링 ## 💬 Questions & Review Points 기능들은 다 구현 되긴 했는데 목표 조회 시 savedamount가 처음 api호출시에는 안 바뀌고 두번째부터 바뀌는 이슈가 있습니다...! 이건 아직 해결을 못해서 조금 더 확인해보겠습니당.. ## 📸 API Test Results (Swagger) ### 목표 생성 <img width="2319" height="1174" alt="image" src="https://github.com/user-attachments/assets/e1f301ba-f73f-4bcb-9e98-0b51e31f9957" /> ### 목표 상세 조회 (15,000원 입금 후) 모은 금액, 달성률 등이 잘 업데이트 됨. <img width="2323" height="987" alt="image" src="https://github.com/user-attachments/assets/922eb15c-887d-45d5-805d-17372f5af2ce" /> ### 목표 거래내역 조회 <img width="2323" height="1057" alt="image" src="https://github.com/user-attachments/assets/bab3740c-eba8-4e21-8c10-fa3ea357418f" /> ### 목표 조회(미션 성공 한 경우) <img width="2325" height="908" alt="image" src="https://github.com/user-attachments/assets/2bebe546-37c8-4977-8860-c8896e164d10" /> ## ✅ Checklist - [x] API 테스트 완료 - [x] 테스트 결과 사진 첨부 - [x] 빌드 성공 확인 (./gradlew build)
1 parent 4573202 commit 57490e7

17 files changed

Lines changed: 298 additions & 40 deletions

File tree

src/main/java/org/umc/valuedi/domain/asset/entity/BankAccount.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,8 @@ public void assignConnection(CodefConnection connection) {
9696
public void deactivate() {
9797
this.isActive = false;
9898
}
99+
100+
public void updateBalance(Long balanceAmount) {
101+
this.balanceAmount = balanceAmount;
102+
}
99103
}

src/main/java/org/umc/valuedi/domain/asset/repository/bank/bankTransaction/BankTransactionRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,7 @@ public interface BankTransactionRepository extends JpaRepository<BankTransaction
1818
Optional<LocalDate> findLatestTransactionDateByAccount(@Param("account") BankAccount account);
1919

2020
List<BankTransaction> findByBankAccountInAndTrDatetimeAfter(List<BankAccount> accounts, LocalDateTime startTime);
21+
22+
// 특정 계좌의 가장 최신 거래내역 1건 조회
23+
Optional<BankTransaction> findTopByBankAccountOrderByTrDatetimeDesc(BankAccount bankAccount);
2124
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package org.umc.valuedi.domain.asset.service;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.stereotype.Service;
6+
import org.springframework.transaction.annotation.Propagation;
7+
import org.springframework.transaction.annotation.Transactional;
8+
import org.umc.valuedi.domain.asset.dto.res.AssetResDTO;
9+
import org.umc.valuedi.domain.asset.entity.BankAccount;
10+
import org.umc.valuedi.domain.asset.entity.BankTransaction;
11+
import org.umc.valuedi.domain.asset.repository.bank.bankAccount.BankAccountRepository;
12+
import org.umc.valuedi.domain.asset.repository.bank.bankTransaction.BankTransactionRepository;
13+
import org.umc.valuedi.domain.asset.service.command.AssetFetchService;
14+
import org.umc.valuedi.domain.goal.exception.GoalException;
15+
import org.umc.valuedi.domain.goal.exception.code.GoalErrorCode;
16+
import org.umc.valuedi.domain.member.entity.Member;
17+
import org.umc.valuedi.domain.member.exception.MemberException;
18+
import org.umc.valuedi.domain.member.exception.code.MemberErrorCode;
19+
import org.umc.valuedi.domain.member.repository.MemberRepository;
20+
21+
@Slf4j
22+
@Service
23+
@RequiredArgsConstructor
24+
public class AssetBalanceService {
25+
26+
private final AssetFetchService assetFetchService;
27+
private final MemberRepository memberRepository;
28+
private final BankAccountRepository bankAccountRepository;
29+
private final BankTransactionRepository bankTransactionRepository;
30+
31+
/**
32+
* 자산 동기화를 수행하고, 특정 계좌의 최신 잔액을 반환합니다.
33+
* 동기화 실패 시 기존 잔액을 반환합니다.
34+
*/
35+
@Transactional(propagation = Propagation.NOT_SUPPORTED)
36+
public Long syncAndGetLatestBalance(Long memberId, Long accountId) {
37+
Member member = memberRepository.findById(memberId)
38+
.orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND));
39+
40+
// 자산 동기화 시도
41+
try {
42+
AssetResDTO.AssetSyncResult result = assetFetchService.fetchAndSaveLatestData(member);
43+
log.info("[AssetBalanceService] 자산 동기화 완료. MemberID: {}, NewTransactions: {}, SuccessOrgs: {}, FailedOrgs: {}",
44+
memberId, result.getNewBankTransactionCount(), result.getSuccessOrganizations(), result.getFailureOrganizations());
45+
46+
47+
} catch (Exception e) {
48+
log.warn("잔액 조회 중 자산 동기화 실패 (기존 잔액 사용): {}", e.getMessage());
49+
}
50+
51+
// 계좌 조회 (영속성 컨텍스트 초기화 가능성 고려하여 재조회 권장)
52+
BankAccount account = bankAccountRepository.findByIdAndMemberId(accountId, memberId)
53+
.orElseThrow(() -> new GoalException(GoalErrorCode.ACCOUNT_NOT_FOUND));
54+
55+
// 최신 거래내역 기반 잔액 조회
56+
return bankTransactionRepository.findTopByBankAccountOrderByTrDatetimeDesc(account)
57+
.map(BankTransaction::getAfterBalance)
58+
.orElse(account.getBalanceAmount());
59+
}
60+
}

src/main/java/org/umc/valuedi/domain/asset/service/command/AssetFetchService.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
import lombok.RequiredArgsConstructor;
55
import lombok.extern.slf4j.Slf4j;
66
import org.springframework.stereotype.Service;
7+
import org.springframework.transaction.annotation.Propagation;
78
import org.springframework.transaction.annotation.Transactional;
89
import org.umc.valuedi.domain.asset.dto.res.AssetResDTO;
910
import org.umc.valuedi.domain.asset.entity.BankAccount;
1011
import org.umc.valuedi.domain.asset.entity.BankTransaction;
1112
import org.umc.valuedi.domain.asset.entity.Card;
1213
import org.umc.valuedi.domain.asset.entity.CardApproval;
14+
import org.umc.valuedi.domain.asset.repository.bank.bankAccount.BankAccountRepository;
1315
import org.umc.valuedi.domain.asset.repository.bank.bankTransaction.BankTransactionRepository;
1416
import org.umc.valuedi.domain.asset.repository.card.cardApproval.CardApprovalRepository;
1517
import org.umc.valuedi.domain.asset.service.command.worker.AssetFetchWorker;
@@ -22,6 +24,7 @@
2224
import java.time.LocalDateTime;
2325
import java.util.ArrayList;
2426
import java.util.List;
27+
import java.util.Map;
2528
import java.util.Objects;
2629
import java.util.Set;
2730
import java.util.concurrent.CompletableFuture;
@@ -36,13 +39,14 @@ public class AssetFetchService {
3639
private final CodefConnectionRepository codefConnectionRepository;
3740
private final BankTransactionRepository bankTransactionRepository;
3841
private final CardApprovalRepository cardApprovalRepository;
42+
private final BankAccountRepository bankAccountRepository;
3943
private final AssetFetchWorker assetFetchWorker;
4044
private final LedgerSyncService ledgerSyncService;
4145

4246
private record BankTransactionKey(LocalDateTime trDatetime, Long inAmount, Long outAmount, String desc3) {}
4347
private record CardApprovalKey(Card card, String approvalNo) {}
4448

45-
@Transactional
49+
@Transactional(propagation = Propagation.REQUIRES_NEW)
4650
public AssetResDTO.AssetSyncResult fetchAndSaveLatestData(Member member) {
4751
List<CodefConnection> connections = codefConnectionRepository.findByMemberId(member.getId());
4852
LocalDate today = LocalDate.now();
@@ -87,6 +91,9 @@ public AssetResDTO.AssetSyncResult fetchAndSaveLatestData(Member member) {
8791
if (!newBankTransactions.isEmpty()) {
8892
bankTransactionRepository.bulkInsert(newBankTransactions);
8993
totalNewBankTransactions = newBankTransactions.size();
94+
95+
// 계좌 잔액 업데이트 로직 추가
96+
updateAccountBalances(newBankTransactions);
9097
}
9198
}
9299

@@ -118,6 +125,29 @@ public AssetResDTO.AssetSyncResult fetchAndSaveLatestData(Member member) {
118125
.build();
119126
}
120127

128+
private void updateAccountBalances(List<BankTransaction> transactions) {
129+
// 계좌별로 가장 최신 거래내역을 찾아서 잔액 업데이트
130+
Map<BankAccount, BankTransaction> latestTransactions = transactions.stream()
131+
.collect(Collectors.groupingBy(BankTransaction::getBankAccount,
132+
Collectors.collectingAndThen(
133+
Collectors.maxBy((t1, t2) -> t1.getTrDatetime().compareTo(t2.getTrDatetime())),
134+
java.util.Optional::get
135+
)));
136+
137+
List<BankAccount> updatedAccounts = new ArrayList<>();
138+
139+
latestTransactions.forEach((account, latestTransaction) -> {
140+
if (latestTransaction.getAfterBalance() != null) {
141+
account.updateBalance(latestTransaction.getAfterBalance());
142+
updatedAccounts.add(account);
143+
}
144+
});
145+
146+
if (!updatedAccounts.isEmpty()) {
147+
bankAccountRepository.saveAll(updatedAccounts);
148+
}
149+
}
150+
121151
private List<BankTransaction> filterNewBankTransactions(List<BankTransaction> allFetched) {
122152
if (allFetched.isEmpty()) return List.of();
123153

src/main/java/org/umc/valuedi/domain/goal/controller/GoalController.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
import org.umc.valuedi.domain.goal.enums.GoalStatus;
1212
import org.umc.valuedi.domain.goal.enums.GoalSort;
1313
import org.umc.valuedi.domain.goal.exception.code.GoalSuccessCode;
14+
import org.umc.valuedi.domain.goal.service.GoalLedgerFacade;
1415
import org.umc.valuedi.domain.goal.service.command.GoalAccountCommandService;
1516
import org.umc.valuedi.domain.goal.service.command.GoalCommandService;
1617
import org.umc.valuedi.domain.goal.service.query.GoalAccountQueryService;
17-
import org.umc.valuedi.domain.goal.service.query.GoalLedgerQueryService;
1818
import org.umc.valuedi.domain.goal.service.query.GoalListQueryService;
1919
import org.umc.valuedi.domain.goal.service.query.GoalQueryService;
2020
import org.umc.valuedi.domain.ledger.dto.response.LedgerListResponse;
@@ -31,7 +31,7 @@ public class GoalController implements GoalControllerDocs{
3131
private final GoalListQueryService goalListQueryService;
3232
private final GoalAccountQueryService goalAccountQueryService;
3333
private final GoalAccountCommandService goalAccountCommandService;
34-
private final GoalLedgerQueryService goalLedgerQueryService;
34+
private final GoalLedgerFacade goalLedgerFacade;
3535

3636
// 목표 추가
3737
@PostMapping
@@ -158,7 +158,7 @@ public ApiResponse<LedgerListResponse> getGoalLedgers(
158158
) {
159159
return ApiResponse.onSuccess(
160160
GoalSuccessCode.GOAL_LEDGER_LIST_FETCHED,
161-
goalLedgerQueryService.getGoalLedgerTransactions(memberId, goalId, page, size)
161+
goalLedgerFacade.getGoalLedgerTransactions(memberId, goalId, page, size)
162162
);
163163
}
164164
}

src/main/java/org/umc/valuedi/domain/goal/converter/GoalConverter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ public static GoalDetailResponseDto toDetailDto(Goal goal, Long savedAmount, int
124124
goal.getTitle(),
125125
savedAmount,
126126
goal.getTargetAmount(),
127+
goal.getStartDate(),
128+
goal.getEndDate(),
127129
remainingDays,
128130
achievementRate,
129131
accountDto,

src/main/java/org/umc/valuedi/domain/goal/dto/response/GoalDetailResponseDto.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import io.swagger.v3.oas.annotations.media.Schema;
44
import org.umc.valuedi.domain.goal.enums.GoalStatus;
55

6+
import java.time.LocalDate;
7+
68
@Schema(description = "목표 상세 조회 응답")
79
public record GoalDetailResponseDto(
810

@@ -18,6 +20,12 @@ public record GoalDetailResponseDto(
1820
@Schema(description = "목표 금액", example = "1000000")
1921
Long targetAmount,
2022

23+
@Schema(description = "시작일", example = "2024-01-01")
24+
LocalDate startDate,
25+
26+
@Schema(description = "목표일", example = "2024-12-31")
27+
LocalDate endDate,
28+
2129
@Schema(description = "남은 일수", example = "30")
2230
Long remainingDays,
2331

src/main/java/org/umc/valuedi/domain/goal/entity/Goal.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,24 +91,21 @@ public void Activate() {
9191
this.completedAt = null;
9292
}
9393

94-
// 실패 종료
94+
// 성공 종료 (계좌 연결 해제)
9595
public void Complete() {
9696
this.status = GoalStatus.COMPLETE;
9797
this.completedAt = LocalDateTime.now();
98+
this.bankAccount = null;
9899
}
99100

100-
// 취소 종료
101+
// 실패 종료 (계좌 연결 해제)
101102
public void Fail() {
102103
this.status = GoalStatus.FAILED;
103104
this.completedAt = LocalDateTime.now();
105+
this.bankAccount = null;
104106
}
105107

106108
public void linkBankAccount(BankAccount bankAccount) {
107109
this.bankAccount = bankAccount;
108110
}
109-
110-
public void unlinkBankAccount() {
111-
this.bankAccount = null;
112-
}
113-
114111
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.umc.valuedi.domain.goal.service;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.stereotype.Service;
6+
import org.umc.valuedi.domain.asset.dto.res.AssetResDTO;
7+
import org.umc.valuedi.domain.asset.service.command.AssetFetchService;
8+
import org.umc.valuedi.domain.goal.entity.Goal;
9+
import org.umc.valuedi.domain.goal.exception.GoalException;
10+
import org.umc.valuedi.domain.goal.exception.code.GoalErrorCode;
11+
import org.umc.valuedi.domain.goal.repository.GoalRepository;
12+
import org.umc.valuedi.domain.goal.service.query.GoalLedgerQueryService;
13+
import org.umc.valuedi.domain.ledger.dto.response.LedgerListResponse;
14+
import org.umc.valuedi.domain.ledger.service.command.LedgerSyncService;
15+
16+
import java.time.LocalDate;
17+
18+
@Slf4j
19+
@Service
20+
@RequiredArgsConstructor
21+
public class GoalLedgerFacade {
22+
23+
private final GoalRepository goalRepository;
24+
private final AssetFetchService assetFetchService;
25+
private final LedgerSyncService ledgerSyncService;
26+
private final GoalLedgerQueryService goalLedgerQueryService;
27+
28+
/**
29+
* 목표 거래내역 조회 (동기화 포함)
30+
* 트랜잭션을 분리하여 최신 데이터를 조회할 수 있도록 함
31+
*/
32+
public LedgerListResponse getGoalLedgerTransactions(Long memberId, Long goalId, int page, int size) {
33+
// 1. 목표 정보 조회 (검증용)
34+
Goal goal = goalRepository.findById(goalId)
35+
.orElseThrow(() -> new GoalException(GoalErrorCode.GOAL_NOT_FOUND));
36+
37+
if (!goal.getMember().getId().equals(memberId)) {
38+
throw new GoalException(GoalErrorCode.GOAL_FORBIDDEN);
39+
}
40+
41+
// 2. 자산 및 가계부 동기화 (각각 별도의 트랜잭션으로 실행됨)
42+
try {
43+
// 자산 동기화 (REQUIRES_NEW)
44+
// 여기서 최신 거래내역을 DB에 저장함
45+
assetFetchService.fetchAndSaveLatestData(goal.getMember());
46+
47+
// 가계부 동기화 (REQUIRES_NEW)
48+
// 목표 기간 내의 데이터가 누락되지 않도록, 목표 시작일 ~ 오늘(또는 종료일)까지 동기화 수행
49+
LocalDate fromDate = goal.getStartDate();
50+
LocalDate toDate = LocalDate.now();
51+
52+
// 목표 종료일이 오늘보다 과거라면 종료일까지, 아니면 오늘까지
53+
if (goal.getEndDate().isBefore(toDate)) {
54+
toDate = goal.getEndDate();
55+
}
56+
57+
// 시작일이 종료일보다 늦으면(미래 목표 등) 동기화 수행 안 함
58+
if (!fromDate.isAfter(toDate)) {
59+
ledgerSyncService.syncTransactionsAndUpdateMember(memberId, fromDate, toDate);
60+
}
61+
62+
} catch (Exception e) {
63+
log.warn("목표 거래내역 조회 중 동기화 실패 (기존 데이터로 조회): {}", e.getMessage());
64+
}
65+
66+
// 3. 최종 조회 (새로운 트랜잭션)
67+
return goalLedgerQueryService.getGoalLedgerTransactions(memberId, goalId, page, size);
68+
}
69+
}

src/main/java/org/umc/valuedi/domain/goal/service/GoalStatusChangeService.java

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,61 @@
22

33
import jakarta.transaction.Transactional;
44
import lombok.RequiredArgsConstructor;
5+
import lombok.extern.slf4j.Slf4j;
56
import org.springframework.stereotype.Service;
7+
import org.umc.valuedi.domain.asset.entity.BankAccount;
8+
import org.umc.valuedi.domain.asset.service.AssetBalanceService;
69
import org.umc.valuedi.domain.goal.entity.Goal;
710
import org.umc.valuedi.domain.goal.enums.GoalStatus;
811
import org.umc.valuedi.domain.goal.repository.GoalRepository;
912

1013
import java.time.LocalDate;
1114
import java.util.List;
1215

16+
@Slf4j
1317
@Service
1418
@RequiredArgsConstructor
1519
@Transactional
1620
public class GoalStatusChangeService {
1721

1822
private final GoalRepository goalRepository;
19-
private final GoalAchievementRateService achievementRateService; // 나중에 savedAmount 계산로직이 여기로
23+
private final AssetBalanceService assetBalanceService;
2024

21-
// 1) 성공 처리: savedAmount >= targetAmount 인 ACTIVE 목표는 COMPLETE
22-
// 2) 실패 처리: endDate 도달/경과했고 savedAmount < targetAmount 인 ACTIVE 목표는 FAILED
25+
// 전체 목표 상태 갱신 (스케줄러용)
2326
public void refreshGoalStatuses() {
2427
List<Goal> activeGoals = goalRepository.findAllByStatus(GoalStatus.ACTIVE);
25-
28+
2629
for (Goal goal : activeGoals) {
27-
int savedAmount = 0; // 계좌 연동 후 수정
28-
boolean isTargetReached = savedAmount >= goal.getTargetAmount();
29-
boolean isExpired = goal.getEndDate().isBefore(LocalDate.now());
30-
31-
if (isTargetReached) {
32-
goal.Complete();
33-
continue;
30+
try {
31+
BankAccount account = goal.getBankAccount();
32+
if (account == null || !account.getIsActive()) {
33+
continue;
34+
}
35+
// 최신 잔액 조회 (동기화 포함)
36+
Long currentBalance = assetBalanceService.syncAndGetLatestBalance(goal.getMember().getId(), account.getId());
37+
long savedAmount = currentBalance - goal.getStartAmount();
38+
39+
checkAndUpdateStatus(goal, savedAmount);
40+
} catch (Exception e) {
41+
log.error("목표 상태 갱신 중 오류 발생. Goal ID: {}", goal.getId(), e);
3442
}
43+
}
44+
}
3545

36-
if (isExpired) {
37-
goal.Fail();
38-
}
46+
// 단일 목표 상태 갱신 (조회 시 호출용)
47+
public void checkAndUpdateStatus(Goal goal, long savedAmount) {
48+
if (goal.getStatus() != GoalStatus.ACTIVE) {
49+
return;
50+
}
51+
52+
boolean isTargetReached = savedAmount >= goal.getTargetAmount();
53+
// 목표 종료일이 오늘보다 이전이면 만료된 것으로 판단 (종료일 당일까지는 진행 중)
54+
boolean isExpired = goal.getEndDate().isBefore(LocalDate.now());
55+
56+
if (isTargetReached) {
57+
goal.Complete();
58+
} else if (isExpired) {
59+
goal.Fail();
3960
}
4061
}
4162
}

0 commit comments

Comments
 (0)