Skip to content

Commit

Permalink
v1.8.0 (#326)
Browse files Browse the repository at this point in the history
  • Loading branch information
kdomo authored Feb 12, 2024
2 parents 33815cf + 101682e commit 6035d38
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ public MissionSummaryListResponse findSummaryList(LocalDate date) {

List<MissionSummaryItem> result =
missions.stream()
.map(mission -> getMissionSummaryItem(mission))
.map(mission -> getMissionSummaryItem(mission, date))
.sorted(
Comparator.comparing(MissionSummaryItem::missionStatus)
.reversed()
Expand All @@ -322,12 +322,6 @@ public MissionSummaryListResponse findSummaryList(LocalDate date) {
.reversed()))
.collect(Collectors.toList());

result.sort(
Comparator.comparing(MissionSummaryItem::missionStatus)
.reversed()
.thenComparing(
Comparator.comparing(MissionSummaryItem::finishedAt).reversed()));

long missionAllCount = missions.size();
long missionCompleteCount =
result.stream()
Expand All @@ -341,13 +335,17 @@ public MissionSummaryListResponse findSummaryList(LocalDate date) {
missionAllCount, missionCompleteCount, missionNoneCount, result);
}

private static MissionSummaryItem getMissionSummaryItem(Mission mission) {
private static MissionSummaryItem getMissionSummaryItem(Mission mission, LocalDate date) {
boolean isCompleted =
mission.getMissionRecords().stream()
.anyMatch(
missionRecord ->
missionRecord.getUploadStatus()
== ImageUploadStatus.COMPLETE);
== ImageUploadStatus.COMPLETE
&& missionRecord
.getStartedAt()
.toLocalDate()
.equals(date));
return isCompleted
? MissionSummaryItem.of(mission, MissionStatus.COMPLETED)
: MissionSummaryItem.of(mission, MissionStatus.NONE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ public List<Mission> findMissionsWithRecordsByRelations(
jpaQueryFactory
.selectFrom(mission)
.leftJoin(mission.missionRecords, missionRecord)
.where(memberIdEq(memberId), visibilityByRelations(existsMemberRelations))
.where(
memberIdEq(memberId),
durationStatusInProgress(),
visibilityByRelations(existsMemberRelations))
.orderBy(mission.id.desc())
.fetchJoin();
return query.fetch();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,10 @@ public ResponseEntity<Void> missionRecordInProgressDelete() {
missionRecordService.deleteInProgressMissionRecord();
return ResponseEntity.ok().build();
}

@Operation(summary = "미션별 상세 통계", description = "미션별 통계로 미션에 대한 현황을 파악합니다.")
@GetMapping("/statistics/{missionId}")
public MissionStatisticsResponse missionStatistics(@PathVariable Long missionId) {
return missionRecordService.findMissionStatistics(missionId);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.depromeet.domain.missionRecord.application;

import com.depromeet.domain.member.domain.Member;
import com.depromeet.domain.mission.application.MissionService;
import com.depromeet.domain.mission.dao.MissionRepository;
import com.depromeet.domain.mission.domain.Mission;
import com.depromeet.domain.missionRecord.dao.MissionRecordRepository;
Expand All @@ -11,6 +12,7 @@
import com.depromeet.domain.missionRecord.dto.request.MissionRecordCreateRequest;
import com.depromeet.domain.missionRecord.dto.request.MissionRecordUpdateRequest;
import com.depromeet.domain.missionRecord.dto.response.*;
import com.depromeet.domain.missionRecord.dto.response.MissionStatisticsResponse;
import com.depromeet.global.error.exception.CustomException;
import com.depromeet.global.error.exception.ErrorCode;
import com.depromeet.global.util.MemberUtil;
Expand All @@ -32,6 +34,7 @@ public class MissionRecordService {
private static final int DAYS_ADJUSTMENT = 1;

private final MemberUtil memberUtil;
private final MissionService missionService;
private final MissionRepository missionRepository;
private final MissionRecordRepository missionRecordRepository;
private final MissionRecordTtlRepository missionRecordTtlRepository;
Expand Down Expand Up @@ -165,6 +168,85 @@ public void deleteInProgressMissionRecord() {
}
}

@Transactional(readOnly = true)
public MissionStatisticsResponse findMissionStatistics(Long missionId) {
final Mission mission = findMissionById(missionId);
final LocalDateTime startedAt = mission.getStartedAt();
final LocalDateTime finishedAt = mission.getFinishedAt();
final LocalDateTime today = LocalDateTime.now();

List<MissionRecord> missionRecords =
missionRecordRepository.findAllByCompletedMission(missionId);
LocalDateTime endedAt = finishedAt.isBefore(today) ? finishedAt : today;

// 달성률
double totalMissionAttainRate =
calculateMissionAttainRate(missionRecords.size(), startedAt, endedAt);

// 시간표 생성
List<FocusMissionRecordItem> timeTable = generateRecordTimeTable(missionRecords);

// 최대 연속성 계산
long maxContinuousSuccessDay =
calculateMaxContinuousSuccessDay(startedAt, finishedAt, missionRecords);

long totalSymbolStack = 0;
long sumDuration = 0;
for (FocusMissionRecordItem timeOfDay : timeTable) {
totalSymbolStack += timeOfDay.symbolStack();
sumDuration += timeOfDay.durationMinute();
}

// 전체 수행 시간 (시간)
long totalMissionHour = sumDuration / 60;

// 전체 수행 시간 (분)
long totalMissionMinute = sumDuration % 60;

return MissionStatisticsResponse.of(
totalMissionHour,
totalMissionMinute,
totalSymbolStack,
maxContinuousSuccessDay,
missionRecords.size(),
totalMissionAttainRate,
startedAt,
finishedAt,
timeTable);
}

private List<FocusMissionRecordItem> generateRecordTimeTable(
List<MissionRecord> missionRecords) {
return missionRecords.stream().map(FocusMissionRecordItem::from).toList();
}

private long calculateMaxContinuousSuccessDay(
LocalDateTime startedAt, LocalDateTime finishedAt, List<MissionRecord> missionRecords) {
long continuousSuccessDay = 1;
long maxContinuousSuccessDay = 0;
LocalDate previousDate = null;

for (MissionRecord missionRecord : missionRecords) {
LocalDate currentDate = missionRecord.getStartedAt().toLocalDate();

// startedAt과 finishedAt 사이에 있는 일자일 때만 고려
if (!(currentDate.isAfter(startedAt.toLocalDate())
&& currentDate.isBefore(finishedAt.toLocalDate()))) {
continue;
}
if (previousDate != null && currentDate.minusDays(1).isEqual(previousDate)) {
continuousSuccessDay++;
} else {
continuousSuccessDay = 1; // 연속성이 깨진 경우 초기화
}

maxContinuousSuccessDay = Math.max(continuousSuccessDay, maxContinuousSuccessDay);

previousDate = currentDate;
}
return maxContinuousSuccessDay;
}

private void validateMissionRecordDuration(Duration duration) {
if (duration.getSeconds() > 3600L) {
throw new CustomException(ErrorCode.MISSION_RECORD_DURATION_OVERBALANCE);
Expand All @@ -176,4 +258,10 @@ private void validateMissionRecordUserMismatch(Mission mission, Member member) {
throw new CustomException(ErrorCode.MISSION_RECORD_USER_MISMATCH);
}
}

private double calculateMissionAttainRate(
long completeSize, LocalDateTime startedAt, LocalDateTime endedAt) {
long totalSize = Duration.between(startedAt, endedAt).toDays() + DAYS_ADJUSTMENT;
return Math.round((double) completeSize / totalSize * 1000) / 10.0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public interface MissionRecordRepositoryCustom {

List<MissionRecord> findAllByMissionIdAndYearMonth(Long missionId, YearMonth yearMonth);

List<MissionRecord> findAllByCompletedMission(Long missionId);

List<FeedOneResponse> findFeedAll(List<Member> members);

List<MissionRecord> findFeedAllByMemberId(Long memberId, List<MissionVisibility> visibilities);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ public List<MissionRecord> findAllByMissionIdAndYearMonth(Long missionId, YearMo
.fetch();
}

@Override
public List<MissionRecord> findAllByCompletedMission(Long missionId) {
return jpaQueryFactory
.selectFrom(missionRecord)
.where(missionIdEq(missionId), uploadStatusCompleteEq())
.orderBy(missionRecord.startedAt.asc())
.fetch();
}

@Override
public boolean isCompletedMissionExistsToday(Long missionId) {
LocalDate now = LocalDate.now();
Expand Down Expand Up @@ -119,4 +128,8 @@ private BooleanExpression monthEq(int month) {
private BooleanExpression dayEq(int day) {
return missionRecord.startedAt.dayOfMonth().eq(day);
}

private BooleanExpression uploadStatusCompleteEq() {
return missionRecord.uploadStatus.eq(ImageUploadStatus.COMPLETE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.depromeet.domain.missionRecord.dto.response;

import com.depromeet.domain.missionRecord.domain.MissionRecord;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;

public record FocusMissionRecordItem(
@Schema(description = "번개 수", defaultValue = "3") long symbolStack,
@Schema(description = "미션 수행 시간 (Minute)", defaultValue = "34") long durationMinute,
@JsonFormat(
shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd HH:mm:ss",
timezone = "Asia/Seoul")
@Schema(
description = "미션 기록 시작 시간",
defaultValue = "2024-01-01 00:34:00",
type = "string")
LocalDateTime startedAt,
@JsonFormat(
shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd HH:mm:ss",
timezone = "Asia/Seoul")
@Schema(
description = "미션 기록 종료 시간",
defaultValue = "2024-01-15 00:34:00",
type = "string")
LocalDateTime finishedAt) {

public static FocusMissionRecordItem from(MissionRecord record) {
return new FocusMissionRecordItem(
record.getDuration().toMinutes() / 10,
record.getDuration().toMinutes(),
record.getStartedAt(),
record.getFinishedAt());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.depromeet.domain.missionRecord.dto.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import java.util.List;

public record MissionStatisticsResponse(
@Schema(description = "수행 시간(시간)", defaultValue = "3") long totalMissionHour,
@Schema(description = "수행 시간(분)", defaultValue = "8") long totalMissionMinute,
@Schema(description = "번개 수", defaultValue = "8") long totalSymbolStack,
@Schema(description = "연속 성공일", defaultValue = "4") long continuousSuccessDay,
@Schema(description = "총 성공일", defaultValue = "9") long totalSuccessDay,
@Schema(description = "달성률", defaultValue = "20.4") double totalMissionAttainRate,
@JsonFormat(
shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd HH:mm:ss",
timezone = "Asia/Seoul")
@Schema(
description = "집중 시작 시각",
defaultValue = "2024-01-01 00:34:00",
type = "string")
LocalDateTime startedAt,
@JsonFormat(
shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd HH:mm:ss",
timezone = "Asia/Seoul")
@Schema(
description = "집중 마친 시각",
defaultValue = "2024-01-15 00:34:00",
type = "string")
LocalDateTime finishedAt,
@Schema(description = "미션 수행 타임 테이블") List<FocusMissionRecordItem> timeTable) {

public static MissionStatisticsResponse of(
long totalMissionHour,
long totalMissionMinute,
long totalSymbolStack,
long continuousSuccessDay,
long totalSuccessDay,
double totalMissionAttainRate,
LocalDateTime startedAt,
LocalDateTime finishedAt,
List<FocusMissionRecordItem> timeTable) {
return new MissionStatisticsResponse(
totalMissionHour,
totalMissionMinute,
totalSymbolStack,
continuousSuccessDay,
totalSuccessDay,
totalMissionAttainRate,
startedAt,
finishedAt,
timeTable);
}
}
Loading

0 comments on commit 6035d38

Please sign in to comment.