Skip to content

Commit

Permalink
Feature: 출결 마감 시간 수정에 따른 기존 출결 기록 변화 (#88)
Browse files Browse the repository at this point in the history
* feat: 범위를 벗어난 출결 시간에 대한 에러 처리

* test: 시간 예외 검증 로직 테스트

* feat: 출결 시간 변경 시 기존 데이터 최신화 작업

* feat: 출결 입력 시간 컬럼 추가
  • Loading branch information
Youthhing authored Aug 11, 2024
1 parent 59de1d6 commit b94bb6f
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public enum ErrorCode {

//출석 관련 AT
OFFLINE_ATTEND_FAIL(HttpStatus.BAD_REQUEST, "AT-101", "거리 부적합으로 인한 대면 출석 실패"),
INVALID_ATTEND_TIME(HttpStatus.BAD_REQUEST, "AT-102", "시간 입력 범위가 잘못되었습니다."),
ALREADY_ATTEND(HttpStatus.CONFLICT, "AT-301", "이미 해당 타입으로 출석한 기록이 있습니다."),
ATTENDANCE_CLOSED(HttpStatus.CONFLICT, "AT-401", "아직 출석 시간이 아닙니다."),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand Down Expand Up @@ -52,34 +53,40 @@ public class AttendanceRecord extends BaseTimeEntity {
@JoinColumn(name = "attendance_id")
private Attendance attendance;

@Column(name = "attend_time", nullable = false)
private LocalDateTime attendTime;

private AttendanceRecord(AttendanceType attendanceType, AttendanceStatus attendanceStatus, Double locationAccuracy,
Long memberId, Attendance attendance) {
Long memberId, Attendance attendance, LocalDateTime attendTime) {
this.attendanceType = attendanceType;
this.attendanceStatus = attendanceStatus;
this.locationAccuracy = locationAccuracy;
this.memberId = memberId;
this.attendance = attendance;
this.attendTime = attendTime;
}

public static AttendanceRecord onLineRecord(Attendance attendance, Long memberId,
AttendanceStatus attendanceStatus) {
public static AttendanceRecord onLineRecord(Attendance attendance, Long memberId, AttendanceStatus attendanceStatus,
LocalDateTime attendTime) {
return new AttendanceRecord(
AttendanceType.ONLINE,
attendanceStatus,
null,
memberId,
attendance
attendance,
attendTime
);
}

public static AttendanceRecord offlineRecord(Attendance attendance, Long memberId, Double locationAccuracy,
AttendanceStatus attendanceStatus) {
AttendanceStatus attendanceStatus, LocalDateTime attendTime) {
return new AttendanceRecord(
AttendanceType.OFFLINE,
attendanceStatus,
locationAccuracy,
memberId,
attendance
attendance,
attendTime
);
}

Expand All @@ -90,4 +97,8 @@ public void updateAttendanceType(AttendanceType attendanceType) {
public void updateLocationAccuracy(Double accuracy) {
this.locationAccuracy = accuracy;
}

public void updateAttendanceStatus(AttendanceStatus attendanceStatus) {
this.attendanceStatus = attendanceStatus;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ public interface AttendanceRecordRepository extends JpaRepository<AttendanceReco
boolean existsByAttendanceIdAndMemberIdAndAttendanceType(Long attendanceId, Long memberId, AttendanceType attendanceType);

Optional<AttendanceRecord> findByMemberIdAndAttendanceId(Long memberId, Long attendanceId);

List<AttendanceRecord> findAllByAttendanceId(Long attendanceId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.cotato.csquiz.domain.attendance.embedded.Location;
import org.cotato.csquiz.domain.attendance.entity.Attendance;
import org.cotato.csquiz.domain.attendance.repository.AttendanceRepository;
import org.cotato.csquiz.domain.attendance.util.AttendanceUtil;
import org.cotato.csquiz.domain.generation.entity.Session;
import org.cotato.csquiz.domain.generation.repository.SessionRepository;
import org.springframework.stereotype.Service;
Expand All @@ -32,6 +33,7 @@ public class AttendanceAdminService {

@Transactional
public void addAttendance(Session session, Location location, AttendanceDeadLineDto attendanceDeadLine) {
AttendanceUtil.validateAttendanceTime(attendanceDeadLine.attendanceDeadLine(), attendanceDeadLine.lateDeadLine());

Attendance attendance = Attendance.builder()
.session(session)
Expand All @@ -53,6 +55,8 @@ public void updateAttendance(UpdateAttendanceRequest request) {
Session attendanceSession = sessionRepository.findById(attendance.getSessionId())
.orElseThrow(() -> new EntityNotFoundException("출석과 연결된 세션을 찾을 수 없습니다"));

AttendanceUtil.validateAttendanceTime(request.attendTime().attendanceDeadLine(), request.attendTime().lateDeadLine());

if (attendanceSession.getSessionDate() == null) {
throw new AppException(ErrorCode.SESSION_DATE_NOT_FOUND);
}
Expand All @@ -63,6 +67,8 @@ public void updateAttendance(UpdateAttendanceRequest request) {
LocalDateTime.of(attendanceSession.getSessionDate(), request.attendTime()
.lateDeadLine()).plusSeconds(DEFAULT_ATTEND_SECOND));
attendance.updateLocation(request.location());

attendanceRecordService.updateAttendanceStatus(attendance);
}

public List<AttendanceRecordResponse> findAttendanceRecords(Long generationId, Integer month) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
import org.cotato.csquiz.domain.attendance.entity.Attendance;
import org.cotato.csquiz.domain.attendance.entity.AttendanceRecord;
import org.cotato.csquiz.domain.attendance.enums.AttendanceOpenStatus;
import org.cotato.csquiz.domain.attendance.enums.AttendanceStatus;
import org.cotato.csquiz.domain.attendance.repository.AttendanceRecordRepository;
import org.cotato.csquiz.domain.attendance.repository.AttendanceRepository;
import org.cotato.csquiz.domain.attendance.util.AttendanceUtil;
import org.cotato.csquiz.domain.auth.entity.Member;
import org.cotato.csquiz.domain.auth.service.MemberService;
import org.springframework.stereotype.Service;
Expand Down Expand Up @@ -70,4 +72,16 @@ public AttendResponse submitRecord(AttendanceParams request, final Long memberId

return requestAttendanceService.attend(request, memberId, attendance);
}

@Transactional
public void updateAttendanceStatus(Attendance attendance) {
List<AttendanceRecord> attendanceRecords = attendanceRecordRepository.findAllByAttendanceId(attendance.getId());

for (AttendanceRecord attendanceRecord : attendanceRecords) {
AttendanceStatus attendanceStatus = AttendanceUtil.calculateAttendanceStatus(attendance, attendanceRecord.getAttendTime());
attendanceRecord.updateAttendanceStatus(attendanceStatus);
}

attendanceRecordRepository.saveAll(attendanceRecords);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public AttendResponse request(AttendanceParams params, Long memberId, Attendance

AttendanceRecord attendanceRecord = attendanceRecordRepository.findByMemberIdAndAttendanceId(memberId,
request.getAttendanceId())
.orElseGet(() -> AttendanceRecord.offlineRecord(attendance, memberId, accuracy, attendanceStatus));
.orElseGet(() -> AttendanceRecord.offlineRecord(attendance, memberId, accuracy, attendanceStatus, request.getRequestTime()));

attendanceRecord.updateAttendanceType(request.attendanceType());
attendanceRecord.updateLocationAccuracy(accuracy);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public AttendanceType attendanceType() {
public AttendResponse request(AttendanceParams params, Long memberId, Attendance attendance) {
AttendanceStatus attendanceStatus = AttendanceUtil.calculateAttendanceStatus(attendance, params.requestTime());

attendanceRecordRepository.save(AttendanceRecord.onLineRecord(attendance, memberId, attendanceStatus));
attendanceRecordRepository.save(AttendanceRecord.onLineRecord(attendance, memberId, attendanceStatus, params.requestTime()));

return AttendResponse.from(attendanceStatus);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.time.LocalDateTime;
import java.time.LocalTime;
import org.cotato.csquiz.common.error.ErrorCode;
import org.cotato.csquiz.common.error.exception.AppException;
import org.cotato.csquiz.domain.attendance.entity.Attendance;
import org.cotato.csquiz.domain.attendance.enums.AttendanceOpenStatus;
import org.cotato.csquiz.domain.attendance.enums.AttendanceStatus;
Expand Down Expand Up @@ -47,4 +49,18 @@ private static boolean isToday(Attendance attendance, LocalDateTime currentDate)
private static boolean isStarted(LocalTime currentTime) {
return currentTime.isAfter(DeadLine.ATTENDANCE_START_TIME.getTime());
}

public static void validateAttendanceTime(LocalTime attendDeadLine, LocalTime lateDeadLine) {
if (!DeadLine.ATTENDANCE_START_TIME.getTime().isBefore(attendDeadLine)) {
throw new AppException(ErrorCode.INVALID_ATTEND_TIME);
}

if (!attendDeadLine.isBefore(lateDeadLine)) {
throw new AppException(ErrorCode.INVALID_ATTEND_TIME);
}

if (!lateDeadLine.isBefore(DeadLine.ATTENDANCE_END_TIME.getTime())) {
throw new AppException(ErrorCode.INVALID_ATTEND_TIME);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package org.cotato.csquiz.domain.attendance.util;

import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.*;

import java.time.LocalDateTime;
import java.time.LocalTime;
import org.cotato.csquiz.common.error.ErrorCode;
import org.cotato.csquiz.common.error.exception.AppException;
import org.cotato.csquiz.domain.attendance.entity.Attendance;
import org.cotato.csquiz.domain.attendance.enums.AttendanceOpenStatus;
import org.cotato.csquiz.domain.generation.entity.Session;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class AttendanceUtilTest {

@Test
void 날짜가_다르면_출석이_닫혀있다(){
void 날짜가_다르면_출석이_닫혀있다() {
//given
Attendance attendance = Attendance.builder()
.attendanceDeadLine(LocalDateTime.now())
Expand Down Expand Up @@ -48,4 +53,45 @@ class AttendanceUtilTest {
//then
assertEquals(attendanceStatus, AttendanceOpenStatus.CLOSED);
}

@Test
void 지각마감이_세션시작보다_빠를__없다() {
//given
LocalTime attendDeadline = LocalTime.of(18, 40, 0);
LocalTime lateDeadline = LocalTime.of(19, 20, 0);

//when, then
assertThatThrownBy(() -> AttendanceUtil.validateAttendanceTime(attendDeadline, lateDeadline))
.isInstanceOf(AppException.class)
.extracting("errorCode")
.isEqualTo(ErrorCode.INVALID_ATTEND_TIME);
}

@DisplayName(value = "지각마감이 출석보다 빠르면 예외를 발생한다.")
@Test
void 지각마감보다_출석마감이_빠르다() {
//given
LocalTime attendDeadline = LocalTime.of(19, 40, 0);
LocalTime lateDeadline = LocalTime.of(19, 20, 0);

//when, then
assertThatThrownBy(() -> AttendanceUtil.validateAttendanceTime(attendDeadline, lateDeadline))
.isInstanceOf(AppException.class)
.extracting("errorCode")
.isEqualTo(ErrorCode.INVALID_ATTEND_TIME);
}

@DisplayName(value = "지각 마감이 세션 종료보다 늦으면 예외를 발생한다.")
@Test
void 지각마감시간_검증_기능() {
//given
LocalTime attendDeadline = LocalTime.of(19, 40, 0);
LocalTime lateDeadline = LocalTime.of(20, 20, 0);

//when, then
assertThatThrownBy(() -> AttendanceUtil.validateAttendanceTime(attendDeadline, lateDeadline))
.isInstanceOf(AppException.class)
.extracting("errorCode")
.isEqualTo(ErrorCode.INVALID_ATTEND_TIME);
}
}

0 comments on commit b94bb6f

Please sign in to comment.