diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceRoundController.java b/backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceRoundController.java index 58efd94c..2fcf5959 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceRoundController.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceRoundController.java @@ -55,9 +55,9 @@ public ResponseEntity createRound( @Valid @RequestBody AttendanceRoundRequest request) { log.info("πŸ“‹ λΌμš΄λ“œ 생성 μš”μ²­ 도착:"); log.info(" - sessionId: {}", sessionId); - log.info(" - roundDate: {} (νƒ€μž…: {})", request.getRoundDate(), request.getRoundDate() != null ? request.getRoundDate().getClass().getSimpleName() : "null"); + log.info(" - date: {} (νƒ€μž…: {})", request.getDate(), request.getDate() != null ? request.getDate().getClass().getSimpleName() : "null"); log.info(" - startTime: {} (νƒ€μž…: {})", request.getStartTime(), request.getStartTime() != null ? request.getStartTime().getClass().getSimpleName() : "null"); - log.info(" - allowedMinutes: {}", request.getAllowedMinutes()); + log.info(" - availableMinutes: {}", request.getAvailableMinutes()); if (request.getStartTime() != null) { log.info(" - startTime 상세: μ‹œκ°„={}, λΆ„={}, 초={}", diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceSessionController.java b/backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceSessionController.java index fa45b245..d3af7fd3 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceSessionController.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceSessionController.java @@ -34,13 +34,13 @@ public class AttendanceSessionController { * μΆœμ„ μ„Έμ…˜ 생성 (κ΄€λ¦¬μžμš©) * - 6자리 랜덀 μ½”λ“œ μžλ™ 생성 * - GPS μœ„μΉ˜ 및 반경 μ„€μ • - * - μ‹œκ°„ μœˆλ„μš° μ„€μ • + * - κΈ°λ³Έ μ‹œμž‘ μ‹œκ°„ 및 μΆœμ„ 인정 μ‹œκ°„ μ„€μ • */ @Operation( summary = "μΆœμ„ μ„Έμ…˜ 생성", description = "μƒˆλ‘œμš΄ μΆœμ„ μ„Έμ…˜μ„ μƒμ„±ν•©λ‹ˆλ‹€. (κ΄€λ¦¬μž μ „μš©) " + - "6자리 랜덀 μ½”λ“œκ°€ μžλ™ μƒμ„±λ˜λ©°, GPS μœ„μΉ˜ 정보, μ‹œκ°„ μœˆλ„μš°, " + - "보상 포인트 등을 μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€." + "6자리 랜덀 μ½”λ“œκ°€ μžλ™ μƒμ„±λ˜λ©°, GPS μœ„μΉ˜ 정보, κΈ°λ³Έ μ‹œμž‘ μ‹œκ°„, " + + "μΆœμ„ 인정 μ‹œκ°„, 보상 포인트 등을 μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€." ) @PostMapping @PreAuthorize("hasRole('PRESIDENT') or hasRole('VICE_PRESIDENT')") @@ -113,13 +113,13 @@ public ResponseEntity> getPublicSessions() { /** * ν˜„μž¬ ν™œμ„± μ„Έμ…˜ λͺ©λ‘ 쑰회 - * - 체크인 κ°€λŠ₯ν•œ μ„Έμ…˜λ“€λ§Œ 쑰회 - * - μ‹œμž‘μ‹œκ°„ ~ μ’…λ£Œ μ‹œκ°„ λ²”μœ„ λ‚΄ + * - μƒνƒœκ°€ OPEN인 μ„Έμ…˜λ“€λ§Œ 쑰회 + * - μ‹€μ œ 체크인 κ°€λŠ₯ μ—¬λΆ€λŠ” λΌμš΄λ“œμ˜ μƒνƒœλ‘œ νŒλ‹¨ */ @Operation( summary = "ν™œμ„± μ„Έμ…˜ λͺ©λ‘ 쑰회", - description = "ν˜„μž¬ 체크인이 κ°€λŠ₯ν•œ ν™œμ„± μ„Έμ…˜λ“€μ„ μ‘°νšŒν•©λ‹ˆλ‹€. " + - "μ„Έμ…˜ μ‹œμž‘ μ‹œκ°„λΆ€ν„° μ‹œκ°„ μœˆλ„μš° μ’…λ£ŒκΉŒμ§€ λ²”μœ„ 내인 μ„Έμ…˜λ“€λ§Œ μ‘°νšŒλ©λ‹ˆλ‹€." + description = "ν˜„μž¬ ν™œμ„±ν™”λœ(OPEN μƒνƒœ) μ„Έμ…˜λ“€μ„ μ‘°νšŒν•©λ‹ˆλ‹€. " + + "μ‹€μ œ 체크인 κ°€λŠ₯ μ—¬λΆ€λŠ” μ„Έμ…˜ λ‚΄ λΌμš΄λ“œμ˜ μ‹œκ°„ μƒνƒœλ‘œ νŒλ‹¨λ©λ‹ˆλ‹€." ) @GetMapping("/active") public ResponseEntity> getActiveSessions() { @@ -132,13 +132,13 @@ public ResponseEntity> getActiveSessions() { /** * μ„Έμ…˜ 정보 μˆ˜μ • (κ΄€λ¦¬μžμš©) - * - 제λͺ©, μ‹œκ°„, μœ„μΉ˜, 반경 λ“± μˆ˜μ • κ°€λŠ₯ + * - 제λͺ©, κΈ°λ³Έ μ‹œκ°„, μœ„μΉ˜, 반경 λ“± μˆ˜μ • κ°€λŠ₯ * - μ½”λ“œλŠ” λ³€κ²½ λΆˆκ°€ */ @Operation( summary = "μ„Έμ…˜ 정보 μˆ˜μ •", description = "μ„Έμ…˜μ˜ κΈ°λ³Έ 정보λ₯Ό μˆ˜μ •ν•©λ‹ˆλ‹€. (κ΄€λ¦¬μž μ „μš©) " + - "제λͺ©, νƒœκ·Έ, μ‹œκ°„, GPS μœ„μΉ˜, 반경, 포인트 등을 μˆ˜μ •ν•  수 있으며, " + + "제λͺ©, κΈ°λ³Έ μ‹œμž‘ μ‹œκ°„, μΆœμ„ 인정 μ‹œκ°„, GPS μœ„μΉ˜, 반경, 포인트 등을 μˆ˜μ •ν•  수 있으며, " + "6자리 μ½”λ“œλŠ” λ³€κ²½ν•  수 μ—†μŠ΅λ‹ˆλ‹€." ) @PutMapping("/{sessionId}") diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundRequest.java b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundRequest.java index 8ea59e6f..73ec590d 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundRequest.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundRequest.java @@ -39,7 +39,7 @@ public class AttendanceRoundRequest { type = "string", format = "date" ) - private LocalDate roundDate; + private LocalDate date; @NotNull(message = "μ‹œμž‘ μ‹œκ°„μ€ ν•„μˆ˜μž…λ‹ˆλ‹€") @Schema( @@ -60,5 +60,5 @@ public class AttendanceRoundRequest { minimum = "1", maximum = "120" ) - private Integer allowedMinutes; + private Integer availableMinutes; } diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundResponse.java b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundResponse.java index 3ba856f4..8aaf9124 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundResponse.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundResponse.java @@ -35,7 +35,7 @@ public class AttendanceRoundResponse { format = "date" ) @JsonFormat(pattern = "yyyy-MM-dd") - private LocalDate roundDate; + private LocalDate date; @Schema( description = "λΌμš΄λ“œ μΆœμ„ μ‹œμž‘ μ‹œκ°„", @@ -69,7 +69,7 @@ public static AttendanceRoundResponse fromEntity(AttendanceRound round) { return AttendanceRoundResponse.builder() .roundId(round.getRoundId()) - .roundDate(round.getRoundDate()) + .date(round.getRoundDate()) .startTime(round.getStartTime()) .availableMinutes(round.getAllowedMinutes()) .status(statusString) diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionRequest.java b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionRequest.java index d3a01520..a63fee2a 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionRequest.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionRequest.java @@ -1,10 +1,11 @@ package org.sejongisc.backend.attendance.dto; +import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.*; import lombok.*; -import java.time.LocalDateTime; +import java.time.LocalTime; @Getter @Builder @@ -19,7 +20,7 @@ public class AttendanceSessionRequest { @Schema( description = "μ„Έμ…˜μ˜ 제λͺ©/이름", - example = "2024λ…„ 10μ›” 동아리 μ •κΈ° λͺ¨μž„", + example = "금육 ITνŒ€ μ„Έμ…˜", maxLength = 100 ) @NotBlank(message = "제λͺ©μ€ ν•„μˆ˜μž…λ‹ˆλ‹€") @@ -27,25 +28,26 @@ public class AttendanceSessionRequest { private String title; @Schema( - description = "μ„Έμ…˜ μ‹œμž‘ μ‹œκ°„ (ISO 8601 ν˜•μ‹). ν˜„μž¬ μ‹œκ°„ 이후여야 ν•©λ‹ˆλ‹€.", - example = "2024-11-15T14:00:00", + description = "μ„Έμ…˜μ˜ κΈ°λ³Έ μ‹œμž‘ μ‹œκ°„ (HH:MM:SS ν˜•μ‹). " + + "λͺ¨λ“  λΌμš΄λ“œλŠ” 이 μ‹œκ°„μ„ κΈ°λ³Έκ°’μœΌλ‘œ μ‚¬μš©ν•©λ‹ˆλ‹€.", + example = "18:30:00", type = "string", - format = "date-time" + format = "time" ) @NotNull(message = "μ‹œμž‘ μ‹œκ°„μ€ ν•„μˆ˜μž…λ‹ˆλ‹€") - @Future(message = "μ‹œμž‘ μ‹œκ°„μ€ ν˜„μž¬ μ‹œκ°„ 이후여야 ν•©λ‹ˆλ‹€") - private LocalDateTime startsAt; + @JsonFormat(pattern = "HH:mm:ss") + private LocalTime defaultStartTime; @Schema( - description = "μΆœμ„ 체크인이 κ°€λŠ₯ν•œ μ‹œκ°„ μœˆλ„μš° (초 λ‹¨μœ„). " + - "λ²”μœ„: 300초(5λΆ„) ~ 14400초(4μ‹œκ°„)", - example = "1800", - minimum = "300", - maximum = "14400" + description = "μΆœμ„ 인정 μ‹œκ°„ (λΆ„ λ‹¨μœ„). " + + "λ²”μœ„: 1λΆ„ ~ 240λΆ„(4μ‹œκ°„)", + example = "30", + minimum = "1", + maximum = "240" ) - @Min(value = 300, message = "μ΅œμ†Œ 5λΆ„ 이상이어야 ν•©λ‹ˆλ‹€") - @Max(value = 14400, message = "μ΅œλŒ€ 4μ‹œκ°„ μ„€μ • κ°€λŠ₯ν•©λ‹ˆλ‹€") - private Integer windowSeconds; + @Min(value = 1, message = "μ΅œμ†Œ 1λΆ„ 이상이어야 ν•©λ‹ˆλ‹€") + @Max(value = 240, message = "μ΅œλŒ€ 240λΆ„(4μ‹œκ°„) μ„€μ • κ°€λŠ₯ν•©λ‹ˆλ‹€") + private Integer defaultAvailableMinutes; @Schema( description = "μΆœμ„ μ™„λ£Œ μ‹œ μ§€κΈ‰ν•  포인트", diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/entity/Attendance.java b/backend/src/main/java/org/sejongisc/backend/attendance/entity/Attendance.java index e5459e01..3bd82bef 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/entity/Attendance.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/entity/Attendance.java @@ -64,15 +64,21 @@ public class Attendance extends BasePostgresEntity { // 지각 μ—¬λΆ€ 계산 / μƒνƒœ μ—…λ°μ΄νŠΈ /** + /** * 지각 μ—¬λΆ€ νŒλ‹¨ + * - λΌμš΄λ“œμ˜ μ‹œμž‘ μ‹œκ°„ 이후에 μ²΄ν¬μΈν–ˆμœΌλ©΄ 지각 */ public boolean isLate() { - if (checkedAt == null || attendanceSession.getStartsAt() == null) { + if (checkedAt == null || attendanceRound == null) { return false; } - return checkedAt.isAfter(attendanceSession.getStartsAt()); + // λΌμš΄λ“œμ˜ μ‹œμž‘ μ‹œκ°„(LocalTime)κ³Ό 체크인 μ‹œκ°„(LocalDateTime)을 비ꡐ + LocalDateTime roundStartDateTime = attendanceRound.getRoundDate() + .atTime(attendanceRound.getStartTime()); + return checkedAt.isAfter(roundStartDateTime); } + /** * μƒνƒœ μ—…λ°μ΄νŠΈ (κ΄€λ¦¬μžμš©) */ diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/entity/AttendanceSession.java b/backend/src/main/java/org/sejongisc/backend/attendance/entity/AttendanceSession.java index 83452dd6..41bddd7b 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/entity/AttendanceSession.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/entity/AttendanceSession.java @@ -1,11 +1,12 @@ package org.sejongisc.backend.attendance.entity; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonManagedReference; import jakarta.persistence.*; import lombok.*; import org.sejongisc.backend.common.entity.postgres.BasePostgresEntity; -import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -24,13 +25,14 @@ public class AttendanceSession extends BasePostgresEntity { private UUID attendanceSessionId; @Column(nullable = false) - private String title; // "μ„Ένˆ¬μ—° 9/17" + private String title; // "금육 ITνŒ€ μ„Έμ…˜" - @Column(name = "starts_at", nullable = false) - private LocalDateTime startsAt; // μ„Έμ…˜ μ‹œμž‘ μ‹œκ°„ + @Column(name = "default_start_time", nullable = false) + @JsonFormat(pattern = "HH:mm:ss") + private LocalTime defaultStartTime; // κΈ°λ³Έ μ‹œμž‘ μ‹œκ°„ (μ‹œκ°„λ§Œ) - 18:30:00 - @Column(name = "window_seconds") - private Integer windowSeconds; // 체크인 κ°€λŠ₯ μ‹œκ°„(초) - 1800 = 30λΆ„ + @Column(name = "default_available_minutes") + private Integer defaultAvailableMinutes; // μΆœμ„ 인정 μ‹œκ°„(λΆ„) - 30λΆ„ @Column(unique = true, length = 6) private String code; // 6자리 μΆœμ„ μ½”λ“œ "942715" @@ -55,46 +57,12 @@ public class AttendanceSession extends BasePostgresEntity { private List attendances = new ArrayList<>(); /** - * ν˜„μž¬ μ„Έμ…˜ μƒνƒœ 계산 + * μ„Έμ…˜μ˜ ν˜„μž¬ μƒνƒœ λ°˜ν™˜ + * - μ„Έμ…˜ μƒνƒœλŠ” μˆ˜λ™μœΌλ‘œ 관리됨 (UPCOMING, OPEN, CLOSED) + * - λΌμš΄λ“œμ˜ μƒνƒœλŠ” λΌμš΄λ“œ μ—”ν‹°ν‹°μ—μ„œ μ‹œκ°„ 기반으둜 계산됨 */ - public SessionStatus calculateCurrentStatus() { - LocalDateTime now = LocalDateTime.now(); - - if (now.isBefore(startsAt)) { - return SessionStatus.UPCOMING; - } else if (now.isAfter(getEndsAt())) { - return SessionStatus.CLOSED; - } else { - return SessionStatus.OPEN; - } - } - - /** - * μ„Έμ…˜ μ’…λ£Œ μ‹œκ°„ 계산 - */ - public boolean isCheckInAvailable() { - LocalDateTime now = LocalDateTime.now(); - return now.isAfter(startsAt) && now.isBefore(getEndsAt()); - } - - /** - * μ„Έμ…˜ μ’…λ£Œ μ‹œκ°„ 계산 - */ - public LocalDateTime getEndsAt() { - return startsAt.plusSeconds(windowSeconds != null ? windowSeconds : 1800); + public SessionStatus getStatus() { + return this.status; } - /** - * 남은 μ‹œκ°„ 계산 (μ΄ˆλ‹¨μœ„) - */ - public long getRemainingSeconds() { - LocalDateTime now = LocalDateTime.now(); - LocalDateTime endsAt = getEndsAt(); - - if (now.isAfter(endsAt)) { - return 0; - } - - return java.time.Duration.between(now, endsAt).getSeconds(); - } } diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/repository/AttendanceSessionRepository.java b/backend/src/main/java/org/sejongisc/backend/attendance/repository/AttendanceSessionRepository.java index dff2c1de..9579e348 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/repository/AttendanceSessionRepository.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/repository/AttendanceSessionRepository.java @@ -1,26 +1,14 @@ package org.sejongisc.backend.attendance.repository; import org.sejongisc.backend.attendance.entity.AttendanceSession; -import org.sejongisc.backend.attendance.entity.SessionStatus; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.Optional; import java.util.UUID; @Repository public interface AttendanceSessionRepository extends JpaRepository { - // μΆœμ„ μ½”λ“œλ‘œ μ„Έμ…˜ μ°ΎκΈ° (학생 μΆœμ„ 체크) - Optional findByCode(String code); - - // μƒνƒœλ³„ μ„Έμ…˜ 쑰회 - List findByStatus(SessionStatus status); - - // λͺ¨λ“  μ„Έμ…˜μ„ μ΅œμ‹ μˆœμœΌλ‘œ 쑰회 (κ΄€λ¦¬μžμš©) - List findAllByOrderByStartsAtDesc(); - // μ½”λ“œ 쀑볡 체크 boolean existsByCode(String code); } diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceRoundService.java b/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceRoundService.java index 64c6fb9e..c634b7f8 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceRoundService.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceRoundService.java @@ -35,28 +35,25 @@ public class AttendanceRoundService { * λΌμš΄λ“œ 생성 */ public AttendanceRoundResponse createRound(UUID sessionId, AttendanceRoundRequest request) { - log.info("πŸ“‹ λΌμš΄λ“œ 생성 μš”μ²­: sessionId={}, roundDate={}, startTime={}, allowedMinutes={}", - sessionId, request.getRoundDate(), request.getStartTime(), request.getAllowedMinutes()); + log.info("πŸ“‹ λΌμš΄λ“œ 생성 μš”μ²­: sessionId={}, date={}, startTime={}, availableMinutes={}", + sessionId, request.getDate(), request.getStartTime(), request.getAvailableMinutes()); AttendanceSession session = attendanceSessionRepository.findById(sessionId) .orElseThrow(() -> new IllegalArgumentException("μ„Έμ…˜μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€: " + sessionId)); try { - // ν΄λΌμ΄μ–ΈνŠΈκ°€ 보낸 λ‚ μ§œ λŒ€μ‹  μ„œλ²„μ˜ ν˜„μž¬ λ‚ μ§œλ₯Ό μ‚¬μš©ν•˜μ—¬ μ‹œκ°„λŒ€ 차이 λ°©μ§€ - LocalDate roundDate = request.getRoundDate(); - if (roundDate == null) { - roundDate = LocalDate.now(); - } + // ν΄λΌμ΄μ–ΈνŠΈκ°€ μ œκ³΅ν•œ λ‚ μ§œλ₯Ό μ‚¬μš©ν•˜κ³ , μ—†μœΌλ©΄ μ„œλ²„μ˜ ν˜„μž¬ λ‚ μ§œλ₯Ό κΈ°λ³Έκ°’μœΌλ‘œ μ‚¬μš© + LocalDate roundDate = request.getDate() != null ? request.getDate() : LocalDate.now(); LocalTime requestStartTime = request.getStartTime(); - log.info("πŸ“… μ‹œκ°„λŒ€ 정보: ν΄λΌμ΄μ–ΈνŠΈ roundDate={}, μ„œλ²„ today={}, μš”μ²­ startTime={}", - request.getRoundDate(), roundDate, requestStartTime); + log.info("πŸ“… μ‹œκ°„λŒ€ 정보: ν΄λΌμ΄μ–ΈνŠΈ date={}, μ‚¬μš©ν•  roundDate={}, μš”μ²­ startTime={}", + request.getDate(), roundDate, requestStartTime); AttendanceRound round = AttendanceRound.builder() .attendanceSession(session) - .roundDate(roundDate) + .roundDate(roundDate) // ν΄λΌμ΄μ–ΈνŠΈ λ‚ μ§œλ₯Ό μš°μ„  μ‚¬μš© .startTime(requestStartTime) - .allowedMinutes(request.getAllowedMinutes() != null ? request.getAllowedMinutes() : 30) + .allowedMinutes(request.getAvailableMinutes() != null ? request.getAvailableMinutes() : 30) .roundStatus(RoundStatus.UPCOMING) .build(); @@ -115,9 +112,9 @@ public AttendanceRoundResponse updateRound(UUID roundId, AttendanceRoundRequest .orElseThrow(() -> new IllegalArgumentException("λΌμš΄λ“œλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€: " + roundId)); round.updateRoundInfo( - request.getRoundDate(), + request.getDate(), request.getStartTime(), - request.getAllowedMinutes() + request.getAvailableMinutes() ); AttendanceRound updated = attendanceRoundRepository.save(round); diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceService.java b/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceService.java index df5a32c2..0ad2b717 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceService.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceService.java @@ -75,24 +75,27 @@ public AttendanceResponse checkIn(UUID sessionId, AttendanceRequest request, UUI } + // μ„Έμ…˜μ˜ ν™œμ„± λΌμš΄λ“œλ₯Ό 찾음 LocalDateTime now = LocalDateTime.now(); - if (now.isBefore(session.getStartsAt())) { - throw new IllegalStateException("아직 μΆœμ„ μ‹œκ°„μ΄ μ•„λ‹™λ‹ˆλ‹€"); - } - - LocalDateTime endTime = session.getEndsAt(); - if (now.isAfter(endTime)) { - throw new IllegalStateException("μΆœμ„ μ‹œκ°„μ΄ μ’…λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€"); - } + AttendanceRound activeRound = session.getRounds().stream() + .filter(round -> { + LocalDateTime roundStart = LocalDateTime.of(round.getRoundDate(), round.getStartTime()); + LocalDateTime roundEnd = roundStart.plusMinutes(round.getAllowedMinutes()); + return !now.isBefore(roundStart) && now.isBefore(roundEnd); + }) + .findFirst() + .orElseThrow(() -> new IllegalStateException("ν˜„μž¬ μ§„ν–‰ 쀑인 λΌμš΄λ“œκ°€ μ—†μŠ΅λ‹ˆλ‹€")); // μ‹œμž‘ ν›„ 5λΆ„ μ΄λ‚΄λŠ” 정상 μΆœμ„, μ΄ν›„λŠ” 지각 - LocalDateTime lateThreshold = session.getStartsAt().plusMinutes(5); + LocalDateTime roundStart = LocalDateTime.of(activeRound.getRoundDate(), activeRound.getStartTime()); + LocalDateTime lateThreshold = roundStart.plusMinutes(5); AttendanceStatus status = now.isAfter(lateThreshold) ? AttendanceStatus.LATE : AttendanceStatus.PRESENT; Attendance attendance = Attendance.builder() .user(user) .attendanceSession(session) + .attendanceRound(activeRound) .attendanceStatus(status) .checkedAt(now) .awardedPoints(session.getRewardPoints()) @@ -101,6 +104,7 @@ public AttendanceResponse checkIn(UUID sessionId, AttendanceRequest request, UUI .deviceInfo(request.getDeviceInfo()) .build(); + attendance = attendanceRepository.save(attendance); log.info("μΆœμ„ 체크인 μ™„λ£Œ: μ‚¬μš©μž={}, μƒνƒœ={}", user.getName(), status); diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceSessionService.java b/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceSessionService.java index ba31eaf7..2f06c8a4 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceSessionService.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceSessionService.java @@ -13,8 +13,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.time.LocalTime; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -35,7 +33,7 @@ public class AttendanceSessionService { * - κΈ°λ³Έ μƒνƒœ UPCOMING 으둜 μ„€μ • */ public AttendanceSessionResponse createSession(AttendanceSessionRequest request) { - log.info("μΆœμ„ μ„Έμ…˜ 생성 μ‹œμž‘: 제λͺ©={}", request.getTitle()); + log.info("μΆœμ„ μ„Έμ…˜ 생성 μ‹œμž‘: 제λͺ©={}, κΈ°λ³Έμ‹œκ°„={}", request.getTitle(), request.getDefaultStartTime()); String code = generateUniqueCode(); Location location = null; @@ -50,8 +48,8 @@ public AttendanceSessionResponse createSession(AttendanceSessionRequest request) AttendanceSession session = AttendanceSession.builder() .title(request.getTitle()) - .startsAt(request.getStartsAt()) - .windowSeconds(request.getWindowSeconds()) + .defaultStartTime(request.getDefaultStartTime()) + .defaultAvailableMinutes(request.getDefaultAvailableMinutes()) .code(code) .rewardPoints(request.getRewardPoints()) .location(location) @@ -80,13 +78,14 @@ public AttendanceSessionResponse getSessionById(UUID sessionId) { /** * λͺ¨λ“  μ„Έμ…˜ λͺ©λ‘ 쑰회 * - κ΄€λ¦¬μžμš©, 곡개/λΉ„κ³΅κ°œ λͺ¨λ‘ 포함 - * - μ΅œμ‹  순으둜 μ •λ ¬ + * - 생성 λ‚ μ§œ μ—­μˆœμœΌλ‘œ μ •λ ¬ */ @Transactional(readOnly = true) public List getAllSessions() { - List sessions = attendanceSessionRepository.findAllByOrderByStartsAtDesc(); + List sessions = attendanceSessionRepository.findAll(); return sessions.stream() + .sorted((a, b) -> b.getCreatedDate().compareTo(a.getCreatedDate())) .map(this::convertToResponse) .collect(Collectors.toList()); } @@ -94,14 +93,14 @@ public List getAllSessions() { /** * 곡개 μ„Έμ…˜ λͺ©λ‘ 쑰회 * - 학생듀이 λ³Ό 수 μžˆλŠ” λͺ¨λ“  μ„Έμ…˜λ§Œ 쑰회 - * - μ΅œμ‹  순으둜 μ •λ ¬ + * - 생성 λ‚ μ§œ μ—­μˆœμœΌλ‘œ μ •λ ¬ */ @Transactional(readOnly = true) public List getPublicSessions() { - List sessions = attendanceSessionRepository - .findAllByOrderByStartsAtDesc(); + List sessions = attendanceSessionRepository.findAll(); return sessions.stream() + .sorted((a, b) -> b.getCreatedDate().compareTo(a.getCreatedDate())) .map(this::convertToResponse) .collect(Collectors.toList()); } @@ -109,28 +108,22 @@ public List getPublicSessions() { /** * ν™œμ„± μ„Έμ…˜ λͺ©λ‘ 쑰회 * - ν˜„μž¬ 체크인 κ°€λŠ₯ν•œ μ„Έμ…˜λ“€λ§Œ 필터링 - * - μ‹œμž‘ μ‹œκ°„ ~ μ’…λ£Œ μ‹œκ°„ λ²”μœ„ λ‚΄ μ„Έμ…˜ + * - μƒνƒœκ°€ OPEN인 μ„Έμ…˜λ§Œ λ°˜ν™˜ */ @Transactional(readOnly = true) public List getActiveSessions() { - LocalDateTime now = LocalDateTime.now(); - List allSessions = attendanceSessionRepository.findAllByOrderByStartsAtDesc(); + List allSessions = attendanceSessionRepository.findAll(); return allSessions.stream() - .filter(session -> { - if (session.getStatus() != SessionStatus.OPEN) { - return false; - } - LocalDateTime endTime = session.getStartsAt().plusSeconds(session.getWindowSeconds()); - return !now.isBefore(session.getStartsAt()) && now.isBefore(endTime); - }) - .map(this::convertToResponse) - .collect(Collectors.toList()); + .filter(session -> SessionStatus.OPEN.equals(session.getStatus())) + .sorted((a, b) -> b.getCreatedDate().compareTo(a.getCreatedDate())) + .map(this::convertToResponse) + .collect(Collectors.toList()); } /** * μ„Έμ…˜ 정보 μˆ˜μ • - * - 제λͺ©, μ‹œκ°„, μœ„μΉ˜, 반경 λ“± μˆ˜μ • κ°€λŠ₯ + * - 제λͺ©, κΈ°λ³Έ μ‹œκ°„, μœ„μΉ˜, 반경 λ“± μˆ˜μ • κ°€λŠ₯ * - μ½”λ“œλŠ” λ³€κ²½λ˜μ§€ μ•ŠμŒ (λ³΄μ•ˆμƒ 이유) */ public AttendanceSessionResponse updateSession(UUID sessionId, AttendanceSessionRequest request) { @@ -152,8 +145,8 @@ public AttendanceSessionResponse updateSession(UUID sessionId, AttendanceSession session = session.toBuilder() .title(request.getTitle()) - .startsAt(request.getStartsAt()) - .windowSeconds(request.getWindowSeconds()) + .defaultStartTime(request.getDefaultStartTime()) + .defaultAvailableMinutes(request.getDefaultAvailableMinutes()) .rewardPoints(request.getRewardPoints()) .location(location) .build(); @@ -184,7 +177,7 @@ public void deleteSession(UUID sessionId) { /** * μ„Έμ…˜ μˆ˜λ™ ν™œμ„±ν™” * - μ„Έμ…˜ μƒνƒœλ₯Ό OPEN으둜 λ³€κ²½ - * - μ‹œκ°„κ³Ό 관계없이 체크인 ν™œμ„±ν™” + * - 체크인 ν™œμ„±ν™” */ public void activateSession(UUID sessionId) { log.info("μΆœμ„ μ„Έμ…˜ ν™œμ„±ν™” μ‹œμž‘: μ„Έμ…˜ID={}", sessionId); @@ -194,7 +187,6 @@ public void activateSession(UUID sessionId) { session = session.toBuilder() .status(SessionStatus.OPEN) - .startsAt(LocalDateTime.now()) .build(); attendanceSessionRepository.save(session); @@ -279,9 +271,8 @@ private String generateRandomCode() { /** * AttendanceSession μ—”ν‹°ν‹°λ₯Ό Response DTO둜 λ³€ν™˜ - * - κΈ°λ³Έ μ„Έμ…˜ 정보: 제λͺ©, μ‹œμž‘ μ‹œκ°„, μΆœμ„ 인정 μ‹œκ°„, 보상 포인트 + * - κΈ°λ³Έ μ„Έμ…˜ 정보: 제λͺ©, κΈ°λ³Έ μ‹œμž‘ μ‹œκ°„, μΆœμ„ 인정 μ‹œκ°„, 보상 포인트 * - μœ„μΉ˜ 정보: location 객체 (lat, lng) - * - 곡개 μ—¬λΆ€: isVisible boolean */ private AttendanceSessionResponse convertToResponse(AttendanceSession session) { // μœ„μΉ˜ 정보 λ³€ν™˜ (location이 μ‘΄μž¬ν•˜λ©΄ LocationInfo 객체 생성, μ—†μœΌλ©΄ null) @@ -293,21 +284,15 @@ private AttendanceSessionResponse convertToResponse(AttendanceSession session) { .build(); } - // defaultAvailableMinutes: windowSecondsλ₯Ό λΆ„ λ‹¨μœ„λ‘œ λ³€ν™˜ - Integer defaultAvailableMinutes = (int) (session.getWindowSeconds() / 60); - - // defaultStartTime: startsAtμ—μ„œ μ‹œκ°„ λΆ€λΆ„λ§Œ μΆ”μΆœ - LocalTime defaultStartTime = session.getStartsAt().toLocalTime(); - return AttendanceSessionResponse.builder() .attendanceSessionId(session.getAttendanceSessionId()) .title(session.getTitle()) .location(location) - .defaultStartTime(defaultStartTime) - .defaultAvailableMinutes(defaultAvailableMinutes) + .defaultStartTime(session.getDefaultStartTime()) + .defaultAvailableMinutes(session.getDefaultAvailableMinutes()) .rewardPoints(session.getRewardPoints()) .build(); - } + } diff --git a/backend/src/test/java/org/sejongisc/backend/attendance/controller/AttendanceRoundControllerTest.java b/backend/src/test/java/org/sejongisc/backend/attendance/controller/AttendanceRoundControllerTest.java index 58fc298a..93e3487a 100644 --- a/backend/src/test/java/org/sejongisc/backend/attendance/controller/AttendanceRoundControllerTest.java +++ b/backend/src/test/java/org/sejongisc/backend/attendance/controller/AttendanceRoundControllerTest.java @@ -70,14 +70,14 @@ void createRound_success() throws Exception { LocalTime startTime = LocalTime.of(14, 0); AttendanceRoundRequest request = AttendanceRoundRequest.builder() - .roundDate(roundDate) + .date(roundDate) .startTime(startTime) - .allowedMinutes(30) + .availableMinutes(30) .build(); AttendanceRoundResponse response = AttendanceRoundResponse.builder() .roundId(roundId) - .roundDate(roundDate) + .date(roundDate) .startTime(startTime) .availableMinutes(30) .status("upcoming") @@ -92,7 +92,7 @@ void createRound_success() throws Exception { .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.roundId").value(roundId.toString())) - .andExpect(jsonPath("$.roundDate").value(roundDate.toString())) + .andExpect(jsonPath("$.date").value(roundDate.toString())) .andExpect(jsonPath("$.startTime").value("14:00:00")) .andExpect(jsonPath("$.availableMinutes").value(30)) .andExpect(jsonPath("$.status").value("upcoming")); @@ -104,9 +104,9 @@ void createRound_success() throws Exception { void createRound_fail_noPermission() throws Exception { // given AttendanceRoundRequest request = AttendanceRoundRequest.builder() - .roundDate(LocalDate.now()) + .date(LocalDate.now()) .startTime(LocalTime.of(14, 0)) - .allowedMinutes(30) + .availableMinutes(30) .build(); // when & then @@ -126,7 +126,7 @@ void getRound_success() throws Exception { AttendanceRoundResponse response = AttendanceRoundResponse.builder() .roundId(roundId) - .roundDate(roundDate) + .date(roundDate) .startTime(startTime) .availableMinutes(30) .status("active") @@ -152,7 +152,7 @@ void getRoundsBySession_success() throws Exception { AttendanceRoundResponse round1 = AttendanceRoundResponse.builder() .roundId(UUID.randomUUID()) - .roundDate(roundDate) + .date(roundDate) .startTime(startTime) .availableMinutes(30) .status("active") @@ -160,7 +160,7 @@ void getRoundsBySession_success() throws Exception { AttendanceRoundResponse round2 = AttendanceRoundResponse.builder() .roundId(UUID.randomUUID()) - .roundDate(roundDate.plusDays(7)) + .date(roundDate.plusDays(7)) .startTime(startTime) .availableMinutes(30) .status("upcoming") @@ -186,14 +186,14 @@ void updateRound_success() throws Exception { LocalTime newStartTime = LocalTime.of(15, 0); AttendanceRoundRequest request = AttendanceRoundRequest.builder() - .roundDate(newDate) + .date(newDate) .startTime(newStartTime) - .allowedMinutes(45) + .availableMinutes(45) .build(); AttendanceRoundResponse response = AttendanceRoundResponse.builder() .roundId(roundId) - .roundDate(newDate) + .date(newDate) .startTime(newStartTime) .availableMinutes(45) .status("upcoming") @@ -207,7 +207,7 @@ void updateRound_success() throws Exception { .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) - .andExpect(jsonPath("$.roundDate").value(newDate.toString())) + .andExpect(jsonPath("$.date").value(newDate.toString())) .andExpect(jsonPath("$.startTime").value("15:00:00")) .andExpect(jsonPath("$.availableMinutes").value(45)); } @@ -320,7 +320,7 @@ void getRoundByDate_success() throws Exception { AttendanceRoundResponse response = AttendanceRoundResponse.builder() .roundId(roundId) - .roundDate(targetDate) + .date(targetDate) .startTime(startTime) .availableMinutes(30) .status("active") diff --git a/backend/src/test/java/org/sejongisc/backend/attendance/service/AttendanceRoundServiceTest.java b/backend/src/test/java/org/sejongisc/backend/attendance/service/AttendanceRoundServiceTest.java index b57c36c1..c4b8dc2e 100644 --- a/backend/src/test/java/org/sejongisc/backend/attendance/service/AttendanceRoundServiceTest.java +++ b/backend/src/test/java/org/sejongisc/backend/attendance/service/AttendanceRoundServiceTest.java @@ -50,16 +50,15 @@ void createRound_success() { LocalTime startTime = LocalTime.of(14, 0); AttendanceRoundRequest request = AttendanceRoundRequest.builder() - .roundDate(roundDate) + .date(roundDate) .startTime(startTime) - .allowedMinutes(30) + .availableMinutes(30) .build(); AttendanceSession session = AttendanceSession.builder() .attendanceSessionId(sessionId) .title("ν…ŒμŠ€νŠΈ μ„Έμ…˜") .code("123456") - .startsAt(LocalDateTime.now().plusDays(1)) .build(); AttendanceRound savedRound = AttendanceRound.builder() @@ -80,7 +79,7 @@ void createRound_success() { // then assertAll( () -> assertThat(response.getRoundId()).isNotNull(), - () -> assertThat(response.getRoundDate()).isEqualTo(roundDate), + () -> assertThat(response.getDate()).isEqualTo(roundDate), () -> assertThat(response.getStartTime()).isEqualTo(startTime), () -> assertThat(response.getAvailableMinutes()).isEqualTo(30) ); @@ -95,9 +94,9 @@ void createRound_fail_sessionNotFound() { // given UUID sessionId = UUID.randomUUID(); AttendanceRoundRequest request = AttendanceRoundRequest.builder() - .roundDate(LocalDate.now().plusDays(1)) + .date(LocalDate.now().plusDays(1)) .startTime(LocalTime.of(14, 0)) - .allowedMinutes(30) + .availableMinutes(30) .build(); when(attendanceSessionRepository.findById(sessionId)).thenReturn(Optional.empty()); @@ -132,7 +131,7 @@ void getRound_success() { // then assertAll( () -> assertThat(response.getRoundId()).isEqualTo(roundId), - () -> assertThat(response.getRoundDate()).isEqualTo(roundDate), + () -> assertThat(response.getDate()).isEqualTo(roundDate), () -> assertThat(response.getStartTime()).isEqualTo(startTime) ); } @@ -185,8 +184,8 @@ void getRoundsBySession_success() { // then assertAll( () -> assertThat(responses).hasSize(2), - () -> assertThat(responses.get(0).getRoundDate()).isEqualTo(date1), - () -> assertThat(responses.get(1).getRoundDate()).isEqualTo(date2) + () -> assertThat(responses.get(0).getDate()).isEqualTo(date1), + () -> assertThat(responses.get(1).getDate()).isEqualTo(date2) ); } @@ -199,9 +198,9 @@ void updateRound_success() { LocalTime newTime = LocalTime.of(15, 0); AttendanceRoundRequest request = AttendanceRoundRequest.builder() - .roundDate(newDate) + .date(newDate) .startTime(newTime) - .allowedMinutes(45) + .availableMinutes(45) .build(); AttendanceRound existingRound = AttendanceRound.builder() @@ -226,7 +225,7 @@ void updateRound_success() { // then assertAll( - () -> assertThat(response.getRoundDate()).isEqualTo(newDate), + () -> assertThat(response.getDate()).isEqualTo(newDate), () -> assertThat(response.getStartTime()).isEqualTo(newTime), () -> assertThat(response.getAvailableMinutes()).isEqualTo(45) ); @@ -285,7 +284,7 @@ void getRoundByDate_success() { // then assertAll( - () -> assertThat(response.getRoundDate()).isEqualTo(targetDate), + () -> assertThat(response.getDate()).isEqualTo(targetDate), () -> assertThat(response.getStartTime()).isEqualTo(LocalTime.of(14, 0)) ); } diff --git a/backend/src/test/java/org/sejongisc/backend/attendance/service/AttendanceServiceTest.java b/backend/src/test/java/org/sejongisc/backend/attendance/service/AttendanceServiceTest.java index 57c55d76..3f6de440 100644 --- a/backend/src/test/java/org/sejongisc/backend/attendance/service/AttendanceServiceTest.java +++ b/backend/src/test/java/org/sejongisc/backend/attendance/service/AttendanceServiceTest.java @@ -66,8 +66,6 @@ public class AttendanceServiceTest { .attendanceSessionId(sessionId) .title("μ„Ένˆ¬μ—° μ •κΈ°λͺ¨μž„") .code(code) - .startsAt(now.minusMinutes(5)) - .windowSeconds(1800) .rewardPoints(10) .location(sessionLocation) .status(SessionStatus.OPEN) diff --git a/backend/src/test/java/org/sejongisc/backend/attendance/service/SessionLocationUpdateTest.java b/backend/src/test/java/org/sejongisc/backend/attendance/service/SessionLocationUpdateTest.java index a9219edc..4f63991f 100644 --- a/backend/src/test/java/org/sejongisc/backend/attendance/service/SessionLocationUpdateTest.java +++ b/backend/src/test/java/org/sejongisc/backend/attendance/service/SessionLocationUpdateTest.java @@ -59,8 +59,6 @@ void updateSessionLocation_success_withExistingLocation() { .attendanceSessionId(sessionId) .title("ν…ŒμŠ€νŠΈ μ„Έμ…˜") .code("123456") - .startsAt(LocalDateTime.now().plusHours(1)) - .windowSeconds(1800) .location(existingLocation) .status(SessionStatus.UPCOMING) .build(); @@ -105,8 +103,6 @@ void updateSessionLocation_success_withoutExistingLocation() { .attendanceSessionId(sessionId) .title("ν…ŒμŠ€νŠΈ μ„Έμ…˜") .code("123456") - .startsAt(LocalDateTime.now().plusHours(1)) - .windowSeconds(1800) .location(null) // κΈ°μ‘΄ μœ„μΉ˜ μ—†μŒ .status(SessionStatus.UPCOMING) .build(); @@ -174,8 +170,6 @@ void updateSessionLocation_verifyRadiusPreservation() { .attendanceSessionId(sessionId) .title("ν…ŒμŠ€νŠΈ μ„Έμ…˜") .code("123456") - .startsAt(LocalDateTime.now().plusHours(1)) - .windowSeconds(1800) .location(existingLocation) .status(SessionStatus.UPCOMING) .build();