diff --git a/src/main/java/clap/server/adapter/outbound/infrastructure/elastic/PeriodConfig.java b/src/main/java/clap/server/adapter/outbound/infrastructure/elastic/PeriodConfig.java index 6515fc4c..e2f52ed9 100644 --- a/src/main/java/clap/server/adapter/outbound/infrastructure/elastic/PeriodConfig.java +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/elastic/PeriodConfig.java @@ -10,7 +10,7 @@ @Getter @RequiredArgsConstructor public enum PeriodConfig { - DAY(1, CalendarInterval.Hour, 11, 16), + DAY(1, CalendarInterval.Hour, 11, 13), WEEK(7, CalendarInterval.Day, 0, 10), MONTH(-1, CalendarInterval.Day, 0, 10); diff --git a/src/main/java/clap/server/adapter/outbound/infrastructure/elastic/TaskDocumentAdapter.java b/src/main/java/clap/server/adapter/outbound/infrastructure/elastic/TaskDocumentAdapter.java index 85caa97e..1bf2335d 100644 --- a/src/main/java/clap/server/adapter/outbound/infrastructure/elastic/TaskDocumentAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/elastic/TaskDocumentAdapter.java @@ -23,6 +23,7 @@ public class TaskDocumentAdapter implements TaskDocumentPort { private final TaskElasticRepository taskElasticRepository; private final ElasticsearchOperations elasticsearchOperations; + private static final String TIME_ZONE = "Asia/Seoul"; @Override public void saveStatistics(List statistics) { @@ -75,9 +76,12 @@ private NativeQuery buildPeriodTaskRequestQuery(PeriodConfig config) { .range(r -> r .date(d -> d .field("created_at") - .gte(String.valueOf(LocalDate.now().minusDays(config.getDaysToSubtract())))))) + .timeZone(TIME_ZONE) + .gte(String.valueOf(LocalDate.now().minusDays(config.getDaysToSubtract()))) + .lt(String.valueOf(LocalDate.now()))))) .withAggregation("period_task", AggregationBuilders.dateHistogram() .field("created_at") + .timeZone(TIME_ZONE) .calendarInterval(config.getCalendarInterval()) .build()._toAggregation()) .withMaxResults(0) @@ -90,12 +94,16 @@ private NativeQuery buildPeriodTaskProcessQuery(PeriodConfig config) { .range(r -> r .date(d -> d .field("created_at") - .gte(String.valueOf(LocalDate.now().minusDays(config.getDaysToSubtract())))))).build(); + .timeZone(TIME_ZONE) + .gte(String.valueOf(LocalDate.now().minusDays(config.getDaysToSubtract()))) + .lt(String.valueOf(LocalDate.now()))))) + .build(); NativeQuery statusQuery = NativeQuery.builder() .withQuery(q -> q .term(v -> v .field("status") - .value("completed"))).build(); + .value("completed"))) + .build(); return NativeQuery.builder() .withQuery(q -> q @@ -104,6 +112,7 @@ private NativeQuery buildPeriodTaskProcessQuery(PeriodConfig config) { ) .withAggregation("period_task", AggregationBuilders.dateHistogram() .field("created_at") + .timeZone(TIME_ZONE) .calendarInterval(config.getCalendarInterval()) .build()._toAggregation()) .withMaxResults(0) @@ -116,7 +125,9 @@ private NativeQuery buildCategoryTaskRequestQuery(PeriodConfig config) { .range(r -> r .date(d -> d .field("created_at") - .gte(String.valueOf(LocalDate.now().minusDays(config.getDaysToSubtract())))))) + .timeZone(TIME_ZONE) + .gte(String.valueOf(LocalDate.now().minusDays(config.getDaysToSubtract()))) + .lt(String.valueOf(LocalDate.now()))))) .withAggregation("category_task", AggregationBuilders.terms() .field("main_category") .build()._toAggregation()) @@ -130,7 +141,10 @@ private NativeQuery buildSubCategoryTaskRequestQuery(PeriodConfig config, String .range(r -> r .date(d -> d .field("created_at") - .gte(String.valueOf(LocalDate.now().minusDays(config.getDaysToSubtract())))))).build(); + .timeZone(TIME_ZONE) + .gte(String.valueOf(LocalDate.now().minusDays(config.getDaysToSubtract()))) + .lt(String.valueOf(LocalDate.now()))))) + .build(); NativeQuery categoryQuery = NativeQuery.builder() .withQuery(q -> q .term(v -> v @@ -155,7 +169,9 @@ private NativeQuery buildManagerTaskProcessQuery(PeriodConfig config) { .range(r -> r .date(d -> d .field("created_at") - .gte(String.valueOf(LocalDate.now().minusDays(config.getDaysToSubtract())))))) + .timeZone(TIME_ZONE) + .gte(String.valueOf(LocalDate.now().minusDays(config.getDaysToSubtract()))) + .lt(String.valueOf(LocalDate.now()))))) .withAggregation("manager_task", AggregationBuilders.terms() .field("processor") .build()._toAggregation()) diff --git a/src/main/java/clap/server/adapter/outbound/infrastructure/elastic/document/TaskDocument.java b/src/main/java/clap/server/adapter/outbound/infrastructure/elastic/document/TaskDocument.java index bc9b2747..f8fdefc4 100644 --- a/src/main/java/clap/server/adapter/outbound/infrastructure/elastic/document/TaskDocument.java +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/elastic/document/TaskDocument.java @@ -34,7 +34,7 @@ public TaskDocument(Task taskEntity) { this.mainCategory = taskEntity.getCategory().getMainCategory().getName(); this.subCategory = taskEntity.getCategory().getName(); this.status = taskEntity.getTaskStatus().name().toLowerCase(); - this.processor = taskEntity.getProcessor().getMemberInfo().getNickname(); + this.processor = taskEntity.getProcessor() != null ? taskEntity.getProcessor().getMemberInfo().getNickname() : ""; this.createdAt = taskEntity.getCreatedAt(); } } diff --git a/src/main/java/clap/server/adapter/outbound/persistense/TaskPersistenceAdapter.java b/src/main/java/clap/server/adapter/outbound/persistense/TaskPersistenceAdapter.java index bed7a99b..ab03cac5 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/TaskPersistenceAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/TaskPersistenceAdapter.java @@ -1,7 +1,7 @@ package clap.server.adapter.outbound.persistense; -import clap.server.adapter.inbound.web.dto.task.request.FilterTaskListRequest; import clap.server.adapter.inbound.web.dto.task.request.FilterTaskBoardRequest; +import clap.server.adapter.inbound.web.dto.task.request.FilterTaskListRequest; import clap.server.adapter.inbound.web.dto.task.request.FilterTeamStatusRequest; import clap.server.adapter.inbound.web.dto.task.response.TeamTaskResponse; import clap.server.adapter.outbound.persistense.entity.task.TaskEntity; @@ -12,19 +12,14 @@ import clap.server.application.port.outbound.task.LoadTaskPort; import clap.server.common.annotation.architecture.PersistenceAdapter; import clap.server.domain.model.task.Task; -import clap.server.exception.ApplicationException; -import clap.server.exception.code.NotificationErrorCode; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.domain.SliceImpl; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.List; import java.util.Optional; @@ -34,7 +29,6 @@ public class TaskPersistenceAdapter implements CommandTaskPort, LoadTaskPort { private final TaskRepository taskRepository; private final TaskPersistenceMapper taskPersistenceMapper; - private final ObjectMapper objectMapper; @Override public Task save(Task task) { @@ -74,7 +68,8 @@ public Optional findByIdAndStatus(Long id, TaskStatus status) { } @Override - public List findYesterdayTaskByDate(LocalDateTime now) { + public List findYesterdayTaskByDate() { + LocalDateTime now = LocalDateTime.of(LocalDate.now(), LocalTime.of(0, 0, 0, 0)); return taskRepository.findYesterdayTaskByUpdatedAtIsBetween(now.minusDays(1), now) .stream().map(taskPersistenceMapper::toDomain).toList(); } diff --git a/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskCustomRepositoryImpl.java b/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskCustomRepositoryImpl.java index 82be0f63..aa36df9b 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskCustomRepositoryImpl.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskCustomRepositoryImpl.java @@ -10,7 +10,6 @@ import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.OrderSpecifier; -import com.querydsl.core.types.dsl.CaseBuilder; import com.querydsl.core.types.dsl.DateTimePath; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -20,10 +19,12 @@ import org.springframework.stereotype.Repository; import java.time.LocalDateTime; +import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.stream.Collectors; +import static clap.server.adapter.inbound.web.dto.task.request.SortBy.CONTRIBUTE; import static clap.server.adapter.outbound.persistense.entity.task.QTaskEntity.taskEntity; import static com.querydsl.core.types.Order.ASC; import static com.querydsl.core.types.Order.DESC; @@ -91,25 +92,10 @@ public List findTeamStatus(Long memberId, FilterTeamStatusRequ SortBy sortBy = (filter != null && filter.sortBy() != null) ? filter.sortBy() : SortBy.DEFAULT; assert filter != null; - OrderSpecifier orderBy; - if (sortBy == SortBy.CONTRIBUTE) { - // 기여도순 (진행 중 + 검토 중 작업 개수 합 기준 내림차순) - orderBy = new CaseBuilder() - .when(taskEntity.taskStatus.eq(TaskStatus.IN_PROGRESS) - .or(taskEntity.taskStatus.eq(TaskStatus.IN_REVIEWING))) - .then(1) - .otherwise(0) - .desc(); - } else { - // 기본순 (닉네임 오름차순) - orderBy = taskEntity.processor.nickname.asc(); - } - // 쿼리 실행 List taskEntities = queryFactory .selectFrom(taskEntity) .where(builder) - .orderBy(orderBy) .fetch(); // null 또는 빈 리스트 처리 @@ -117,7 +103,7 @@ public List findTeamStatus(Long memberId, FilterTeamStatusRequ return List.of(); // 빈 리스트 반환 } - return taskEntities.stream() + List members = taskEntities.stream() .collect(Collectors.groupingBy(t -> t.getProcessor().getMemberId(), LinkedHashMap::new, Collectors.toList())) .entrySet().stream() .map(entry -> { @@ -157,6 +143,12 @@ public List findTeamStatus(Long memberId, FilterTeamStatusRequ ); }).collect(Collectors.toList()); + // 기여도순 (진행 중 + 검토 중 작업 개수 합 기준 내림차순) + if (sortBy.equals(CONTRIBUTE)) members.sort((a, b) -> b.totalTaskCount() - a.totalTaskCount()); + // 기본순 (닉네임 오름차순) + else members.sort(Comparator.comparing(TeamTaskResponse::nickname)); + + return members; } diff --git a/src/main/java/clap/server/application/port/outbound/task/LoadTaskPort.java b/src/main/java/clap/server/application/port/outbound/task/LoadTaskPort.java index 0b385460..e94f726d 100644 --- a/src/main/java/clap/server/application/port/outbound/task/LoadTaskPort.java +++ b/src/main/java/clap/server/application/port/outbound/task/LoadTaskPort.java @@ -16,7 +16,7 @@ public interface LoadTaskPort { Optional findById(Long id); - List findYesterdayTaskByDate(LocalDateTime now); + List findYesterdayTaskByDate(); Page findTasksRequestedByUser(Long requesterId, Pageable pageable, FilterTaskListRequest findTaskListRequest); diff --git a/src/main/java/clap/server/application/service/statistics/FindTaskProcessService.java b/src/main/java/clap/server/application/service/statistics/FindTaskProcessService.java index 8707ec3b..cb0bcc2a 100644 --- a/src/main/java/clap/server/application/service/statistics/FindTaskProcessService.java +++ b/src/main/java/clap/server/application/service/statistics/FindTaskProcessService.java @@ -31,16 +31,16 @@ public List aggregateManagerTaskProcess(String period) { @Override public List aggregatePeriodTaskProcess(String period) { if (period.equals("week") || period.equals("month")) { - return FindTaskStatisticsMapper.toStatisticsResponse( taskStatisticsPolicy.filterAndFormatWeekdayStatistics(taskDocumentPort.findPeriodTaskProcessByPeriod(period))); + return FindTaskStatisticsMapper.toStatisticsResponse(taskStatisticsPolicy.formatStatistics(taskDocumentPort.findPeriodTaskProcessByPeriod(period))); } - return FindTaskStatisticsMapper.toStatisticsResponse(taskDocumentPort.findPeriodTaskProcessByPeriod(period)); + return FindTaskStatisticsMapper.toStatisticsResponse(taskStatisticsPolicy.formatDayStatistics(taskDocumentPort.findPeriodTaskProcessByPeriod(period))); } @Override public List aggregatePeriodTaskRequest(String period) { if (period.equals("week") || period.equals("month")) { - return FindTaskStatisticsMapper.toStatisticsResponse(taskStatisticsPolicy.filterAndFormatWeekdayStatistics(taskDocumentPort.findPeriodTaskRequestByPeriod(period))); + return FindTaskStatisticsMapper.toStatisticsResponse(taskStatisticsPolicy.formatStatistics(taskDocumentPort.findPeriodTaskRequestByPeriod(period))); } - return FindTaskStatisticsMapper.toStatisticsResponse(taskDocumentPort.findPeriodTaskRequestByPeriod(period)); + return FindTaskStatisticsMapper.toStatisticsResponse(taskStatisticsPolicy.formatDayStatistics(taskDocumentPort.findPeriodTaskRequestByPeriod(period))); } } diff --git a/src/main/java/clap/server/application/service/statistics/StatisticsIndexingService.java b/src/main/java/clap/server/application/service/statistics/StatisticsIndexingService.java index 50366739..e865b261 100644 --- a/src/main/java/clap/server/application/service/statistics/StatisticsIndexingService.java +++ b/src/main/java/clap/server/application/service/statistics/StatisticsIndexingService.java @@ -7,8 +7,6 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; - @ApplicationService @RequiredArgsConstructor public class StatisticsIndexingService { @@ -19,7 +17,7 @@ public class StatisticsIndexingService { @Transactional public void IndexStatistics() { taskDocumentPort.saveStatistics( - loadTaskPort.findYesterdayTaskByDate(LocalDateTime.now().withNano(0)) + loadTaskPort.findYesterdayTaskByDate() ); } } diff --git a/src/main/java/clap/server/application/service/task/TeamStatusService.java b/src/main/java/clap/server/application/service/task/TeamStatusService.java index 2bc9b055..28c2b4d2 100644 --- a/src/main/java/clap/server/application/service/task/TeamStatusService.java +++ b/src/main/java/clap/server/application/service/task/TeamStatusService.java @@ -11,8 +11,6 @@ import java.util.List; -import static clap.server.adapter.inbound.web.dto.task.request.SortBy.CONTRIBUTE; - @ApplicationService public class TeamStatusService implements LoadTeamStatusUsecase, FilterTeamStatusUsecase { @@ -43,7 +41,6 @@ public TeamStatusResponse filterTeamStatus(FilterTeamStatusRequest filter) { // 전체 팀의 진행 중 & 검토 중 작업 수 계산 int totalInProgressTaskCount = members.stream().mapToInt(TeamTaskResponse::inProgressTaskCount).sum(); int totalInReviewingTaskCount = members.stream().mapToInt(TeamTaskResponse::inReviewingTaskCount).sum(); - if (filter.sortBy().equals(CONTRIBUTE)) members.sort((a, b) -> b.totalTaskCount() - a.totalTaskCount()); return new TeamStatusResponse(members, totalInProgressTaskCount, totalInReviewingTaskCount); } diff --git a/src/main/java/clap/server/domain/policy/task/TaskStatisticsPolicy.java b/src/main/java/clap/server/domain/policy/task/TaskStatisticsPolicy.java index 57b6a427..1797a05a 100644 --- a/src/main/java/clap/server/domain/policy/task/TaskStatisticsPolicy.java +++ b/src/main/java/clap/server/domain/policy/task/TaskStatisticsPolicy.java @@ -2,9 +2,9 @@ import clap.server.common.annotation.architecture.Policy; -import java.time.DayOfWeek; import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; @@ -14,9 +14,8 @@ public class TaskStatisticsPolicy { private static final String DISPLAY_FORMAT = "M월 d일"; - public Map filterAndFormatWeekdayStatistics(Map statistics) { + public Map formatStatistics(Map statistics) { return statistics.entrySet().stream() - .filter(this::isWeekday) .collect(Collectors.toMap( entry -> formatDate(entry.getKey()), Entry::getValue, @@ -25,13 +24,20 @@ public Map filterAndFormatWeekdayStatistics(Map stat )); } - private boolean isWeekday(Entry entry) { - LocalDate date = LocalDate.parse(entry.getKey()); - return !(date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY); - } - private String formatDate(String dateString) { LocalDate date = LocalDate.parse(dateString); return date.format(DateTimeFormatter.ofPattern(DISPLAY_FORMAT)); } + + public Map formatDayStatistics(Map statistics) { + for (int i = 0; i <= 23; i++) statistics.putIfAbsent(String.valueOf(i), 0L); + + return statistics.entrySet().stream() + .collect(Collectors.toMap( + entry -> Integer.parseInt(entry.getKey()) + "시", + Entry::getValue, + (v1, v2) -> v1, + LinkedHashMap::new + )); + } } diff --git a/src/test/java/clap/server/application/statistics/FindTaskProcessServiceTest.java b/src/test/java/clap/server/application/statistics/FindTaskProcessServiceTest.java index 0212a3db..a7a877b1 100644 --- a/src/test/java/clap/server/application/statistics/FindTaskProcessServiceTest.java +++ b/src/test/java/clap/server/application/statistics/FindTaskProcessServiceTest.java @@ -86,9 +86,10 @@ void aggregatePeriodTaskProcess() { when(taskDocumentPort.findPeriodTaskProcessByPeriod(eq("week"))).thenReturn(statistics); Map formattedStatistics = new TreeMap<>(); + formattedStatistics.put("2월 1일", 8L); formattedStatistics.put("1월 30일", 7L); formattedStatistics.put("1월 31일", 6L); - when(taskStatisticsPolicy.filterAndFormatWeekdayStatistics(statistics)).thenReturn(formattedStatistics); + when(taskStatisticsPolicy.formatStatistics(statistics)).thenReturn(formattedStatistics); //when List week = findTaskProcessService.aggregatePeriodTaskProcess("week"); @@ -98,6 +99,8 @@ void aggregatePeriodTaskProcess() { assertThat(week.get(0).count()).isEqualTo(7L); assertThat(week.get(1).key()).isEqualTo("1월 31일"); assertThat(week.get(1).count()).isEqualTo(6L); + assertThat(week.get(2).key()).isEqualTo("2월 1일"); + assertThat(week.get(2).count()).isEqualTo(8L); } @Test @@ -112,9 +115,10 @@ void aggregatePeriodTaskRequest() { when(taskDocumentPort.findPeriodTaskRequestByPeriod(eq("week"))).thenReturn(statistics); Map formattedStatistics = new TreeMap<>(); + formattedStatistics.put("2월 1일", 8L); formattedStatistics.put("1월 30일", 7L); formattedStatistics.put("1월 31일", 6L); - when(taskStatisticsPolicy.filterAndFormatWeekdayStatistics(statistics)).thenReturn(formattedStatistics); + when(taskStatisticsPolicy.formatStatistics(statistics)).thenReturn(formattedStatistics); //when List week = findTaskProcessService.aggregatePeriodTaskRequest("week"); @@ -124,5 +128,7 @@ void aggregatePeriodTaskRequest() { assertThat(week.get(0).count()).isEqualTo(7L); assertThat(week.get(1).key()).isEqualTo("1월 31일"); assertThat(week.get(1).count()).isEqualTo(6L); + assertThat(week.get(2).key()).isEqualTo("2월 1일"); + assertThat(week.get(2).count()).isEqualTo(8L); } } \ No newline at end of file diff --git a/src/test/java/clap/server/domain/policy/task/TaskStatisticsPolicyTest.java b/src/test/java/clap/server/domain/policy/task/TaskStatisticsPolicyTest.java index 0b6e1694..76413675 100644 --- a/src/test/java/clap/server/domain/policy/task/TaskStatisticsPolicyTest.java +++ b/src/test/java/clap/server/domain/policy/task/TaskStatisticsPolicyTest.java @@ -11,7 +11,7 @@ class TaskStatisticsPolicyTest { private final TaskStatisticsPolicy taskStatisticsPolicy = new TaskStatisticsPolicy(); @Test - void filterAndFormatWeekdayStatistics() { + void formatStatistics() { //given Map statistics = new TreeMap<>(); statistics.put("2025-02-01", 8L); @@ -19,11 +19,12 @@ void filterAndFormatWeekdayStatistics() { statistics.put("2025-01-31", 6L); Map formattedStatistics = new TreeMap<>(); + formattedStatistics.put("2월 1일", 8L); formattedStatistics.put("1월 30일", 7L); formattedStatistics.put("1월 31일", 6L); //when - Map weekdayStatistics = taskStatisticsPolicy.filterAndFormatWeekdayStatistics(statistics); + Map weekdayStatistics = taskStatisticsPolicy.formatStatistics(statistics); //then assertThat(weekdayStatistics).isEqualTo(formattedStatistics);