Merge pull request #24 from hwanny1128/feat/meeting

Chore: 대시보드 조회 API todo완료율 제거
This commit is contained in:
Cho Yoon Jin 2025-10-28 14:16:07 +09:00 committed by GitHub
commit 8a812072d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 4794 additions and 27 deletions

File diff suppressed because it is too large Load Diff

View File

@ -86,7 +86,7 @@ public class DashboardDTO {
public static class StatisticsDTO { public static class StatisticsDTO {
private final Integer upcomingMeetingsCount; private final Integer upcomingMeetingsCount;
private final Integer activeTodosCount; private final Integer activeTodosCount;
private final Double todoCompletionRate; private final Integer draftMinutesCount;
} }
/** /**
@ -127,10 +127,9 @@ public class DashboardDTO {
.build()) .build())
.toList()) .toList())
.statistics(StatisticsDTO.builder() .statistics(StatisticsDTO.builder()
.upcomingMeetingsCount(dashboard.getStatistics().getScheduledMeetings()) .upcomingMeetingsCount(dashboard.getUpcomingMeetings().size())
.activeTodosCount(dashboard.getStatistics().getPendingTodos()) .activeTodosCount(dashboard.getStatistics().getPendingTodos())
.todoCompletionRate(dashboard.getStatistics().getTotalTodos() > 0 ? .draftMinutesCount(dashboard.getStatistics().getDraftMinutes())
(double) dashboard.getStatistics().getCompletedTodos() / dashboard.getStatistics().getTotalTodos() * 100 : 0.0)
.build()) .build())
.build(); .build();
} }

View File

@ -1,13 +1,19 @@
package com.unicorn.hgzero.meeting.biz.service; package com.unicorn.hgzero.meeting.biz.service;
import com.unicorn.hgzero.meeting.biz.domain.Dashboard; import com.unicorn.hgzero.meeting.biz.domain.Dashboard;
import com.unicorn.hgzero.meeting.biz.domain.Meeting;
import com.unicorn.hgzero.meeting.biz.usecase.in.dashboard.GetDashboardUseCase; import com.unicorn.hgzero.meeting.biz.usecase.in.dashboard.GetDashboardUseCase;
import com.unicorn.hgzero.meeting.biz.usecase.out.DashboardReader; import com.unicorn.hgzero.meeting.biz.usecase.out.DashboardReader;
import com.unicorn.hgzero.meeting.biz.usecase.out.MeetingReader;
import com.unicorn.hgzero.meeting.biz.usecase.out.MeetingWriter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
/** /**
* 대시보드 Service * 대시보드 Service
* Dashboard 관련 모든 UseCase 구현 * Dashboard 관련 모든 UseCase 구현
@ -18,15 +24,20 @@ import org.springframework.transaction.annotation.Transactional;
public class DashboardService implements GetDashboardUseCase { public class DashboardService implements GetDashboardUseCase {
private final DashboardReader dashboardReader; private final DashboardReader dashboardReader;
private final MeetingReader meetingReader;
private final MeetingWriter meetingWriter;
/** /**
* 사용자 대시보드 조회 * 사용자 대시보드 조회
*/ */
@Override @Override
@Transactional(readOnly = true) @Transactional
public Dashboard getDashboard(String userId) { public Dashboard getDashboard(String userId) {
log.debug("Getting dashboard for user: {}", userId); log.debug("Getting dashboard for user: {}", userId);
// 대시보드 조회 회의 상태 자동 업데이트
updateMeetingStatusesIfNeeded();
return dashboardReader.getDashboardByUserId(userId); return dashboardReader.getDashboardByUserId(userId);
} }
@ -34,10 +45,41 @@ public class DashboardService implements GetDashboardUseCase {
* 사용자 대시보드 (기간 필터) 조회 * 사용자 대시보드 (기간 필터) 조회
*/ */
@Override @Override
@Transactional(readOnly = true) @Transactional
public Dashboard getDashboardByPeriod(String userId, String period) { public Dashboard getDashboardByPeriod(String userId, String period) {
log.debug("Getting dashboard for user: {} with period: {}", userId, period); log.debug("Getting dashboard for user: {} with period: {}", userId, period);
// 대시보드 조회 회의 상태 자동 업데이트
updateMeetingStatusesIfNeeded();
return dashboardReader.getDashboardByUserIdAndPeriod(userId, period); return dashboardReader.getDashboardByUserIdAndPeriod(userId, period);
} }
/**
* 시간이 지난 SCHEDULED 회의들을 IN_PROGRESS로 상태 변경
*/
private void updateMeetingStatusesIfNeeded() {
LocalDateTime now = LocalDateTime.now();
try {
// SCHEDULED 상태이면서 시작 시간이 지난 회의들 조회
List<Meeting> meetingsToStart = meetingReader.findScheduledMeetingsBeforeTime(now);
if (!meetingsToStart.isEmpty()) {
log.info("Found {} meetings to update status to IN_PROGRESS", meetingsToStart.size());
for (Meeting meeting : meetingsToStart) {
// 회의 시작 처리 (상태를 IN_PROGRESS로 변경)
meeting.start();
meetingWriter.save(meeting);
log.debug("Updated meeting status to IN_PROGRESS: meetingId={}, title={}",
meeting.getMeetingId(), meeting.getTitle());
}
}
} catch (Exception e) {
log.error("Failed to update meeting statuses", e);
// 상태 업데이트 실패해도 대시보드 조회는 계속 진행
}
}
} }

View File

