Skip to content

Commit 72607b7

Browse files
authored
[BE] SISC1-223 [FIX] 출석 세션 시간 변수 수정 (#140)
* refactor: 출석 세션 관련 수정 * fix: roundDto 변수명 수정 * test: test코드 수정
1 parent 5e4416b commit 72607b7

12 files changed

Lines changed: 131 additions & 155 deletions

File tree

backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceRoundController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ public ResponseEntity<AttendanceRoundResponse> createRound(
5555
@Valid @RequestBody AttendanceRoundRequest request) {
5656
log.info("📋 라운드 생성 요청 도착:");
5757
log.info(" - sessionId: {}", sessionId);
58-
log.info(" - roundDate: {} (타입: {})", request.getRoundDate(), request.getRoundDate() != null ? request.getRoundDate().getClass().getSimpleName() : "null");
58+
log.info(" - date: {} (타입: {})", request.getDate(), request.getDate() != null ? request.getDate().getClass().getSimpleName() : "null");
5959
log.info(" - startTime: {} (타입: {})", request.getStartTime(), request.getStartTime() != null ? request.getStartTime().getClass().getSimpleName() : "null");
60-
log.info(" - allowedMinutes: {}", request.getAllowedMinutes());
60+
log.info(" - availableMinutes: {}", request.getAvailableMinutes());
6161

6262
if (request.getStartTime() != null) {
6363
log.info(" - startTime 상세: 시간={}, 분={}, 초={}",

backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceSessionController.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ public class AttendanceSessionController {
3434
* 출석 세션 생성 (관리자용)
3535
* - 6자리 랜덤 코드 자동 생성
3636
* - GPS 위치 및 반경 설정
37-
* - 시간 윈도우 설정
37+
* - 기본 시작 시간 및 출석 인정 시간 설정
3838
*/
3939
@Operation(
4040
summary = "출석 세션 생성",
4141
description = "새로운 출석 세션을 생성합니다. (관리자 전용) " +
42-
"6자리 랜덤 코드가 자동 생성되며, GPS 위치 정보, 시간 윈도우, " +
43-
"보상 포인트 등을 설정할 수 있습니다."
42+
"6자리 랜덤 코드가 자동 생성되며, GPS 위치 정보, 기본 시작 시간, " +
43+
"출석 인정 시간, 보상 포인트 등을 설정할 수 있습니다."
4444
)
4545
@PostMapping
4646
@PreAuthorize("hasRole('PRESIDENT') or hasRole('VICE_PRESIDENT')")
@@ -132,13 +132,13 @@ public ResponseEntity<List<AttendanceSessionResponse>> getActiveSessions() {
132132

133133
/**
134134
* 세션 정보 수정 (관리자용)
135-
* - 제목, 시간, 위치, 반경 등 수정 가능
135+
* - 제목, 기본 시간, 위치, 반경 등 수정 가능
136136
* - 코드는 변경 불가
137137
*/
138138
@Operation(
139139
summary = "세션 정보 수정",
140140
description = "세션의 기본 정보를 수정합니다. (관리자 전용) " +
141-
"제목, 태그, 시간, GPS 위치, 반경, 포인트 등을 수정할 수 있으며, " +
141+
"제목, 기본 시작 시간, 출석 인정 시간, GPS 위치, 반경, 포인트 등을 수정할 수 있으며, " +
142142
"6자리 코드는 변경할 수 없습니다."
143143
)
144144
@PutMapping("/{sessionId}")

backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundRequest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class AttendanceRoundRequest {
3939
type = "string",
4040
format = "date"
4141
)
42-
private LocalDate roundDate;
42+
private LocalDate date;
4343

4444
@NotNull(message = "시작 시간은 필수입니다")
4545
@Schema(
@@ -60,5 +60,5 @@ public class AttendanceRoundRequest {
6060
minimum = "1",
6161
maximum = "120"
6262
)
63-
private Integer allowedMinutes;
63+
private Integer availableMinutes;
6464
}

backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundResponse.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public class AttendanceRoundResponse {
3535
format = "date"
3636
)
3737
@JsonFormat(pattern = "yyyy-MM-dd")
38-
private LocalDate roundDate;
38+
private LocalDate date;
3939

4040
@Schema(
4141
description = "라운드 출석 시작 시간",
@@ -69,7 +69,7 @@ public static AttendanceRoundResponse fromEntity(AttendanceRound round) {
6969

7070
return AttendanceRoundResponse.builder()
7171
.roundId(round.getRoundId())
72-
.roundDate(round.getRoundDate())
72+
.date(round.getRoundDate())
7373
.startTime(round.getStartTime())
7474
.availableMinutes(round.getAllowedMinutes())
7575
.status(statusString)

backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionRequest.java

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package org.sejongisc.backend.attendance.dto;
22

3+
import com.fasterxml.jackson.annotation.JsonFormat;
34
import io.swagger.v3.oas.annotations.media.Schema;
45
import jakarta.validation.constraints.*;
56
import lombok.*;
67

7-
import java.time.LocalDateTime;
8+
import java.time.LocalTime;
89

910
@Getter
1011
@Builder
@@ -19,33 +20,34 @@ public class AttendanceSessionRequest {
1920

2021
@Schema(
2122
description = "세션의 제목/이름",
22-
example = "2024년 10월 동아리 정기 모임",
23+
example = "금융 IT팀 세션",
2324
maxLength = 100
2425
)
2526
@NotBlank(message = "제목은 필수입니다")
2627
@Size(max = 100, message = "제목은 100자 이하여야 합니다")
2728
private String title;
2829

2930
@Schema(
30-
description = "세션 시작 시간 (ISO 8601 형식). 현재 시간 이후여야 합니다.",
31-
example = "2024-11-15T14:00:00",
31+
description = "세션의 기본 시작 시간 (HH:MM:SS 형식). " +
32+
"모든 라운드는 이 시간을 기본값으로 사용합니다.",
33+
example = "18:30:00",
3234
type = "string",
33-
format = "date-time"
35+
format = "time"
3436
)
3537
@NotNull(message = "시작 시간은 필수입니다")
36-
@Future(message = "시작 시간은 현재 시간 이후여야 합니다")
37-
private LocalDateTime startsAt;
38+
@JsonFormat(pattern = "HH:mm:ss")
39+
private LocalTime defaultStartTime;
3840

3941
@Schema(
40-
description = "출석 체크인이 가능한 시간 윈도우 (초 단위). " +
41-
"범위: 300초(5분) ~ 14400초(4시간)",
42-
example = "1800",
43-
minimum = "300",
44-
maximum = "14400"
42+
description = "출석 인정 시간 (분 단위). " +
43+
"범위: 1분 ~ 240분(4시간)",
44+
example = "30",
45+
minimum = "1",
46+
maximum = "240"
4547
)
46-
@Min(value = 300, message = "최소 5분 이상이어야 합니다")
47-
@Max(value = 14400, message = "최대 4시간 설정 가능합니다")
48-
private Integer windowSeconds;
48+
@Min(value = 1, message = "최소 1분 이상이어야 합니다")
49+
@Max(value = 240, message = "최대 240분(4시간) 설정 가능합니다")
50+
private Integer defaultAvailableMinutes;
4951

5052
@Schema(
5153
description = "출석 완료 시 지급할 포인트",

backend/src/main/java/org/sejongisc/backend/attendance/entity/Attendance.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,25 @@ public class Attendance extends BasePostgresEntity {
6565

6666
/**
6767
* 지각 여부 판단
68+
* - 라운드 기반: 라운드 시작 시간 이후이면 지각
69+
* - 세션 기반: 라운드 없이 진행되는 경우는 확인 불가
6870
*/
6971
public boolean isLate() {
70-
if (checkedAt == null || attendanceSession.getStartsAt() == null) {
72+
if (checkedAt == null) {
7173
return false;
7274
}
73-
return checkedAt.isAfter(attendanceSession.getStartsAt());
75+
76+
// 라운드 기반 체크인인 경우
77+
if (attendanceRound != null) {
78+
LocalDateTime roundStartTime = LocalDateTime.of(
79+
attendanceRound.getRoundDate(),
80+
attendanceRound.getStartTime()
81+
);
82+
return checkedAt.isAfter(roundStartTime);
83+
}
84+
85+
// 라운드 없는 경우 지각 판단 불가
86+
return false;
7487
}
7588

7689
/**
Lines changed: 16 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package org.sejongisc.backend.attendance.entity;
22

3+
import com.fasterxml.jackson.annotation.JsonFormat;
34
import com.fasterxml.jackson.annotation.JsonManagedReference;
45
import jakarta.persistence.*;
56
import lombok.*;
67
import org.sejongisc.backend.common.entity.postgres.BasePostgresEntity;
78

8-
import java.time.LocalDateTime;
9+
import java.time.LocalTime;
910
import java.util.ArrayList;
1011
import java.util.List;
1112
import java.util.UUID;
@@ -24,13 +25,14 @@ public class AttendanceSession extends BasePostgresEntity {
2425
private UUID attendanceSessionId;
2526

2627
@Column(nullable = false)
27-
private String title; // "세투연 9/17"
28+
private String title; // "금융 IT팀 세션"
2829

29-
@Column(name = "starts_at", nullable = false)
30-
private LocalDateTime startsAt; // 세션 시작 시간
30+
@Column(name = "default_start_time", nullable = false)
31+
@JsonFormat(pattern = "HH:mm:ss")
32+
private LocalTime defaultStartTime; // 기본 시작 시간 (시간만) - 18:30:00
3133

32-
@Column(name = "window_seconds")
33-
private Integer windowSeconds; // 체크인 가능 시간() - 1800 = 30분
34+
@Column(name = "default_available_minutes")
35+
private Integer defaultAvailableMinutes; // 출석 인정 시간() - 30분
3436

3537
@Column(unique = true, length = 6)
3638
private String code; // 6자리 출석 코드 "942715"
@@ -55,46 +57,19 @@ public class AttendanceSession extends BasePostgresEntity {
5557
private List<Attendance> attendances = new ArrayList<>();
5658

5759
/**
58-
* 현재 세션 상태 계산
60+
* 세션 상태를 반환합니다.
61+
* - UPCOMING: 활성화되지 않은 상태
62+
* - OPEN: 관리자가 활성화한 상태 (체크인 가능)
63+
* - CLOSED: 관리자가 종료한 상태 (체크인 불가)
5964
*/
60-
public SessionStatus calculateCurrentStatus() {
61-
LocalDateTime now = LocalDateTime.now();
62-
63-
if (now.isBefore(startsAt)) {
64-
return SessionStatus.UPCOMING;
65-
} else if (now.isAfter(getEndsAt())) {
66-
return SessionStatus.CLOSED;
67-
} else {
68-
return SessionStatus.OPEN;
69-
}
65+
public SessionStatus getCurrentStatus() {
66+
return this.status;
7067
}
7168

7269
/**
73-
* 세션 종료 시간 계산
70+
* 체크인이 가능한 상태인지 확인합니다.
7471
*/
7572
public boolean isCheckInAvailable() {
76-
LocalDateTime now = LocalDateTime.now();
77-
return now.isAfter(startsAt) && now.isBefore(getEndsAt());
78-
}
79-
80-
/**
81-
* 세션 종료 시간 계산
82-
*/
83-
public LocalDateTime getEndsAt() {
84-
return startsAt.plusSeconds(windowSeconds != null ? windowSeconds : 1800);
85-
}
86-
87-
/**
88-
* 남은 시간 계산 (초단위)
89-
*/
90-
public long getRemainingSeconds() {
91-
LocalDateTime now = LocalDateTime.now();
92-
LocalDateTime endsAt = getEndsAt();
93-
94-
if (now.isAfter(endsAt)) {
95-
return 0;
96-
}
97-
98-
return java.time.Duration.between(now, endsAt).getSeconds();
73+
return SessionStatus.OPEN.equals(this.status);
9974
}
10075
}

backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceRoundService.java

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,28 +35,25 @@ public class AttendanceRoundService {
3535
* 라운드 생성
3636
*/
3737
public AttendanceRoundResponse createRound(UUID sessionId, AttendanceRoundRequest request) {
38-
log.info("📋 라운드 생성 요청: sessionId={}, roundDate={}, startTime={}, allowedMinutes={}",
39-
sessionId, request.getRoundDate(), request.getStartTime(), request.getAllowedMinutes());
38+
log.info("📋 라운드 생성 요청: sessionId={}, date={}, startTime={}, availableMinutes={}",
39+
sessionId, request.getDate(), request.getStartTime(), request.getAvailableMinutes());
4040

4141
AttendanceSession session = attendanceSessionRepository.findById(sessionId)
4242
.orElseThrow(() -> new IllegalArgumentException("세션을 찾을 수 없습니다: " + sessionId));
4343

4444
try {
45-
// 클라이언트가 보낸 날짜 대신 서버의 현재 날짜를 사용하여 시간대 차이 방지
46-
LocalDate roundDate = request.getRoundDate();
47-
if (roundDate == null) {
48-
roundDate = LocalDate.now();
49-
}
45+
// 클라이언트가 제공한 날짜를 사용하고, 없으면 서버의 현재 날짜를 기본값으로 사용
46+
LocalDate roundDate = request.getDate() != null ? request.getDate() : LocalDate.now();
5047
LocalTime requestStartTime = request.getStartTime();
5148

52-
log.info("📅 시간대 정보: 클라이언트 roundDate={}, 서버 today={}, 요청 startTime={}",
53-
request.getRoundDate(), roundDate, requestStartTime);
49+
log.info("📅 시간대 정보: 클라이언트 date={}, 사용할 roundDate={}, 요청 startTime={}",
50+
request.getDate(), roundDate, requestStartTime);
5451

5552
AttendanceRound round = AttendanceRound.builder()
5653
.attendanceSession(session)
57-
.roundDate(roundDate)
54+
.roundDate(roundDate) // 클라이언트 날짜를 우선 사용
5855
.startTime(requestStartTime)
59-
.allowedMinutes(request.getAllowedMinutes() != null ? request.getAllowedMinutes() : 30)
56+
.allowedMinutes(request.getAvailableMinutes() != null ? request.getAvailableMinutes() : 30)
6057
.roundStatus(RoundStatus.UPCOMING)
6158
.build();
6259

@@ -115,9 +112,9 @@ public AttendanceRoundResponse updateRound(UUID roundId, AttendanceRoundRequest
115112
.orElseThrow(() -> new IllegalArgumentException("라운드를 찾을 수 없습니다: " + roundId));
116113

117114
round.updateRoundInfo(
118-
request.getRoundDate(),
115+
request.getDate(),
119116
request.getStartTime(),
120-
request.getAllowedMinutes()
117+
request.getAvailableMinutes()
121118
);
122119

123120
AttendanceRound updated = attendanceRoundRepository.save(round);

backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceService.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,24 +75,27 @@ public AttendanceResponse checkIn(UUID sessionId, AttendanceRequest request, UUI
7575
}
7676

7777

78+
// 세션의 활성 라운드를 찾음
7879
LocalDateTime now = LocalDateTime.now();
79-
if (now.isBefore(session.getStartsAt())) {
80-
throw new IllegalStateException("아직 출석 시간이 아닙니다");
81-
}
82-
83-
LocalDateTime endTime = session.getEndsAt();
84-
if (now.isAfter(endTime)) {
85-
throw new IllegalStateException("출석 시간이 종료되었습니다");
86-
}
80+
AttendanceRound activeRound = session.getRounds().stream()
81+
.filter(round -> {
82+
LocalDateTime roundStart = LocalDateTime.of(round.getRoundDate(), round.getStartTime());
83+
LocalDateTime roundEnd = roundStart.plusMinutes(round.getAllowedMinutes());
84+
return !now.isBefore(roundStart) && now.isBefore(roundEnd);
85+
})
86+
.findFirst()
87+
.orElseThrow(() -> new IllegalStateException("현재 진행 중인 라운드가 없습니다"));
8788

8889
// 시작 후 5분 이내는 정상 출석, 이후는 지각
89-
LocalDateTime lateThreshold = session.getStartsAt().plusMinutes(5);
90+
LocalDateTime roundStart = LocalDateTime.of(activeRound.getRoundDate(), activeRound.getStartTime());
91+
LocalDateTime lateThreshold = roundStart.plusMinutes(5);
9092
AttendanceStatus status = now.isAfter(lateThreshold) ?
9193
AttendanceStatus.LATE : AttendanceStatus.PRESENT;
9294

9395
Attendance attendance = Attendance.builder()
9496
.user(user)
9597
.attendanceSession(session)
98+
.attendanceRound(activeRound)
9699
.attendanceStatus(status)
97100
.checkedAt(now)
98101
.awardedPoints(session.getRewardPoints())
@@ -101,6 +104,7 @@ public AttendanceResponse checkIn(UUID sessionId, AttendanceRequest request, UUI
101104
.deviceInfo(request.getDeviceInfo())
102105
.build();
103106

107+
104108
attendance = attendanceRepository.save(attendance);
105109

106110
log.info("출석 체크인 완료: 사용자={}, 상태={}", user.getName(), status);

0 commit comments

Comments
 (0)