Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: 세션 날짜 변경에 따른 출결 관련 로직 수정 #152

Merged
merged 17 commits into from
Sep 12, 2024
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
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
aes.secret.salt: ${{ secrets.AES_SECRET_SALT }}
spring.mail.username: ${{ secrets.SENDER_EMAIL }}
spring.mail.password: ${{ secrets.SENDER_PASSWORD }}
location.distance: ${{ secrets.STANDARD_DISTANCE }}


# [2] 실행 권한 부여
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ jobs:
aes.secret.salt: ${{ secrets.AES_SECRET_SALT }}
spring.mail.username: ${{ secrets.SENDER_EMAIL }}
spring.mail.password: ${{ secrets.SENDER_PASSWORD }}
location.distance: ${{ secrets.STANDARD_DISTANCE }}


# [2] 실행 권한 부여
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.cotato.csquiz.api.attendance.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDate;
import java.time.LocalDateTime;
import org.cotato.csquiz.domain.attendance.entity.Attendance;
import org.cotato.csquiz.domain.attendance.entity.AttendanceRecord;
Expand All @@ -21,7 +20,7 @@ public record MemberAttendResponse(
@Schema(description = "세션 타이틀", example = "3주차 세션")
String sessionTitle,
@Schema(description = "세션 날짜")
LocalDate sessionDate,
LocalDateTime sessionDateTime,
@Schema(description = "출결 진행 여부", examples = {
"CLOSED", "OPEN"
})
Expand All @@ -37,7 +36,7 @@ public static MemberAttendResponse closedAttendanceResponse(Session session, Att
attendanceRecord.getAttendance().getId(),
attendanceRecord.getMemberId(),
session.getTitle(),
session.getSessionDate(),
session.getSessionDateTime(),
AttendanceOpenStatus.CLOSED,
attendanceRecord.getAttendanceType(),
attendanceRecord.getAttendanceResult()
Expand All @@ -50,8 +49,8 @@ public static MemberAttendResponse openedAttendanceResponse(Attendance attendanc
attendance.getId(),
memberId,
session.getTitle(),
session.getSessionDate(),
AttendanceUtil.getAttendanceOpenStatus(attendance, LocalDateTime.now()),
session.getSessionDateTime(),
AttendanceUtil.getAttendanceOpenStatus(session.getSessionDateTime(), attendance, LocalDateTime.now()),
null,
null
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import org.cotato.csquiz.api.session.dto.CsEducationOnSessionNumberResponse;
import org.cotato.csquiz.api.session.dto.DeleteSessionImageRequest;
import org.cotato.csquiz.api.session.dto.SessionListResponse;
import org.cotato.csquiz.api.session.dto.UpdateSessionNumberRequest;
import org.cotato.csquiz.api.session.dto.UpdateSessionImageOrderRequest;
import org.cotato.csquiz.api.session.dto.UpdateSessionRequest;
import org.cotato.csquiz.domain.generation.service.SessionImageService;
Expand Down Expand Up @@ -42,7 +41,7 @@ public class SessionController {
private final SessionImageService sessionImageService;

@Operation(summary = "세션 목록 반환 API")
@GetMapping("")
@GetMapping
public ResponseEntity<List<SessionListResponse>> findSessionsByGenerationId(@RequestParam Long generationId) {
return ResponseEntity.status(HttpStatus.OK).body(sessionService.findSessionsByGenerationId(generationId));
}
Expand All @@ -69,13 +68,6 @@ public ResponseEntity<Void> updateSession(@RequestBody @Valid UpdateSessionReque
return ResponseEntity.noContent().build();
}

@Operation(summary = "세션 숫자 변경 API")
@PatchMapping("/number")
public ResponseEntity<Void> updateSessionNumber(@RequestBody @Valid UpdateSessionNumberRequest request) {
sessionService.updateSessionNumber(request);
return ResponseEntity.noContent().build();
}

@Operation(summary = "세션 사진 순서 변경 API")
@PatchMapping("/image/order")
public ResponseEntity<Void> updateSessionImageOrder(@RequestBody UpdateSessionImageOrderRequest request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import org.cotato.csquiz.domain.generation.enums.CSEducation;
Expand All @@ -23,14 +23,15 @@ public record AddSessionRequest(
Double latitude,
Double longitude,
String placeName,
@Schema(description = "세션 날짜 및 시작 시간")
@NotNull
LocalDate sessionDate,
LocalDateTime sessionDateTime,

@Schema(example = "19:05:00")
@Schema(example = "19:10:00", description = "출석 마감 시간, 해당 시간 이후 지각 처리")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss")
LocalTime attendanceDeadLine,

@Schema(example = "19:20:00")
@Schema(example = "19:20:00", description = "지각 마감 시간, 해당 시간 이후 결석 처리")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss")
LocalTime lateDeadLine,

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.cotato.csquiz.api.session.dto;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import org.cotato.csquiz.domain.generation.embedded.SessionContents;
import org.cotato.csquiz.domain.generation.entity.Session;
Expand All @@ -14,7 +14,7 @@ public record SessionListResponse(
String description,
Long generationId,
String placeName,
LocalDate sessionDate,
LocalDateTime sessionDateTime,
SessionContents sessionContents
) {
public static SessionListResponse of(Session session, List<SessionImage> sessionImages) {
Expand All @@ -28,7 +28,7 @@ public static SessionListResponse of(Session session, List<SessionImage> session
session.getDescription(),
session.getGeneration().getId(),
session.getPlaceName(),
session.getSessionDate(),
session.getSessionDateTime(),
session.getSessionContents()
);
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import static org.cotato.csquiz.domain.attendance.enums.DeadLine.DEFAULT_ATTENDANCE_DEADLINE;
import static org.cotato.csquiz.domain.attendance.enums.DeadLine.DEFAULT_LATE_DEADLINE;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Objects;
import org.cotato.csquiz.api.attendance.dto.AttendanceDeadLineDto;
import org.cotato.csquiz.domain.attendance.embedded.Location;
Expand All @@ -19,7 +19,7 @@ public record UpdateSessionRequest(
String title,
String description,
@NotNull
LocalDate sessionDate,
LocalDateTime sessionDateTime,
String placeName,
Location location,
AttendanceDeadLineDto attendTime,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public enum ErrorCode {
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.BAD_REQUEST, "AT-401", "아직 출석 시간이 아닙니다."),
ATTENDANCE_NOT_OPEN(HttpStatus.BAD_REQUEST, "AT-401", "출석 시간이 아닙니다."),

// 500 오류 -> 서버측에서 처리가 실패한 부분들
WEBSOCKET_SEND_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, "S-001", "소캣 메세지 전송 실패"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,7 @@ public void closeAllCsQuiz() {
log.info("[ CS 퀴즈 모두 닫기 Scheduler 완료 ]");
}

// sessionDate 18시 50분에 출결을 구독 중인 부원들에게 출결 입력 시작 알림을 전송하는 스케줄러
public void scheduleSessionNotification(LocalDate sessionDate) {
LocalDateTime notificationTime = LocalDateTime.of(sessionDate, DeadLine.ATTENDANCE_START_TIME.getTime());

public void scheduleSessionNotification(LocalDateTime notificationTime) {
ZonedDateTime zonedDateTime = notificationTime.atZone(ZoneId.of("Asia/Seoul"));

taskScheduler.schedule(() -> sseSender.sendNotification(notificationTime), zonedDateTime.toInstant());
Expand Down
13 changes: 10 additions & 3 deletions src/main/java/org/cotato/csquiz/common/sse/SseSender.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import org.cotato.csquiz.domain.attendance.enums.AttendanceOpenStatus;
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.Component;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter.DataWithMediaType;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
Expand All @@ -23,7 +25,7 @@ public class SseSender {

private static final String ATTENDANCE_STATUS = "AttendanceStatus";
private final SseAttendanceRepository sseAttendanceRepository;

private final SessionRepository sessionRepository;
private final AttendanceRepository attendanceRepository;

public void sendInitialAttendanceStatus(SseEmitter sseEmitter) {
Expand All @@ -39,16 +41,21 @@ public void sendInitialAttendanceStatus(SseEmitter sseEmitter) {
return;
}

Attendance attendance = maybeAttendance.get();
Session session = sessionRepository.findById(attendance.getSessionId())
.orElseThrow(() -> new EntityNotFoundException("해당 출석에 대한 세션이 존재하지 않습니다."));

send(sseEmitter, SseEmitter.event()
.name(ATTENDANCE_STATUS)
.data(AttendanceStatusInfo.builder()
.attendanceId(maybeAttendance.get().getId())
.openStatus(AttendanceUtil.getAttendanceOpenStatus(maybeAttendance.get(), LocalDateTime.now()))
.openStatus(AttendanceUtil.getAttendanceOpenStatus(session.getSessionDateTime(), attendance,
LocalDateTime.now()))
.build())
.build());
}

// sessionDate 6시 50분에 출결을 구독 중인 부원들에게 출결 입력 시작 알림을 전송한다.
// sessionDateTime 7시에 출결을 구독 중인 부원들에게 출결 입력 시작 알림을 전송한다.
public void sendNotification(LocalDateTime notificationDate) {
Attendance attendance = attendanceRepository.findByAttendanceDeadLineDate(notificationDate)
.orElseThrow(() -> new EntityNotFoundException("해당 날짜에 진행하는 출석이 없습니다."));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ private Location(Double latitude, Double longitude) {
this.longitude = longitude;
}

public static Location location(Double latitude, Double longitude){
return new Location(latitude, longitude);
}

public Double calculateAccuracy(Location location) {
return Math.pow(this.latitude - location.latitude, 2) + Math.pow(this.longitude - location.longitude, 2);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public void updateLocationAccuracy(Double accuracy) {
this.locationAccuracy = accuracy;
}

public void updateAttendanceStatus(AttendanceResult attendanceResult) {
public void updateAttendanceResult(AttendanceResult attendanceResult) {
this.attendanceResult = attendanceResult;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.cotato.csquiz.domain.attendance.enums;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
Expand All @@ -10,20 +8,10 @@
@AllArgsConstructor
public enum DeadLine {

ATTENDANCE_START_TIME(LocalTime.of(18, 50, 0), "고정 출석 시작 시간"),
DEFAULT_ATTENDANCE_DEADLINE(LocalTime.of(19, 5, 0), "기본 출석 마감 시간"),
DEFAULT_LATE_DEADLINE(LocalTime.of(19,20,0),"기본 지각 마감 시간"),
ATTENDANCE_END_TIME(LocalTime.of(20, 0,0), "고정 세션 종료 시간")
;

private final LocalTime time;
private final String description;

public static LocalDateTime sessionStartTime(LocalDate date) {
return LocalDateTime.of(date, ATTENDANCE_START_TIME.getTime());
}

public static LocalDateTime sessionEndTime(LocalDate date) {
return LocalDateTime.of(date, ATTENDANCE_END_TIME.getTime());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.cotato.csquiz.domain.attendance.service;

import java.time.LocalDateTime;
import org.cotato.csquiz.api.attendance.dto.AttendResponse;
import org.cotato.csquiz.api.attendance.dto.AttendanceParams;
import org.cotato.csquiz.domain.attendance.entity.Attendance;
Expand All @@ -8,5 +9,5 @@
public interface AttendClient {
AttendanceType attendanceType();

AttendResponse request(AttendanceParams params, Long memberId, Attendance attendance);
AttendResponse request(AttendanceParams params, LocalDateTime sessionStartTime, Long memberId, Attendance attendance);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import jakarta.persistence.EntityNotFoundException;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -26,23 +27,19 @@
@Slf4j
public class AttendanceAdminService {

private static final int DEFAULT_ATTEND_SECOND = 59;
private final AttendanceRepository attendanceRepository;
private final AttendanceRecordService attendanceRecordService;
private final SessionRepository sessionRepository;

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

Attendance attendance = Attendance.builder()
.session(session)
.location(location)
.attendanceDeadLine(LocalDateTime.of(session.getSessionDate(), attendanceDeadLine.attendanceDeadLine())
.plusSeconds(DEFAULT_ATTEND_SECOND))
.lateDeadLine(LocalDateTime.of(session.getSessionDate(), attendanceDeadLine.lateDeadLine())
.plusSeconds(DEFAULT_ATTEND_SECOND))
.attendanceDeadLine(LocalDateTime.of(session.getSessionDateTime().toLocalDate(), attendanceDeadline))
.lateDeadLine(LocalDateTime.of(session.getSessionDateTime().toLocalDate(), lateDeadline))
.build();

attendanceRepository.save(attendance);
Expand All @@ -55,35 +52,32 @@ public void updateAttendanceByAttendanceId(UpdateAttendanceRequest request) {
Session attendanceSession = sessionRepository.findById(attendance.getSessionId())
.orElseThrow(() -> new EntityNotFoundException("출석과 연결된 세션을 찾을 수 없습니다"));

updateAttendance(attendanceSession, attendance, request
.attendTime(), request.location());
updateAttendance(attendanceSession, attendance, request.attendTime(), request.location());
}

@Transactional
public void updateAttendance(Session attendanceSession, Attendance attendance,
AttendanceDeadLineDto attendanceDeadLine, Location location) {
AttendanceUtil.validateAttendanceTime(attendanceDeadLine.attendanceDeadLine(),
AttendanceUtil.validateAttendanceTime(attendanceSession.getSessionDateTime(), attendanceDeadLine.attendanceDeadLine(),
attendanceDeadLine.lateDeadLine());

if (attendanceSession.getSessionDate() == null) {
// 세션 날짜가 존재하지 않는 경우 예외 발생
if (attendanceSession.getSessionDateTime() == null) {
throw new AppException(ErrorCode.SESSION_DATE_NOT_FOUND);
}

attendance.updateDeadLine(
LocalDateTime.of(attendanceSession.getSessionDate(), attendanceDeadLine.attendanceDeadLine())
.plusSeconds(DEFAULT_ATTEND_SECOND),
LocalDateTime.of(attendanceSession.getSessionDate(), attendanceDeadLine.lateDeadLine())
.plusSeconds(DEFAULT_ATTEND_SECOND));
attendance.updateDeadLine(LocalDateTime.of(attendanceSession.getSessionDateTime().toLocalDate(), attendanceDeadLine.attendanceDeadLine()),
LocalDateTime.of(attendanceSession.getSessionDateTime().toLocalDate(), attendanceDeadLine.lateDeadLine()));
attendance.updateLocation(location);

attendanceRecordService.updateAttendanceStatus(attendance);
attendanceRecordService.updateAttendanceStatus(attendanceSession.getSessionDateTime(), attendance);
}

public List<AttendanceRecordResponse> findAttendanceRecords(Long generationId, Integer month) {
List<Session> sessions = sessionRepository.findAllByGenerationId(generationId);
if (month != null) {
sessions = sessions.stream()
.filter(session -> session.getSessionDate().getMonthValue() == month)
.filter(session -> session.getSessionDateTime().getMonthValue() == month)
.toList();
}
List<Long> sessionIds = sessions.stream()
Expand Down
Loading
Loading