Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.sejongisc.backend.common.entity.postgres.BasePostgresEntity;
import org.sejongisc.backend.user.entity.User;
Expand All @@ -15,6 +12,7 @@

@Entity
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.sejongisc.backend.attendance.entity;

public enum AttendanceStatus {
PENDING("미정"), // 라운드 예정 중 - 아직 체크인 안 됨
PRESENT("출석"), // 정상 출석
LATE("지각"), // 지각 출석
ABSENT("결석"), // 미출석
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,16 @@ public interface AttendanceRoundRepository extends JpaRepository<AttendanceRound
List<AttendanceRound> findBySession_SessionIdAndRoundDateBefore(
@Param("sessionId") UUID sessionId,
@Param("date") LocalDate date);

/**
* 세션의 특정 날짜 이후의 모든 라운드 조회
* - 세션에 유저 추가 시, 미래 라운드들에 자동으로 PENDING 처리하기 위해 사용
*/
@Query("SELECT r FROM AttendanceRound r " +
"WHERE r.attendanceSession.attendanceSessionId = :sessionId " +
"AND r.roundDate >= :date " +
"ORDER BY r.roundDate ASC")
List<AttendanceRound> findBySession_SessionIdAndRoundDateAfterOrEqual(
@Param("sessionId") UUID sessionId,
@Param("date") LocalDate date);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
import lombok.extern.slf4j.Slf4j;
import org.sejongisc.backend.attendance.dto.AttendanceRoundRequest;
import org.sejongisc.backend.attendance.dto.AttendanceRoundResponse;
import org.sejongisc.backend.attendance.entity.AttendanceRound;
import org.sejongisc.backend.attendance.entity.AttendanceSession;
import org.sejongisc.backend.attendance.entity.RoundStatus;
import org.sejongisc.backend.attendance.entity.*;
import org.sejongisc.backend.attendance.repository.AttendanceRepository;
import org.sejongisc.backend.attendance.repository.AttendanceRoundRepository;
import org.sejongisc.backend.attendance.repository.AttendanceSessionRepository;
import org.sejongisc.backend.attendance.repository.SessionUserRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -30,6 +30,9 @@ public class AttendanceRoundService {

private final AttendanceRoundRepository attendanceRoundRepository;
private final AttendanceSessionRepository attendanceSessionRepository;
private final SessionUserRepository sessionUserRepository;
private final AttendanceRepository attendanceRepository;


/**
* 라운드 생성
Expand Down Expand Up @@ -74,8 +77,25 @@ public AttendanceRoundResponse createRound(UUID sessionId, AttendanceRoundReques
// 양방향 관계를 DB에 반영하기 위해 세션도 저장
attendanceSessionRepository.save(session);

log.info("✅ 라운드 생성 완료 - sessionId: {}, roundId: {}, roundDate: {}, roundStatus: {}",
sessionId, saved.getRoundId(), saved.getRoundDate(), saved.getRoundStatus());
// ⭐ 라운드 생성 시 세션의 모든 SessionUser에 대해 PENDING 상태의 Attendance 미리 생성
log.info("📝 세션 사용자에 대한 PENDING 출석 기록 생성 시작: sessionId={}, roundId={}",
sessionId, saved.getRoundId());

List<SessionUser> sessionUsers = sessionUserRepository.findBySessionId(sessionId);
for (SessionUser sessionUser : sessionUsers) {
Attendance pendingAttendance = Attendance.builder()
.user(sessionUser.getUser())
.attendanceSession(session)
.attendanceRound(saved)
.attendanceStatus(AttendanceStatus.PENDING)
.build();
attendanceRepository.save(pendingAttendance);
log.info(" ✓ PENDING 출석 기록 생성: userId={}, userName={}, roundId={}",
sessionUser.getUser().getUserId(), sessionUser.getUser().getName(), saved.getRoundId());
}

log.info("✅ 라운드 생성 완료 - sessionId: {}, roundId: {}, roundDate: {}, roundStatus: {}, 생성된PENDING개수: {}",
sessionId, saved.getRoundId(), saved.getRoundDate(), saved.getRoundStatus(), sessionUsers.size());
return AttendanceRoundResponse.fromEntity(saved);
} catch (Exception e) {
log.error("❌ 라운드 생성 중 오류 발생: sessionId={}, error={}", sessionId, e.getMessage(), e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,12 @@ public AttendanceCheckInResponse checkInByRound(AttendanceCheckInRequest request
log.info("✅ 시간 검증 성공: 라운드ID={}, 사용자={}, 시간={}, 범위=[{}~{}]",
request.getRoundId(), user.getName(), checkTime, startTime, endTime);

// 2. 중복 출석 확인
boolean alreadyCheckedIn = attendanceRepository.findByAttendanceRound_RoundIdAndUser(request.getRoundId(), user)
.isPresent();
if (alreadyCheckedIn) {
log.warn("중복 출석 시도: 라운드ID={}, 사용자={}", request.getRoundId(), user.getName());
// 2. 기존 출석 기록 확인 (PENDING 제외하고 실제 체크인한 기록만 중복으로 취급)
Attendance existingAttendance = attendanceRepository.findByAttendanceRound_RoundIdAndUser(request.getRoundId(), user)
.orElse(null);
if (existingAttendance != null && existingAttendance.getAttendanceStatus() != AttendanceStatus.PENDING) {
log.warn("중복 출석 시도: 라운드ID={}, 사용자={}, 기존상태={}",
request.getRoundId(), user.getName(), existingAttendance.getAttendanceStatus());
return AttendanceCheckInResponse.builder()
.roundId(request.getRoundId())
.success(false)
Expand Down Expand Up @@ -332,8 +333,8 @@ public AttendanceResponse updateAttendanceStatusByRound(UUID roundId, UUID userI
private AttendanceResponse convertToResponse(Attendance attendance) {
return AttendanceResponse.builder()
.attendanceId(attendance.getAttendanceId())
.userId(attendance.getUser().getUserId())
.userName(attendance.getUser().getName())
.userId(attendance.getUser() != null ? attendance.getUser().getUserId() : null)
.userName(attendance.getUser() != null ? attendance.getUser().getName() : "익명")
.attendanceSessionId(attendance.getAttendanceSession().getAttendanceSessionId())
.attendanceRoundId(attendance.getAttendanceRound() != null ?
attendance.getAttendanceRound().getRoundId() : null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,38 @@ public SessionUserResponse addUserToSession(UUID sessionId, UUID userId) {
log.info("✅ 과거 라운드 자동 결석 처리 완료: 처리된 라운드 수={}", pastRounds.size());
}

// 6. ⭐ 미래 라운드들에 대해 자동으로 PENDING 상태 처리
List<AttendanceRound> futureRounds = attendanceRoundRepository.findBySession_SessionIdAndRoundDateAfterOrEqual(
sessionId,
LocalDate.now()
);

if (!futureRounds.isEmpty()) {
log.info("📅 미래 라운드 PENDING 처리: 미래 라운드 수={}", futureRounds.size());

for (AttendanceRound round : futureRounds) {
// 이미 해당 라운드에 출석 기록이 있는지 확인
boolean alreadyExists = attendanceRepository.findByAttendanceRound_RoundIdAndUser(round.getRoundId(), user)
.isPresent();

if (!alreadyExists) {
// 새로운 Attendance 레코드 생성 (PENDING 상태)
Attendance pendingRecord = Attendance.builder()
.user(user)
.attendanceSession(session)
.attendanceRound(round)
.attendanceStatus(AttendanceStatus.PENDING)
.build();

attendanceRepository.save(pendingRecord);
log.info(" - PENDING 기록 생성: roundId={}, date={}, userName={}",
round.getRoundId(), round.getRoundDate(), user.getName());
}
}

log.info("✅ 미래 라운드 PENDING 처리 완료: 처리된 라운드 수={}", futureRounds.size());
}

log.info("✅ 세션에 사용자 추가 완료: sessionId={}, userId={}, userName={}",
sessionId, userId, user.getName());

Expand Down