Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ Resolves: #12
<img width="200" alt="인증/회원" src="https://github.com/user-attachments/assets/b45948c7-ee4a-47b4-b049-b4d96adbb79b" />
</td>
<td align="center" valign="top">
<b>기록/리포트</b><br/>
<b>기록/리포트/AI피드백</b><br/>
<img width="200" alt="기록/리포트" src="https://github.com/user-attachments/assets/884a130d-54f6-4818-93b5-0973c10f6525" />
</td>
<td align="center" valign="top">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<RecordEntry> entries,
Map<Long, Stock> stockMap
) {
// 프리즘 피드백 타이틀 생성
String title = generatePrismTitle(entries);
TodayRecordResDTO.PrismFeedback prismFeedback = TodayRecordResDTO.PrismFeedback.builder()
.title(title)
.generatedAt(LocalDateTime.now())
.build();

// 타임라인 엔트리 변환
List<TodayRecordResDTO.TimelineEntry> 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<RecordEntry> 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<RecordEntry> entries, Map<Long, Stock> stockMap) {
List<RecordSearchResDTO.SearchEntry> 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<String> keywords) {
return RecentSearchResDTO.builder()
.recentKeywords(keywords)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,4 @@
public class RecentSearchResDTO {

private List<String> recentKeywords; // 최근 검색 키워드 리스트 (최신순)

// 키워드 리스트 → DTO 변환
public static RecentSearchResDTO from(List<String> keywords) {
return RecentSearchResDTO.builder()
.recentKeywords(keywords)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
}
}
}
Loading