diff --git a/README.md b/README.md index 9bad4ae0..440264ab 100644 --- a/README.md +++ b/README.md @@ -251,7 +251,7 @@ Resolves: #12 인증/회원 - 기록/리포트
+ 기록/리포트/AI피드백
기록/리포트 diff --git a/src/main/java/com/umc/finly/domain/record/converter/RecordConverter.java b/src/main/java/com/umc/finly/domain/record/converter/RecordConverter.java index 67de0ebc..ac7d47e3 100644 --- a/src/main/java/com/umc/finly/domain/record/converter/RecordConverter.java +++ b/src/main/java/com/umc/finly/domain/record/converter/RecordConverter.java @@ -3,15 +3,25 @@ import com.umc.finly.domain.market.stock.entity.Stock; import com.umc.finly.domain.record.dto.request.RecordCreateReqDTO; import com.umc.finly.domain.record.dto.request.RecordUpdateReqDTO; +import com.umc.finly.domain.record.dto.response.DailyReportResDTO; +import com.umc.finly.domain.record.dto.response.RecentSearchResDTO; import com.umc.finly.domain.record.dto.response.RecordCreateResDTO; import com.umc.finly.domain.record.dto.response.RecordDetailResDTO; +import com.umc.finly.domain.record.dto.response.RecordSearchResDTO; import com.umc.finly.domain.record.dto.response.RecordUpdateResDTO; +import com.umc.finly.domain.record.dto.response.TodayRecordResDTO; import com.umc.finly.domain.record.entity.RecordEntry; import com.umc.finly.domain.record.entity.RecordFeedback; +import com.umc.finly.domain.record.enums.EmotionCode; import com.umc.finly.domain.record.enums.Session; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + @Component @RequiredArgsConstructor public class RecordConverter { @@ -51,16 +61,177 @@ public void applyUpdate(RecordEntry entry, RecordUpdateReqDTO req, Stock stockOr // 생성 응답 DTO로 변환 public RecordCreateResDTO toCreateRes(RecordEntry entry, Stock stock, RecordFeedback feedback) { - return RecordCreateResDTO.from(entry, stock, feedback); + RecordCreateResDTO.FeedbackInfo feedbackInfo = null; + if (feedback != null) { + feedbackInfo = RecordCreateResDTO.FeedbackInfo.builder() + .feedbackId(feedback.getId()) + .status(feedback.getStatus().name()) + .build(); + } + + return RecordCreateResDTO.builder() + .recordId(entry.getId()) + .recordDate(entry.getRecordDate()) + .recordedAt(entry.getCreatedAt()) + .session(entry.getSession()) + .tradeAction(entry.getTradeAction()) + .symbol(stock.getSymbol()) + .emotionCode(entry.getEmotionCode()) + .emotionIntensity(entry.getEmotionIntensity()) + .memo(entry.getMemo()) + .feedback(feedbackInfo) + .build(); } // 상세 응답 DTO로 변환 public RecordDetailResDTO toDetailRes(RecordEntry entry, Stock stock) { - return RecordDetailResDTO.from(entry, stock); + return RecordDetailResDTO.builder() + .recordId(entry.getId()) + .recordDate(entry.getRecordDate()) + .recordedAt(entry.getCreatedAt()) + .session(entry.getSession()) + .tradeAction(entry.getTradeAction()) + .symbol(stock.getSymbol()) + .unitPrice(entry.getUnitPrice()) + .quantity(entry.getQuantity()) + .emotionCode(entry.getEmotionCode()) + .emotionIntensity(entry.getEmotionIntensity()) + .memo(entry.getMemo()) + .build(); } // 수정 응답 DTO로 변환 public RecordUpdateResDTO toUpdateRes(RecordEntry entry, Stock stock) { - return RecordUpdateResDTO.from(entry, stock); + return RecordUpdateResDTO.builder() + .recordId(entry.getId()) + .recordDate(entry.getRecordDate()) + .updatedAt(entry.getUpdatedAt()) + .session(entry.getSession()) + .tradeAction(entry.getTradeAction()) + .symbol(stock.getSymbol()) + .unitPrice(entry.getUnitPrice()) + .quantity(entry.getQuantity()) + .emotionCode(entry.getEmotionCode()) + .emotionIntensity(entry.getEmotionIntensity()) + .memo(entry.getMemo()) + .build(); + } + + // 일일 리포트 응답 DTO로 변환 + public DailyReportResDTO toDailyReportRes(RecordEntry entry, Stock stock, String content) { + return DailyReportResDTO.builder() + .recordId(entry.getId()) + .recordDate(entry.getRecordDate()) + .recordedAt(entry.getCreatedAt()) + .session(entry.getSession()) + .tradeAction(entry.getTradeAction()) + .unitPrice(entry.getUnitPrice()) + .quantity(entry.getQuantity()) + .emotionCode(entry.getEmotionCode()) + .name(stock.getName()) + .content(content) + .build(); + } + + // 오늘 기록 응답 DTO로 변환 + public TodayRecordResDTO toTodayRecordRes( + LocalDate date, + List entries, + Map stockMap + ) { + // 프리즘 피드백 타이틀 생성 + String title = generatePrismTitle(entries); + TodayRecordResDTO.PrismFeedback prismFeedback = TodayRecordResDTO.PrismFeedback.builder() + .title(title) + .generatedAt(LocalDateTime.now()) + .build(); + + // 타임라인 엔트리 변환 + List timelineSummary = entries.stream() + .map(entry -> toTimelineEntry(entry, stockMap.get(entry.getStockId()))) + .toList(); + + return TodayRecordResDTO.builder() + .date(date) + .prismFeedback(prismFeedback) + .timelineSummary(timelineSummary) + .hasRecords(!entries.isEmpty()) + .recordCount(entries.size()) + .build(); + } + + // 타임라인 엔트리 변환 + public TodayRecordResDTO.TimelineEntry toTimelineEntry(RecordEntry entry, Stock stock) { + String symbol = stock != null ? stock.getSymbol() : ""; + return TodayRecordResDTO.TimelineEntry.builder() + .recordId(entry.getId()) + .recordDate(entry.getRecordDate()) + .recordedAt(entry.getCreatedAt()) + .session(entry.getSession()) + .tradeAction(entry.getTradeAction()) + .symbol(symbol) + .unitPrice(entry.getUnitPrice()) + .quantity(entry.getQuantity()) + .emotionCode(entry.getEmotionCode()) + .emotionIntensity(entry.getEmotionIntensity()) + .memo(entry.getMemo()) + .build(); + } + + // 프리즘 타이틀 생성 (오늘 기록들의 감정 흐름 요약) + public String generatePrismTitle(List entries) { + if (entries == null || entries.isEmpty()) { + return "괜찮아요. 기록이 없는 날도 있을 수 있죠."; + } + if (entries.size() == 1) { + EmotionCode emotion = entries.get(0).getEmotionCode(); + if (emotion == null) { + return "오늘 하루도 기록을 남겼네요!"; + } + return "{{" + emotion.getLabel() + "}}한 하루네요!"; + } + EmotionCode first = entries.get(0).getEmotionCode(); + EmotionCode last = entries.get(entries.size() - 1).getEmotionCode(); + if (first == null || last == null) { + return "오늘도 열심히 기록했네요!"; + } + return "{{" + first.getLabel() + "}}하게 시작해 {{" + last.getLabel() + "}}" + last.getParticle() + " 마무리한 날이네요."; + } + + // 검색 응답 DTO로 변환 + public RecordSearchResDTO toSearchRes(List entries, Map stockMap) { + List searchEntries = entries.stream() + .map(entry -> toSearchEntry(entry, stockMap.get(entry.getStockId()))) + .toList(); + + return RecordSearchResDTO.builder() + .records(searchEntries) + .totalCount(searchEntries.size()) + .build(); + } + + // 검색 엔트리 변환 + public RecordSearchResDTO.SearchEntry toSearchEntry(RecordEntry entry, Stock stock) { + String symbol = stock != null ? stock.getSymbol() : ""; + return RecordSearchResDTO.SearchEntry.builder() + .recordId(entry.getId()) + .recordDate(entry.getRecordDate()) + .recordedAt(entry.getCreatedAt()) + .session(entry.getSession()) + .tradeAction(entry.getTradeAction()) + .symbol(symbol) + .unitPrice(entry.getUnitPrice()) + .quantity(entry.getQuantity()) + .emotionCode(entry.getEmotionCode()) + .emotionIntensity(entry.getEmotionIntensity()) + .memo(entry.getMemo()) + .build(); + } + + // 최근 검색어 응답 DTO로 변환 + public RecentSearchResDTO toRecentSearchRes(List keywords) { + return RecentSearchResDTO.builder() + .recentKeywords(keywords) + .build(); } } diff --git a/src/main/java/com/umc/finly/domain/record/converter/RecordFeedbackConverter.java b/src/main/java/com/umc/finly/domain/record/converter/RecordFeedbackConverter.java index cdcb82ea..05065d96 100644 --- a/src/main/java/com/umc/finly/domain/record/converter/RecordFeedbackConverter.java +++ b/src/main/java/com/umc/finly/domain/record/converter/RecordFeedbackConverter.java @@ -10,6 +10,14 @@ public class RecordFeedbackConverter { // RecordFeedback 엔티티를 응답 DTO로 변환 public RecordFeedbackResDTO toResDTO(RecordFeedback feedback) { - return RecordFeedbackResDTO.from(feedback); + return RecordFeedbackResDTO.builder() + .feedbackId(feedback.getId()) + .recordEntryId(feedback.getRecordEntryId()) + .status(feedback.getStatus()) + .content(feedback.getContent()) + .suggestion(feedback.getSuggestion()) + .createdAt(feedback.getCreatedAt()) + .updatedAt(feedback.getUpdatedAt()) + .build(); } } diff --git a/src/main/java/com/umc/finly/domain/record/dto/response/DailyReportResDTO.java b/src/main/java/com/umc/finly/domain/record/dto/response/DailyReportResDTO.java index 848d10b9..042f6afb 100644 --- a/src/main/java/com/umc/finly/domain/record/dto/response/DailyReportResDTO.java +++ b/src/main/java/com/umc/finly/domain/record/dto/response/DailyReportResDTO.java @@ -1,7 +1,5 @@ package com.umc.finly.domain.record.dto.response; -import com.umc.finly.domain.market.stock.entity.Stock; -import com.umc.finly.domain.record.entity.RecordEntry; import com.umc.finly.domain.record.enums.EmotionCode; import com.umc.finly.domain.record.enums.Session; import com.umc.finly.domain.record.enums.TradeAction; @@ -28,20 +26,4 @@ public class DailyReportResDTO { private EmotionCode emotionCode; // 감정 코드 private String name; // 종목명 (symbol 아닌 name) private String content; // AI 피드백 내용 - - // Entity → DTO 변환 - public static DailyReportResDTO from(RecordEntry entry, Stock stock, String content) { - return DailyReportResDTO.builder() - .recordId(entry.getId()) - .recordDate(entry.getRecordDate()) - .recordedAt(entry.getCreatedAt()) - .session(entry.getSession()) - .tradeAction(entry.getTradeAction()) - .unitPrice(entry.getUnitPrice()) - .quantity(entry.getQuantity()) - .emotionCode(entry.getEmotionCode()) - .name(stock.getName()) - .content(content) - .build(); - } } diff --git a/src/main/java/com/umc/finly/domain/record/dto/response/RecentSearchResDTO.java b/src/main/java/com/umc/finly/domain/record/dto/response/RecentSearchResDTO.java index 389b382a..7e495a18 100644 --- a/src/main/java/com/umc/finly/domain/record/dto/response/RecentSearchResDTO.java +++ b/src/main/java/com/umc/finly/domain/record/dto/response/RecentSearchResDTO.java @@ -12,11 +12,4 @@ public class RecentSearchResDTO { private List recentKeywords; // 최근 검색 키워드 리스트 (최신순) - - // 키워드 리스트 → DTO 변환 - public static RecentSearchResDTO from(List keywords) { - return RecentSearchResDTO.builder() - .recentKeywords(keywords) - .build(); - } } diff --git a/src/main/java/com/umc/finly/domain/record/dto/response/RecordCreateResDTO.java b/src/main/java/com/umc/finly/domain/record/dto/response/RecordCreateResDTO.java index b27718c1..600a45cd 100644 --- a/src/main/java/com/umc/finly/domain/record/dto/response/RecordCreateResDTO.java +++ b/src/main/java/com/umc/finly/domain/record/dto/response/RecordCreateResDTO.java @@ -1,8 +1,5 @@ package com.umc.finly.domain.record.dto.response; -import com.umc.finly.domain.market.stock.entity.Stock; -import com.umc.finly.domain.record.entity.RecordEntry; -import com.umc.finly.domain.record.entity.RecordFeedback; import com.umc.finly.domain.record.enums.EmotionCode; import com.umc.finly.domain.record.enums.Session; import com.umc.finly.domain.record.enums.TradeAction; @@ -36,28 +33,4 @@ public static class FeedbackInfo { private Long feedbackId; // 피드백 ID private String status; // 피드백 상태 (PENDING, GENERATING, COMPLETED, FAILED) } - - // Entity → DTO 변환 (팩토리 메서드) - public static RecordCreateResDTO from(RecordEntry entry, Stock stock, RecordFeedback feedback) { - FeedbackInfo feedbackInfo = null; - if (feedback != null) { - feedbackInfo = FeedbackInfo.builder() - .feedbackId(feedback.getId()) - .status(feedback.getStatus().name()) - .build(); - } - - return RecordCreateResDTO.builder() - .recordId(entry.getId()) - .recordDate(entry.getRecordDate()) - .recordedAt(entry.getCreatedAt()) - .session(entry.getSession()) - .tradeAction(entry.getTradeAction()) - .symbol(stock.getSymbol()) - .emotionCode(entry.getEmotionCode()) - .emotionIntensity(entry.getEmotionIntensity()) - .memo(entry.getMemo()) - .feedback(feedbackInfo) - .build(); - } } diff --git a/src/main/java/com/umc/finly/domain/record/dto/response/RecordDetailResDTO.java b/src/main/java/com/umc/finly/domain/record/dto/response/RecordDetailResDTO.java index 9fc71dbb..f13216b6 100644 --- a/src/main/java/com/umc/finly/domain/record/dto/response/RecordDetailResDTO.java +++ b/src/main/java/com/umc/finly/domain/record/dto/response/RecordDetailResDTO.java @@ -1,7 +1,5 @@ package com.umc.finly.domain.record.dto.response; -import com.umc.finly.domain.market.stock.entity.Stock; -import com.umc.finly.domain.record.entity.RecordEntry; import com.umc.finly.domain.record.enums.EmotionCode; import com.umc.finly.domain.record.enums.Session; import com.umc.finly.domain.record.enums.TradeAction; @@ -29,21 +27,4 @@ public class RecordDetailResDTO { private EmotionCode emotionCode; // 감정 코드 private Integer emotionIntensity; // 감정 강도 private String memo; // 메모 - - // Entity → DTO 변환 - public static RecordDetailResDTO from(RecordEntry entry, Stock stock) { - return RecordDetailResDTO.builder() - .recordId(entry.getId()) - .recordDate(entry.getRecordDate()) - .recordedAt(entry.getCreatedAt()) - .session(entry.getSession()) - .tradeAction(entry.getTradeAction()) - .symbol(stock.getSymbol()) - .unitPrice(entry.getUnitPrice()) - .quantity(entry.getQuantity()) - .emotionCode(entry.getEmotionCode()) - .emotionIntensity(entry.getEmotionIntensity()) - .memo(entry.getMemo()) - .build(); - } } diff --git a/src/main/java/com/umc/finly/domain/record/dto/response/RecordFeedbackResDTO.java b/src/main/java/com/umc/finly/domain/record/dto/response/RecordFeedbackResDTO.java index 41f15997..3282b038 100644 --- a/src/main/java/com/umc/finly/domain/record/dto/response/RecordFeedbackResDTO.java +++ b/src/main/java/com/umc/finly/domain/record/dto/response/RecordFeedbackResDTO.java @@ -1,6 +1,5 @@ package com.umc.finly.domain.record.dto.response; -import com.umc.finly.domain.record.entity.RecordFeedback; import com.umc.finly.domain.record.enums.FeedbackStatus; import lombok.Builder; import lombok.Getter; @@ -20,17 +19,4 @@ public class RecordFeedbackResDTO { private String suggestion; // AI 제안 (투자 조언) private LocalDateTime createdAt; // 생성 시각 private LocalDateTime updatedAt; // 수정 시각 (상태 변경 시 갱신) - - // Entity → DTO 변환 - public static RecordFeedbackResDTO from(RecordFeedback feedback) { - return RecordFeedbackResDTO.builder() - .feedbackId(feedback.getId()) - .recordEntryId(feedback.getRecordEntryId()) - .status(feedback.getStatus()) - .content(feedback.getContent()) - .suggestion(feedback.getSuggestion()) - .createdAt(feedback.getCreatedAt()) - .updatedAt(feedback.getUpdatedAt()) - .build(); - } } diff --git a/src/main/java/com/umc/finly/domain/record/dto/response/RecordSearchResDTO.java b/src/main/java/com/umc/finly/domain/record/dto/response/RecordSearchResDTO.java index 65df16b2..9ba22063 100644 --- a/src/main/java/com/umc/finly/domain/record/dto/response/RecordSearchResDTO.java +++ b/src/main/java/com/umc/finly/domain/record/dto/response/RecordSearchResDTO.java @@ -1,6 +1,5 @@ package com.umc.finly.domain.record.dto.response; -import com.umc.finly.domain.record.entity.RecordEntry; import com.umc.finly.domain.record.enums.EmotionCode; import com.umc.finly.domain.record.enums.Session; import com.umc.finly.domain.record.enums.TradeAction; @@ -36,22 +35,5 @@ public static class SearchEntry { private EmotionCode emotionCode; // 감정 코드 private Integer emotionIntensity; // 감정 강도 private String memo; // 메모 - - // Entity → DTO 변환 - public static SearchEntry from(RecordEntry entry, String symbol) { - return SearchEntry.builder() - .recordId(entry.getId()) - .recordDate(entry.getRecordDate()) - .recordedAt(entry.getCreatedAt()) - .session(entry.getSession()) - .tradeAction(entry.getTradeAction()) - .symbol(symbol) - .unitPrice(entry.getUnitPrice()) - .quantity(entry.getQuantity()) - .emotionCode(entry.getEmotionCode()) - .emotionIntensity(entry.getEmotionIntensity()) - .memo(entry.getMemo()) - .build(); - } } } diff --git a/src/main/java/com/umc/finly/domain/record/dto/response/RecordUpdateResDTO.java b/src/main/java/com/umc/finly/domain/record/dto/response/RecordUpdateResDTO.java index f58b6cb0..748c1c96 100644 --- a/src/main/java/com/umc/finly/domain/record/dto/response/RecordUpdateResDTO.java +++ b/src/main/java/com/umc/finly/domain/record/dto/response/RecordUpdateResDTO.java @@ -1,7 +1,5 @@ package com.umc.finly.domain.record.dto.response; -import com.umc.finly.domain.market.stock.entity.Stock; -import com.umc.finly.domain.record.entity.RecordEntry; import com.umc.finly.domain.record.enums.EmotionCode; import com.umc.finly.domain.record.enums.Session; import com.umc.finly.domain.record.enums.TradeAction; @@ -29,21 +27,4 @@ public class RecordUpdateResDTO { private EmotionCode emotionCode; // 감정 코드 private Integer emotionIntensity; // 감정 강도 private String memo; // 메모 - - // Entity → DTO 변환 - public static RecordUpdateResDTO from(RecordEntry entry, Stock stock) { - return RecordUpdateResDTO.builder() - .recordId(entry.getId()) - .recordDate(entry.getRecordDate()) - .updatedAt(entry.getUpdatedAt()) - .session(entry.getSession()) - .tradeAction(entry.getTradeAction()) - .symbol(stock.getSymbol()) - .unitPrice(entry.getUnitPrice()) - .quantity(entry.getQuantity()) - .emotionCode(entry.getEmotionCode()) - .emotionIntensity(entry.getEmotionIntensity()) - .memo(entry.getMemo()) - .build(); - } } diff --git a/src/main/java/com/umc/finly/domain/record/dto/response/TodayRecordResDTO.java b/src/main/java/com/umc/finly/domain/record/dto/response/TodayRecordResDTO.java index 37232dcc..60fe4fcf 100644 --- a/src/main/java/com/umc/finly/domain/record/dto/response/TodayRecordResDTO.java +++ b/src/main/java/com/umc/finly/domain/record/dto/response/TodayRecordResDTO.java @@ -1,6 +1,5 @@ package com.umc.finly.domain.record.dto.response; -import com.umc.finly.domain.record.entity.RecordEntry; import com.umc.finly.domain.record.enums.EmotionCode; import com.umc.finly.domain.record.enums.Session; import com.umc.finly.domain.record.enums.TradeAction; @@ -47,46 +46,5 @@ public static class TimelineEntry { private EmotionCode emotionCode; // 감정 코드 private Integer emotionIntensity; // 감정 강도 private String memo; // 메모 - - // Entity → DTO 변환 - public static TimelineEntry from(RecordEntry entry, String symbol) { - return TimelineEntry.builder() - .recordId(entry.getId()) - .recordDate(entry.getRecordDate()) - .recordedAt(entry.getCreatedAt()) - .session(entry.getSession()) - .tradeAction(entry.getTradeAction()) - .symbol(symbol) - .unitPrice(entry.getUnitPrice()) - .quantity(entry.getQuantity()) - .emotionCode(entry.getEmotionCode()) - .emotionIntensity(entry.getEmotionIntensity()) - .memo(entry.getMemo()) - .build(); - } - } - - // 프리즘 타이틀 생성 (오늘 기록들의 감정 흐름 요약) - // 기록 0개: 위로 메시지 - // 기록 1개: 해당 감정 강조 - // 기록 2개+: 첫 감정 → 마지막 감정 흐름 표현 - public static String generatePrismTitle(List entries) { - if (entries == null || entries.isEmpty()) { - return "괜찮아요. 기록이 없는 날도 있을 수 있죠."; - } - if (entries.size() == 1) { - EmotionCode emotion = entries.get(0).getEmotionCode(); - if (emotion == null) { // null 체크 - return "오늘 하루도 기록을 남겼네요!"; - } - return "{{" + emotion.getLabel() + "}}한 하루네요!"; - } - EmotionCode first = entries.get(0).getEmotionCode(); - EmotionCode last = entries.get(entries.size() - 1).getEmotionCode(); - if (first == null || last == null) { // null 체크 - return "오늘도 열심히 기록했네요!"; - } - // {{감정}}으로/로 조사 처리 (particle 사용) - return "{{" + first.getLabel() + "}}하게 시작해 {{" + last.getLabel() + "}}" + last.getParticle() + " 마무리한 날이네요."; } } diff --git a/src/main/java/com/umc/finly/domain/record/service/RecordServiceImpl.java b/src/main/java/com/umc/finly/domain/record/service/RecordServiceImpl.java index b1a36d0b..b22896fc 100644 --- a/src/main/java/com/umc/finly/domain/record/service/RecordServiceImpl.java +++ b/src/main/java/com/umc/finly/domain/record/service/RecordServiceImpl.java @@ -33,7 +33,6 @@ import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.LocalTime; import java.util.List; import java.util.Map; @@ -193,7 +192,7 @@ public DailyReportResDTO getDailyReport(Long memberId, Long recordId) { } } - return DailyReportResDTO.from(entry, stock, content); + return recordConverter.toDailyReportRes(entry, stock, content); } @Override @@ -202,14 +201,7 @@ public TodayRecordResDTO getTodayRecords(Long memberId, LocalDate date) { List entries = recordEntryRepository .findByMemberIdAndRecordDateOrderByCreatedAtAsc(memberId, date); - // 2. 프리즘 피드백 타이틀 자동 생성 (감정 기반 문구) - String title = TodayRecordResDTO.generatePrismTitle(entries); - TodayRecordResDTO.PrismFeedback prismFeedback = TodayRecordResDTO.PrismFeedback.builder() - .title(title) - .generatedAt(LocalDateTime.now()) - .build(); - - // 3. N+1 방지: 기록에 포함된 stockId 일괄 조회 + // 2. N+1 방지: 기록에 포함된 stockId 일괄 조회 List stockIds = entries.stream() .map(RecordEntry::getStockId) .distinct() @@ -217,22 +209,8 @@ public TodayRecordResDTO getTodayRecords(Long memberId, LocalDate date) { Map stockMap = stockRepository.findAllById(stockIds).stream() .collect(Collectors.toMap(Stock::getId, Function.identity())); - // 4. 타임라인 엔트리 변환 - List timelineSummary = entries.stream() - .map(entry -> { - Stock stock = stockMap.get(entry.getStockId()); - String symbol = stock != null ? stock.getSymbol() : ""; - return TodayRecordResDTO.TimelineEntry.from(entry, symbol); - }) - .toList(); - - return TodayRecordResDTO.builder() - .date(date) - .prismFeedback(prismFeedback) - .timelineSummary(timelineSummary) - .hasRecords(!entries.isEmpty()) - .recordCount(entries.size()) - .build(); + // 3. 응답 DTO 변환 (converter에 위임) + return recordConverter.toTodayRecordRes(date, entries, stockMap); } @Override @@ -272,19 +250,8 @@ public RecordSearchResDTO searchRecords(Long memberId, String keyword, EmotionCo Map stockMap = stockRepository.findAllById(resultStockIds).stream() .collect(Collectors.toMap(Stock::getId, Function.identity())); - // 6. 응답 DTO 생성 - List searchEntries = entries.stream() - .map(entry -> { - Stock stock = stockMap.get(entry.getStockId()); - String symbol = stock != null ? stock.getSymbol() : ""; - return RecordSearchResDTO.SearchEntry.from(entry, symbol); - }) - .toList(); - - return RecordSearchResDTO.builder() - .records(searchEntries) - .totalCount(searchEntries.size()) - .build(); + // 6. 응답 DTO 변환 (converter에 위임) + return recordConverter.toSearchRes(entries, stockMap); } @Override @@ -292,7 +259,7 @@ public RecentSearchResDTO getRecentSearchKeywords(Long memberId) { // 최근 검색 키워드 조회 (최신순, 상위 N개) List recentKeywords = searchHistoryRepository .findRecentKeywordsByMemberId(memberId, PageRequest.of(0, RECENT_SEARCH_LIMIT)); - return RecentSearchResDTO.from(recentKeywords); + return recordConverter.toRecentSearchRes(recentKeywords); } @Override