@ -50,4 +50,12 @@ public interface MeetingReader {
* @return 중복 회의 개수 * @return 중복 회의 개수
*/ */
long countConflictingMeetings(String organizerId, LocalDateTime startTime, LocalDateTime endTime); long countConflictingMeetings(String organizerId, LocalDateTime startTime, LocalDateTime endTime);
/**
* SCHEDULED 상태이면서 현재 시간보다 이전에 시작 예정인 회의 목록 조회
*
* @param currentTime 현재 시간
* @return 시작 시간이 지난 예정된 회의 목록
*/
List<Meeting> findScheduledMeetingsBeforeTime(LocalDateTime currentTime);
} }

View File

@ -122,13 +122,13 @@ public class DashboardResponse {
private final Integer upcomingMeetingsCount; private final Integer upcomingMeetingsCount;
@Schema(description = "Todo 완료율", example = "68.5") @Schema(description = "작성 중인 회의록 수", example = "3")
private final Double todoCompletionRate; private final Integer draftMinutesCount;
public static StatisticsResponse from(DashboardDTO.StatisticsDTO dto) { public static StatisticsResponse from(DashboardDTO.StatisticsDTO dto) {
return StatisticsResponse.builder() return StatisticsResponse.builder()
.upcomingMeetingsCount(dto.getUpcomingMeetingsCount()) .upcomingMeetingsCount(dto.getUpcomingMeetingsCount())
.todoCompletionRate(dto.getTodoCompletionRate()) .draftMinutesCount(dto.getDraftMinutesCount())
.build(); .build();
} }
} }

View File

@ -107,4 +107,11 @@ public class MeetingGateway implements MeetingReader, MeetingWriter {
public long countConflictingMeetings(String organizerId, LocalDateTime startTime, LocalDateTime endTime) { public long countConflictingMeetings(String organizerId, LocalDateTime startTime, LocalDateTime endTime) {
return meetingJpaRepository.countConflictingMeetings(organizerId, startTime, endTime); return meetingJpaRepository.countConflictingMeetings(organizerId, startTime, endTime);
} }
@Override
public List<Meeting> findScheduledMeetingsBeforeTime(LocalDateTime currentTime) {
return meetingJpaRepository.findByStatusAndScheduledAtBefore("SCHEDULED", currentTime).stream()
.map(this::enrichWithParticipants)
.collect(Collectors.toList());
}
} }

View File

@ -59,4 +59,9 @@ public interface MeetingJpaRepository extends JpaRepository<MeetingEntity, Strin
@org.springframework.data.repository.query.Param("startTime") LocalDateTime startTime, @org.springframework.data.repository.query.Param("startTime") LocalDateTime startTime,
@org.springframework.data.repository.query.Param("endTime") LocalDateTime endTime @org.springframework.data.repository.query.Param("endTime") LocalDateTime endTime
); );
/**
* 특정 상태이면서 지정된 시간보다 이전에 예정된 회의 목록 조회
*/
List<MeetingEntity> findByStatusAndScheduledAtBefore(String status, LocalDateTime dateTime);
} }

View File

@ -24,10 +24,13 @@ public class DashboardResponseMapper {
return null; return null;
} }
List<DashboardResponse.UpcomingMeetingResponse> upcomingMeetings = toUpcomingMeetingResponses(dashboard.getUpcomingMeetings());
List<DashboardResponse.RecentMinutesResponse> myMinutes = toRecentMinutesResponses(dashboard.getRecentMinutes());
return DashboardResponse.builder() return DashboardResponse.builder()
.upcomingMeetings(toUpcomingMeetingResponses(dashboard.getUpcomingMeetings())) .upcomingMeetings(upcomingMeetings)
.myMinutes(toRecentMinutesResponses(dashboard.getRecentMinutes())) .myMinutes(myMinutes)
.statistics(toStatisticsResponse(dashboard.getStatistics())) .statistics(toStatisticsResponse(dashboard, upcomingMeetings.size()))
.build(); .build();
} }
@ -91,27 +94,20 @@ public class DashboardResponseMapper {
/** /**
* Dashboard.Statistics를 StatisticsResponse로 변환 * Dashboard.Statistics를 StatisticsResponse로 변환
*/ */
private DashboardResponse.StatisticsResponse toStatisticsResponse(Dashboard.Statistics statistics) { private DashboardResponse.StatisticsResponse toStatisticsResponse(Dashboard dashboard, int upcomingMeetingsCount) {
Dashboard.Statistics statistics = dashboard.getStatistics();
if (statistics == null) { if (statistics == null) {
return DashboardResponse.StatisticsResponse.builder() return DashboardResponse.StatisticsResponse.builder()
.upcomingMeetingsCount(0) .upcomingMeetingsCount(upcomingMeetingsCount)
.todoCompletionRate(0.0) .draftMinutesCount(0)
.build(); .build();
} }
// Todo 완료율 계산
double todoCompletionRate = 0.0;
int totalTodos = statistics.getTotalTodos() != null ? statistics.getTotalTodos() : 0;
int completedTodos = statistics.getCompletedTodos() != null ? statistics.getCompletedTodos() : 0;
if (totalTodos > 0) {
todoCompletionRate = (double) completedTodos / totalTodos * 100.0;
}
return DashboardResponse.StatisticsResponse.builder() return DashboardResponse.StatisticsResponse.builder()
.upcomingMeetingsCount(statistics.getScheduledMeetings() != null ? .upcomingMeetingsCount(upcomingMeetingsCount)
statistics.getScheduledMeetings() : 0) .draftMinutesCount(statistics.getDraftMinutes() != null ?
.todoCompletionRate(todoCompletionRate) statistics.getDraftMinutes() : 0)
.build(); .build();
} }
} }