mirror of
https://github.com/hwanny1128/HGZero.git
synced 2026-06-13 09:29:10 +00:00
Chore: 회의록 목록 조회 API 실제 데이터 연동
This commit is contained in:
@@ -116,6 +116,11 @@ public class MinutesDTO {
|
||||
*/
|
||||
private final Integer completedTodoCount;
|
||||
|
||||
/**
|
||||
* 참석자 수
|
||||
*/
|
||||
private final Integer participantCount;
|
||||
|
||||
/**
|
||||
* 회의 정보
|
||||
*/
|
||||
|
||||
@@ -44,6 +44,7 @@ public class MinutesService implements
|
||||
private final MinutesSectionReader minutesSectionReader;
|
||||
private final MinutesSectionWriter minutesSectionWriter;
|
||||
private final CacheService cacheService;
|
||||
private final com.unicorn.hgzero.meeting.biz.usecase.out.ParticipantReader participantReader;
|
||||
|
||||
/**
|
||||
* 회의록 생성
|
||||
@@ -317,6 +318,29 @@ public class MinutesService implements
|
||||
* Minutes 도메인을 MinutesDTO로 변환
|
||||
*/
|
||||
private MinutesDTO convertToMinutesDTO(Minutes minutes) {
|
||||
// 회의 정보 조회
|
||||
String meetingTitle = "회의 제목 없음";
|
||||
try {
|
||||
Meeting meeting = meetingReader.findById(minutes.getMeetingId()).orElse(null);
|
||||
if (meeting != null) {
|
||||
meetingTitle = meeting.getTitle();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("회의 정보 조회 실패 - meetingId: {}", minutes.getMeetingId(), e);
|
||||
}
|
||||
|
||||
// TODO 정보는 추후 구현 (현재는 기본값)
|
||||
int todoCount = 0;
|
||||
int completedTodoCount = 0;
|
||||
|
||||
// 참석자 수 계산 (모든 참석자)
|
||||
int participantCount = 0;
|
||||
try {
|
||||
participantCount = participantReader.countParticipantsByMeetingId(minutes.getMeetingId());
|
||||
} catch (Exception e) {
|
||||
log.warn("참석자 수 계산 실패 - meetingId: {}", minutes.getMeetingId(), e);
|
||||
}
|
||||
|
||||
return MinutesDTO.builder()
|
||||
.minutesId(minutes.getMinutesId())
|
||||
.meetingId(minutes.getMeetingId())
|
||||
@@ -327,11 +351,11 @@ public class MinutesService implements
|
||||
.lastModifiedAt(minutes.getLastModifiedAt())
|
||||
.createdBy(minutes.getCreatedBy())
|
||||
.lastModifiedBy(minutes.getLastModifiedBy())
|
||||
// 추가 필드들은 임시로 기본값 설정
|
||||
.meetingTitle("임시 회의 제목")
|
||||
.todoCount(0)
|
||||
.completedTodoCount(0)
|
||||
.memo("")
|
||||
.meetingTitle(meetingTitle)
|
||||
.todoCount(todoCount)
|
||||
.completedTodoCount(completedTodoCount)
|
||||
.participantCount(participantCount)
|
||||
.memo("") // 메모 필드는 추후 구현
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -21,4 +21,9 @@ public interface ParticipantReader {
|
||||
* 특정 회의에 특정 사용자가 참석자로 등록되어 있는지 확인
|
||||
*/
|
||||
boolean existsParticipant(String meetingId, String userId);
|
||||
|
||||
/**
|
||||
* 회의 ID로 참석자 수 조회 (모든 참석자)
|
||||
*/
|
||||
int countParticipantsByMeetingId(String meetingId);
|
||||
}
|
||||
|
||||
+139
-39
@@ -2,6 +2,7 @@ package com.unicorn.hgzero.meeting.infra.controller;
|
||||
|
||||
import com.unicorn.hgzero.common.dto.ApiResponse;
|
||||
import com.unicorn.hgzero.common.exception.BusinessException;
|
||||
import com.unicorn.hgzero.meeting.biz.domain.Minutes;
|
||||
import com.unicorn.hgzero.meeting.biz.dto.MinutesDTO;
|
||||
import com.unicorn.hgzero.meeting.biz.service.MinutesService;
|
||||
import com.unicorn.hgzero.meeting.biz.service.MinutesSectionService;
|
||||
@@ -16,6 +17,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -69,40 +71,37 @@ public class MinutesController {
|
||||
userId, page, size, status, participationType, search);
|
||||
|
||||
try {
|
||||
// Mock 데이터 생성 (프론트엔드 테스트용)
|
||||
List<MinutesListResponse.MinutesItem> mockMinutes = createMockMinutesList(userId);
|
||||
// 정렬 및 페이징 설정
|
||||
Sort sort = createSort(sortBy, sortDir);
|
||||
Pageable pageable = PageRequest.of(page, size, sort);
|
||||
|
||||
// 실제 데이터 조회
|
||||
Page<MinutesDTO> minutesPage = minutesService.getMinutesListByUserId(userId, pageable);
|
||||
|
||||
// 필터링 적용
|
||||
List<MinutesListResponse.MinutesItem> filteredMinutes = mockMinutes.stream()
|
||||
.filter(item -> filterByStatus(item, status))
|
||||
.filter(item -> filterByParticipationType(item, participationType, userId))
|
||||
.filter(item -> filterBySearch(item, search))
|
||||
// DTO를 Response 형식으로 변환
|
||||
List<MinutesListResponse.MinutesItem> minutesList = minutesPage.getContent().stream()
|
||||
.map(this::convertToMinutesItem)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 정렬 적용
|
||||
applySorting(filteredMinutes, sortBy, sortDir);
|
||||
|
||||
// 페이징 적용
|
||||
int startIndex = page * size;
|
||||
int endIndex = Math.min(startIndex + size, filteredMinutes.size());
|
||||
List<MinutesListResponse.MinutesItem> pagedMinutes =
|
||||
startIndex < filteredMinutes.size() ?
|
||||
filteredMinutes.subList(startIndex, endIndex) :
|
||||
List.of();
|
||||
// 필터링 적용 (상태별)
|
||||
List<MinutesListResponse.MinutesItem> filteredMinutes = minutesList.stream()
|
||||
.filter(item -> filterByStatus(item, status))
|
||||
.filter(item -> filterBySearch(item, search))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 통계 계산
|
||||
MinutesListResponse.Statistics stats = calculateStatistics(mockMinutes, participationType, userId);
|
||||
// 통계 계산 (전체 데이터 기준)
|
||||
MinutesListResponse.Statistics stats = calculateRealStatistics(userId, participationType);
|
||||
|
||||
MinutesListResponse response = MinutesListResponse.builder()
|
||||
.minutesList(pagedMinutes)
|
||||
.totalCount(filteredMinutes.size())
|
||||
.minutesList(filteredMinutes)
|
||||
.totalCount((int) minutesPage.getTotalElements())
|
||||
.currentPage(page)
|
||||
.totalPages((int) Math.ceil((double) filteredMinutes.size() / size))
|
||||
.totalPages(minutesPage.getTotalPages())
|
||||
.statistics(stats)
|
||||
.build();
|
||||
|
||||
log.info("회의록 목록 조회 성공 - userId: {}, total: {}, filtered: {}, paged: {}",
|
||||
userId, mockMinutes.size(), filteredMinutes.size(), pagedMinutes.size());
|
||||
log.info("회의록 목록 조회 성공 - userId: {}, total: {}, filtered: {}",
|
||||
userId, minutesPage.getTotalElements(), filteredMinutes.size());
|
||||
return ResponseEntity.ok(ApiResponse.success(response));
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -126,13 +125,14 @@ public class MinutesController {
|
||||
log.info("회의록 상세 조회 요청 - userId: {}, minutesId: {}", userId, minutesId);
|
||||
|
||||
try {
|
||||
// Mock 데이터 생성 (프론트엔드 테스트용)
|
||||
MinutesDetailResponse response = createMockMinutesDetail(minutesId, userId);
|
||||
// 실제 데이터 조회
|
||||
MinutesDTO minutesDTO = minutesService.getMinutesById(minutesId);
|
||||
MinutesDetailResponse response = convertToMinutesDetailResponse(minutesDTO);
|
||||
|
||||
// 캐시 저장
|
||||
cacheService.cacheMinutesDetail(minutesId, response);
|
||||
|
||||
log.info("회의록 상세 조회 성공 (Mock) - minutesId: {}", minutesId);
|
||||
log.info("회의록 상세 조회 성공 - minutesId: {}", minutesId);
|
||||
return ResponseEntity.ok(ApiResponse.success(response));
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -326,7 +326,60 @@ public class MinutesController {
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
/**
|
||||
* 정렬 옵션 생성
|
||||
*/
|
||||
private Sort createSort(String sortBy, String sortDir) {
|
||||
Sort.Direction direction = "asc".equalsIgnoreCase(sortDir) ? Sort.Direction.ASC : Sort.Direction.DESC;
|
||||
|
||||
switch (sortBy) {
|
||||
case "title":
|
||||
return Sort.by(direction, "title");
|
||||
case "meeting":
|
||||
return Sort.by(direction, "createdAt"); // 회의 일시로 정렬 (임시로 생성일시 사용)
|
||||
case "modified":
|
||||
default:
|
||||
return Sort.by(direction, "lastModifiedAt");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 실제 통계 계산
|
||||
*/
|
||||
private MinutesListResponse.Statistics calculateRealStatistics(String userId, String participationType) {
|
||||
try {
|
||||
// 전체 회의록 조회 (작성자 기준)
|
||||
List<Minutes> allMinutes = minutesService.getMinutesByCreator(userId);
|
||||
|
||||
long totalCount = allMinutes.size();
|
||||
long draftCount = allMinutes.stream()
|
||||
.filter(m -> "DRAFT".equals(m.getStatus()))
|
||||
.count();
|
||||
long completeCount = allMinutes.stream()
|
||||
.filter(m -> "FINALIZED".equals(m.getStatus()))
|
||||
.count();
|
||||
|
||||
return MinutesListResponse.Statistics.builder()
|
||||
.totalCount(totalCount)
|
||||
.draftCount(draftCount)
|
||||
.completeCount(completeCount)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.warn("통계 계산 실패, 기본값 반환 - userId: {}", userId, e);
|
||||
return MinutesListResponse.Statistics.builder()
|
||||
.totalCount(0L)
|
||||
.draftCount(0L)
|
||||
.completeCount(0L)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private MinutesListResponse.MinutesItem convertToMinutesItem(MinutesDTO minutesDTO) {
|
||||
// 완료율 계산
|
||||
int completionRate = minutesDTO.getTodoCount() > 0 ?
|
||||
(minutesDTO.getCompletedTodoCount() * 100) / minutesDTO.getTodoCount() : 100;
|
||||
|
||||
return MinutesListResponse.MinutesItem.builder()
|
||||
.minutesId(minutesDTO.getMinutesId())
|
||||
.title(minutesDTO.getTitle())
|
||||
@@ -335,10 +388,14 @@ public class MinutesController {
|
||||
.version(minutesDTO.getVersion())
|
||||
.createdAt(minutesDTO.getCreatedAt())
|
||||
.lastModifiedAt(minutesDTO.getLastModifiedAt())
|
||||
.meetingDate(minutesDTO.getCreatedAt()) // 임시로 생성일시 사용
|
||||
.createdBy(minutesDTO.getCreatedBy())
|
||||
.lastModifiedBy(minutesDTO.getLastModifiedBy())
|
||||
.participantCount(minutesDTO.getParticipantCount() != null ? minutesDTO.getParticipantCount() : 0)
|
||||
.todoCount(minutesDTO.getTodoCount())
|
||||
.completedTodoCount(minutesDTO.getCompletedTodoCount())
|
||||
.completionRate(completionRate)
|
||||
.isCreatedByUser(true) // 현재는 작성자 기준으로만 조회하므로 true
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -497,18 +554,10 @@ public class MinutesController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 참여 유형별 필터링
|
||||
* 참여 유형별 필터링 - 현재는 사용하지 않음 (작성자 기준으로만 조회)
|
||||
*/
|
||||
private boolean filterByParticipationType(MinutesListResponse.MinutesItem item, String participationType, String userId) {
|
||||
if (participationType == null || participationType.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if ("created".equals(participationType)) {
|
||||
return item.isCreatedByUser();
|
||||
}
|
||||
if ("attended".equals(participationType)) {
|
||||
return !item.isCreatedByUser();
|
||||
}
|
||||
// 현재는 작성자 기준으로만 조회하므로 항상 true 반환
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -870,8 +919,59 @@ public class MinutesController {
|
||||
|
||||
|
||||
private MinutesDetailResponse convertToMinutesDetailResponse(MinutesDTO minutesDTO) {
|
||||
// Mock 데이터로 대체 (프로토타입용)
|
||||
return createMockMinutesDetail(minutesDTO.getMinutesId(), "user123");
|
||||
// 기본 회의록 정보는 실제 데이터 사용
|
||||
MinutesDetailResponse.MeetingInfo meetingInfo = MinutesDetailResponse.MeetingInfo.builder()
|
||||
.meetingId(minutesDTO.getMeetingId())
|
||||
.title(minutesDTO.getMeetingTitle())
|
||||
.location("회의실 정보 없음") // 추후 실제 데이터로 변경 필요
|
||||
.participants(List.of()) // 추후 실제 참석자 정보로 변경 필요
|
||||
.build();
|
||||
|
||||
MinutesDetailResponse.Statistics stats = MinutesDetailResponse.Statistics.builder()
|
||||
.participantCount(minutesDTO.getParticipantCount() != null ? minutesDTO.getParticipantCount() : 0)
|
||||
.durationMinutes(90) // 기본값 - 추후 실제 데이터로 변경 필요
|
||||
.agendaCount(0) // 기본값 - 추후 실제 데이터로 변경 필요
|
||||
.todoCount(minutesDTO.getTodoCount() != null ? minutesDTO.getTodoCount() : 0)
|
||||
.build();
|
||||
|
||||
MinutesDetailResponse.DashboardInfo dashboardInfo = MinutesDetailResponse.DashboardInfo.builder()
|
||||
.keyPoints(List.of()) // 추후 실제 데이터로 변경 필요
|
||||
.keywords(List.of()) // 추후 실제 데이터로 변경 필요
|
||||
.stats(stats)
|
||||
.decisions(List.of()) // 추후 실제 데이터로 변경 필요
|
||||
.todoProgress(MinutesDetailResponse.TodoProgress.builder()
|
||||
.totalCount(minutesDTO.getTodoCount() != null ? minutesDTO.getTodoCount() : 0)
|
||||
.completedCount(minutesDTO.getCompletedTodoCount() != null ? minutesDTO.getCompletedTodoCount() : 0)
|
||||
.progressPercentage(calculateProgressPercentage(minutesDTO.getTodoCount(), minutesDTO.getCompletedTodoCount()))
|
||||
.todos(List.of()) // 추후 실제 데이터로 변경 필요
|
||||
.build())
|
||||
.relatedMinutes(List.of()) // 추후 실제 데이터로 변경 필요
|
||||
.build();
|
||||
|
||||
return MinutesDetailResponse.builder()
|
||||
.minutesId(minutesDTO.getMinutesId())
|
||||
.title(minutesDTO.getTitle())
|
||||
.memo(minutesDTO.getMemo() != null ? minutesDTO.getMemo() : "")
|
||||
.status(minutesDTO.getStatus())
|
||||
.version(minutesDTO.getVersion())
|
||||
.createdAt(minutesDTO.getCreatedAt())
|
||||
.lastModifiedAt(minutesDTO.getLastModifiedAt())
|
||||
.createdBy(minutesDTO.getCreatedBy())
|
||||
.lastModifiedBy(minutesDTO.getLastModifiedBy())
|
||||
.meeting(meetingInfo)
|
||||
.dashboard(dashboardInfo)
|
||||
.agendas(List.of()) // 추후 실제 안건 데이터로 변경 필요
|
||||
.build();
|
||||
}
|
||||
|
||||
private int calculateProgressPercentage(Integer totalCount, Integer completedCount) {
|
||||
if (totalCount == null || totalCount == 0) {
|
||||
return 100;
|
||||
}
|
||||
if (completedCount == null) {
|
||||
return 0;
|
||||
}
|
||||
return (completedCount * 100) / totalCount;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -45,6 +45,12 @@ public class ParticipantGateway implements ParticipantReader, ParticipantWriter
|
||||
return participantRepository.existsByMeetingIdAndUserId(meetingId, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public int countParticipantsByMeetingId(String meetingId) {
|
||||
return participantRepository.countByMeetingId(meetingId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void saveParticipant(String meetingId, String userId) {
|
||||
|
||||
+5
@@ -42,4 +42,9 @@ public interface MeetingParticipantJpaRepository extends JpaRepository<MeetingPa
|
||||
* 회의 ID와 사용자 ID로 참석자 존재 여부 확인
|
||||
*/
|
||||
boolean existsByMeetingIdAndUserId(String meetingId, String userId);
|
||||
|
||||
/**
|
||||
* 회의 ID로 참석자 수 조회 (모든 참석자)
|
||||
*/
|
||||
int countByMeetingId(String meetingId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user