Skip to content

Commit dc626af

Browse files
authored
Merge pull request #117 from prgrms-web-devcourse-final-project/feat/mission
close #117
2 parents 291caa6 + 8dbff5a commit dc626af

20 files changed

+937
-220
lines changed
Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package com.back.domain.mission.dto.request;
22

3-
import com.back.domain.mission.enums.TaskStatus;
43
import jakarta.validation.constraints.NotNull;
54
import lombok.*;
65

7-
import java.time.LocalDate;
8-
96
@Getter
107
@Setter
118
@AllArgsConstructor
@@ -15,8 +12,4 @@ public class TaskCompleteRequest {
1512
@NotNull(message = "테스크 ID는 필수입니다.")
1613
private Integer taskId;
1714

18-
@NotNull(message = "완료 상태는 필수입니다.")
19-
private TaskStatus status;
20-
21-
private LocalDate date; // null이면 오늘
2215
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.back.domain.mission.entity;
2+
3+
import com.back.global.jpa.entity.BaseEntity;
4+
import jakarta.persistence.Column;
5+
import jakarta.persistence.Entity;
6+
import jakarta.persistence.Table;
7+
import jakarta.persistence.UniqueConstraint;
8+
import lombok.*;
9+
10+
import java.time.LocalDate;
11+
12+
@Entity
13+
@Table(name = "mission_completion_logs",
14+
uniqueConstraints = @UniqueConstraint(columnNames = {"mission_id", "member_id"}))
15+
@Getter
16+
@Setter
17+
@NoArgsConstructor
18+
@AllArgsConstructor
19+
@Builder
20+
public class MissionCompletionLog extends BaseEntity {
21+
22+
23+
@Column(name = "mission_id", nullable = false)
24+
private Integer missionId;
25+
26+
@Column(name = "member_id", nullable = false)
27+
private Integer memberId;
28+
29+
@Column(nullable = false)
30+
private LocalDate completedDate;
31+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package com.back.domain.mission.enums;
22

33
public enum TaskStatus {
4-
PENDING, COMPLETED, SKIPPED
4+
PENDING, COMPLETED, SKIPPED,CANCELLED
55
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.back.domain.mission.event;
2+
3+
import lombok.Builder;
4+
import lombok.Getter;
5+
6+
import java.time.LocalDate;
7+
8+
@Getter
9+
@Builder
10+
public class MissionCompletedEvent {
11+
private Integer missionId;
12+
private Integer memberId;
13+
private boolean isPartyMission;
14+
private Integer partyId;
15+
private LocalDate completedDate;
16+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.back.domain.mission.event;
2+
3+
import com.back.domain.mission.enums.TaskStatus;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
import java.time.LocalDate;
8+
9+
10+
@Getter
11+
@Builder
12+
public class TaskCompletedEvent {
13+
private Integer memberId;
14+
private Integer taskId;
15+
private Integer missionId;
16+
private Integer subGoalId;
17+
private LocalDate completedDate;
18+
private TaskStatus status;
19+
}

backend/src/main/java/com/back/domain/mission/exception/MissionErrorCode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public enum MissionErrorCode {
2727
TASK_ALREADY_COMPLETED(HttpStatus.BAD_REQUEST, "TASK-400", "이미 완료된 태스크입니다."),
2828
TASK_NOT_IN_DATE_RANGE(HttpStatus.BAD_REQUEST, "TASK-402", "해당 날짜는 이 주차 범위가 아닙니다"),
2929
TASK_WRONG_DAY(HttpStatus.BAD_REQUEST, "TASK-403", "해당 태스크의 요일이 아닙니다"),
30-
30+
PARTY_TASK_CANNOT_CANCEL(HttpStatus.BAD_REQUEST,"TASK-402","파티 미션의 완료한 태스크는 취소할 수 없습니다" ),
3131

3232
// Member
3333
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBER-404", "멤버를 찾을 수 없습니다."),
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.back.domain.mission.listener;
2+
3+
import com.back.domain.mission.event.MissionCompletedEvent;
4+
import groovy.util.logging.Slf4j;
5+
import org.springframework.stereotype.Component;
6+
import org.springframework.transaction.event.TransactionalEventListener;
7+
8+
@lombok.extern.slf4j.Slf4j
9+
@Component
10+
@Slf4j
11+
public class MissionCompletedEventListener {
12+
13+
@TransactionalEventListener
14+
public void handleMissionCompleted(MissionCompletedEvent event) {
15+
log.info("═══════════════════════════════════════");
16+
log.info("🎉 미션 완료 이벤트 발행됨!");
17+
log.info("Mission ID: {}", event.getMissionId());
18+
log.info("Member ID: {}", event.getMemberId());
19+
log.info("완료일: {}", event.getCompletedDate());
20+
log.info("═══════════════════════════════════════");
21+
22+
}
23+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.back.domain.mission.repository;
2+
3+
import com.back.domain.mission.entity.MissionCompletionLog;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
public interface MissionCompletionLogRepository extends JpaRepository<MissionCompletionLog, Integer> {
7+
8+
boolean existsByMissionIdAndMemberId(Integer missionId, Integer memberId);
9+
}

backend/src/main/java/com/back/domain/mission/repository/MissionRepository.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
import org.springframework.data.repository.query.Param;
77
import org.springframework.stereotype.Repository;
88

9+
import java.time.LocalDate;
910
import java.util.List;
1011
import java.util.Optional;
1112

1213
@Repository
1314
public interface MissionRepository extends JpaRepository<Mission, Integer> {
14-
15+
List<Mission> findByEndDate(LocalDate endDate);
1516
List<Mission> findByMemberId(Integer memberId);
1617

1718
List<Mission> findByMemberIdAndIsCompleted(Integer memberId, Boolean isCompleted);

backend/src/main/java/com/back/domain/mission/repository/TaskLogRepository.java

Lines changed: 60 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -13,82 +13,79 @@
1313

1414
@Repository
1515
public interface TaskLogRepository extends JpaRepository<TaskLog, Integer> {
16-
boolean existsByTaskIdAndMemberIdAndDate(Integer taskId, Integer memberId, LocalDate date);
16+
Optional<TaskLog> findByTaskIdAndMemberIdAndDate(
17+
Integer taskId, Integer memberId, LocalDate date);
1718

18-
boolean existsByTaskIdAndStatus(Integer taskId, TaskStatus status);
19+
boolean existsByTaskIdAndMemberIdAndDate(
20+
Integer taskId, Integer memberId, LocalDate date);
21+
22+
boolean existsByTaskIdAndMemberIdAndDateAndStatus(
23+
Integer taskId, Integer memberId, LocalDate date, TaskStatus status);
24+
25+
Optional<TaskLog> findTopByTaskIdAndMemberIdOrderByDateDesc(
26+
Integer taskId, Integer memberId);
1927

20-
Long countByMemberIdAndDateAndStatus(Integer memberId, LocalDate date, TaskStatus status);
28+
// 배치 조회 ( N+1 방지)
29+
@Query("SELECT tl FROM TaskLog tl WHERE tl.task.id IN :taskIds " +
30+
"AND tl.memberId = :memberId AND tl.date = :date")
31+
List<TaskLog> findByTaskIdsAndMemberIdAndDate(
32+
@Param("taskIds") List<Integer> taskIds,
33+
@Param("memberId") Integer memberId,
34+
@Param("date") LocalDate date);
2135

22-
@Query("""
23-
SELECT COUNT(t)
24-
FROM Task t
25-
WHERE t.subGoal.mission.member.id = :memberId
26-
AND t.dayNum = :dayOfWeek
27-
AND :date BETWEEN t.subGoal.startDate AND t.subGoal.endDate
28-
""")
36+
@Query("SELECT tl FROM TaskLog tl WHERE tl.task.id IN :taskIds " +
37+
"AND tl.memberId = :memberId " +
38+
"AND tl.date = (SELECT MAX(tl2.date) FROM TaskLog tl2 " +
39+
"WHERE tl2.task.id = tl.task.id AND tl2.memberId = :memberId)")
40+
List<TaskLog> findLastCompletedByTaskIds(
41+
@Param("taskIds") List<Integer> taskIds,
42+
@Param("memberId") Integer memberId);
43+
44+
// N + 1 방지
45+
@Query("SELECT COUNT(DISTINCT tl.memberId) FROM TaskLog tl " +
46+
"WHERE tl.task.id = :taskId AND tl.date = :date " +
47+
"AND tl.status = :status AND tl.memberId IN :memberIds")
48+
Long countCompletedMembers(
49+
@Param("taskId") Integer taskId,
50+
@Param("date") LocalDate date,
51+
@Param("status") TaskStatus status,
52+
@Param("memberIds") List<Integer> memberIds);
53+
54+
// 일일 진행률 곗ㄴ
55+
@Query("SELECT COUNT(DISTINCT t.id) FROM Task t " +
56+
"JOIN t.subGoal sg " +
57+
"JOIN sg.mission m " +
58+
"WHERE m.member.id = :memberId " +
59+
"AND t.dayNum = :dayNum " +
60+
"AND :date BETWEEN sg.startDate AND sg.endDate " +
61+
"AND m.isCompleted = false")
2962
Long countDailyTasks(
3063
@Param("memberId") Integer memberId,
3164
@Param("date") LocalDate date,
32-
@Param("dayOfWeek") int dayOfWeek
33-
);
65+
@Param("dayNum") Integer dayNum);
66+
67+
Long countByMemberIdAndDateAndStatus(
68+
Integer memberId, LocalDate date, TaskStatus status);
3469

35-
@Query("""
36-
SELECT COUNT(tl)
37-
FROM TaskLog tl
38-
WHERE tl.task.subGoal.mission.id = :missionId
39-
AND tl.memberId = :memberId
40-
AND tl.status = :status
41-
""")
70+
// 완료된 태스크 수 조회 (미션 진행률용)
71+
@Query("SELECT COUNT(tl) FROM TaskLog tl " +
72+
"WHERE tl.task.subGoal.mission.id = :missionId " +
73+
"AND tl.memberId = :memberId " +
74+
"AND tl.status = :status")
4275
Long countCompletedTasksByMissionAndMember(
4376
@Param("missionId") Integer missionId,
4477
@Param("memberId") Integer memberId,
45-
@Param("status") TaskStatus status
46-
);
78+
@Param("status") TaskStatus status);
4779

48-
@Query("""
49-
SELECT COUNT(tl)
50-
FROM TaskLog tl
51-
WHERE tl.task.subGoal.id = :subGoalId
52-
AND tl.memberId = :memberId
53-
AND tl.status = :status
54-
""")
80+
// 주차별 완료된 태스크 수
81+
@Query("SELECT COUNT(tl) FROM TaskLog tl " +
82+
"WHERE tl.task.subGoal.id = :subGoalId " +
83+
"AND tl.memberId = :memberId " +
84+
"AND tl.status = :status")
5585
Long countCompletedTasksBySubGoalAndMember(
5686
@Param("subGoalId") Integer subGoalId,
5787
@Param("memberId") Integer memberId,
58-
@Param("status") TaskStatus status
59-
);
60-
61-
Optional<TaskLog> findTopByTaskIdAndMemberIdOrderByDateDesc(Integer taskId, Integer memberId);
62-
63-
Optional<TaskLog> findByTaskIdAndMemberIdAndDate(Integer taskId, Integer memberId, LocalDate date);
64-
65-
@Query("SELECT tl FROM TaskLog tl " +
66-
"WHERE tl.task.id IN :taskIds " +
67-
"AND tl.memberId = :memberId " +
68-
"AND tl.date = :date")
69-
List<TaskLog> findByTaskIdsAndMemberIdAndDate(
70-
@Param("taskIds") List<Integer> taskIds,
71-
@Param("memberId") Integer memberId,
72-
@Param("date") LocalDate date
73-
);
88+
@Param("status") TaskStatus status);
7489

75-
@Query("SELECT tl FROM TaskLog tl " +
76-
"WHERE tl.task.id IN :taskIds " +
77-
"AND tl.memberId = :memberId " +
78-
"AND tl.id IN (" +
79-
" SELECT MAX(tl2.id) FROM TaskLog tl2 " +
80-
" WHERE tl2.task.id = tl.task.id " +
81-
" AND tl2.memberId = :memberId" +
82-
")")
83-
List<TaskLog> findLastCompletedByTaskIds(
84-
@Param("taskIds") List<Integer> taskIds,
85-
@Param("memberId") Integer memberId
86-
);
87-
88-
boolean existsByTaskIdAndMemberIdAndDateAndStatus(
89-
Integer taskId,
90-
Integer memberId,
91-
LocalDate date,
92-
TaskStatus status
93-
);
90+
boolean existsByTaskIdAndStatus(Integer taskId, TaskStatus status);
9491
}

0 commit comments

Comments
 (0)