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 @@ -6,6 +6,7 @@
import com.mos.backend.attendances.entity.AttendanceStatus;
import com.mos.backend.attendances.entity.exception.AttendanceErrorCode;
import com.mos.backend.attendances.infrastructure.AttendanceRepository;
import com.mos.backend.attendances.presentation.req.AttendanceUpdateReq;
import com.mos.backend.common.exception.MosException;
import com.mos.backend.common.infrastructure.EntityFacade;
import com.mos.backend.studies.entity.Study;
Expand All @@ -16,6 +17,7 @@
import com.mos.backend.studyschedules.entity.StudySchedule;
import com.mos.backend.studyschedules.entity.exception.StudyScheduleErrorCode;
import com.mos.backend.studyschedules.infrastructure.StudyScheduleRepository;
import com.mos.backend.studysettings.entity.StudySettings;
import com.mos.backend.users.entity.User;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -38,6 +40,7 @@ public class AttendanceService {
public void create(Long userId, Long studyId, Long studyScheduleId) {
User user = entityFacade.getUser(userId);
Study study = entityFacade.getStudy(studyId);
StudySettings studySettings = entityFacade.getStudySettings(studyId);
StudySchedule studySchedule = entityFacade.getStudySchedule(studyScheduleId);

validateRelation(study, studySchedule);
Expand All @@ -48,18 +51,15 @@ public void create(Long userId, Long studyId, Long studyScheduleId) {

validateAlreadyExist(studySchedule, studyMember);

if (studySchedule.isBeforePresentTime())
throw new MosException(AttendanceErrorCode.NOT_PRESENT_TIME);

Attendance attendance = studySchedule.isPresentTime()
? Attendance.createPresentAttendance(studySchedule, studyMember)
: Attendance.createLateAttendance(studySchedule, studyMember);
Attendance attendance = Attendance.createWithThreshold(
studySchedule, studyMember, studySettings.getLateThresholdMinutes(), studySettings.getAbsenceThresholdMinutes()
);

attendanceRepository.save(attendance);
}

@Transactional
public void update(Long userId, Long studyId, Long studyScheduleId, String attendanceStatusDescription) {
public void update(Long userId, Long studyId, Long studyScheduleId, AttendanceUpdateReq req) {
User user = entityFacade.getUser(userId);
Study study = entityFacade.getStudy(studyId);
StudySchedule studySchedule = entityFacade.getStudySchedule(studyScheduleId);
Expand All @@ -70,7 +70,7 @@ public void update(Long userId, Long studyId, Long studyScheduleId, String atten
StudyMember studyMember = studyMemberRepository.findByUserIdAndStudyId(user.getId(), study.getId())
.orElseThrow(() -> new MosException(StudyMemberErrorCode.STUDY_MEMBER_NOT_FOUND));

AttendanceStatus attendanceStatus = AttendanceStatus.fromDescription(attendanceStatusDescription);
AttendanceStatus attendanceStatus = req.getAttendanceStatus();

if (!attendanceStatus.isModifiable())
throw new MosException(AttendanceErrorCode.UNMODIFIABLE_STATUS);
Expand Down
45 changes: 28 additions & 17 deletions src/main/java/com/mos/backend/attendances/entity/Attendance.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
package com.mos.backend.attendances.entity;

import com.mos.backend.attendances.entity.exception.AttendanceErrorCode;
import com.mos.backend.common.entity.BaseTimeEntity;
import com.mos.backend.common.exception.MosException;
import com.mos.backend.studymembers.entity.StudyMember;
import com.mos.backend.studyschedules.entity.StudySchedule;
import jakarta.persistence.*;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.OnDelete;

import java.time.LocalDateTime;

import static org.hibernate.annotations.OnDeleteAction.CASCADE;

@Entity
Expand All @@ -23,7 +35,7 @@ public class Attendance extends BaseTimeEntity {
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "study_id", nullable = false)
@JoinColumn(name = "study_schedule_id", nullable = false)
@OnDelete(action = CASCADE)
private StudySchedule studySchedule;

Expand All @@ -35,28 +47,27 @@ public class Attendance extends BaseTimeEntity {
@Column(nullable = false)
private AttendanceStatus attendanceStatus;

public static Attendance createPresentAttendance(StudySchedule studySchedule, StudyMember studyMember) {
public static Attendance create(StudySchedule studySchedule, StudyMember studyMember, AttendanceStatus attendanceStatus) {
Attendance attendance = new Attendance();
attendance.studySchedule = studySchedule;
attendance.studyMember = studyMember;
attendance.attendanceStatus = AttendanceStatus.PRESENT;
attendance.attendanceStatus = attendanceStatus;
return attendance;
}

public static Attendance createLateAttendance(StudySchedule studySchedule, StudyMember studyMember) {
Attendance attendance = new Attendance();
attendance.studySchedule = studySchedule;
attendance.studyMember = studyMember;
attendance.attendanceStatus = AttendanceStatus.LATE;
return attendance;
}
public static Attendance createWithThreshold(StudySchedule studySchedule, StudyMember studyMember, Integer lateThresholdMinutes, Integer absenceThresholdMinutes) {
LocalDateTime now = LocalDateTime.now();

public static Attendance create(StudySchedule studySchedule, StudyMember studyMember, AttendanceStatus attendanceStatus) {
Attendance attendance = new Attendance();
attendance.studySchedule = studySchedule;
attendance.studyMember = studyMember;
attendance.attendanceStatus = attendanceStatus;
return attendance;
if (now.isBefore(studySchedule.getStartDateTime().minusMinutes(lateThresholdMinutes))) {
throw new MosException(AttendanceErrorCode.NOT_PRESENT_TIME);
}
if (now.isAfter(studySchedule.getEndDateTime().plusMinutes(absenceThresholdMinutes))) {
return create(studySchedule, studyMember, AttendanceStatus.UNEXCUSED_ABSENCE);
}
if (now.isAfter(studySchedule.getStartDateTime().plusMinutes(lateThresholdMinutes))) {
return create(studySchedule, studyMember, AttendanceStatus.LATE);
}
return create(studySchedule, studyMember, AttendanceStatus.PRESENT);
}

public void leaveEarly() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.mos.backend.attendances.application.AttendanceService;
import com.mos.backend.attendances.application.res.StudyMemberAttendanceRes;
import com.mos.backend.attendances.application.res.StudyScheduleAttendanceRateRes;
import com.mos.backend.attendances.presentation.req.AttendanceUpdateReq;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
Expand All @@ -28,8 +29,8 @@ public void create(@AuthenticationPrincipal Long userId,
public void update(@AuthenticationPrincipal Long userId,
@PathVariable Long studyId,
@PathVariable Long studyScheduleId,
@RequestBody String attendanceStatus) {
attendanceService.update(userId, studyId, studyScheduleId, attendanceStatus);
@RequestBody AttendanceUpdateReq attendanceUpdateReq) {
attendanceService.update(userId, studyId, studyScheduleId, attendanceUpdateReq);
}

@ResponseStatus(HttpStatus.OK)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.mos.backend.attendances.presentation.req;

import com.mos.backend.attendances.entity.AttendanceStatus;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class AttendanceUpdateReq {
private AttendanceStatus attendanceStatus;
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
import com.mos.backend.studyschedules.entity.StudySchedule;
import com.mos.backend.studyschedules.entity.exception.StudyScheduleErrorCode;
import com.mos.backend.studyschedules.infrastructure.StudyScheduleRepository;
import com.mos.backend.studysettings.entity.StudySettings;
import com.mos.backend.studysettings.entity.exception.StudySettingsErrorCode;
import com.mos.backend.studysettings.infrastructure.StudySettingRepository;
import com.mos.backend.users.entity.User;
import com.mos.backend.users.entity.exception.UserErrorCode;
import com.mos.backend.users.infrastructure.respository.UserRepository;
Expand Down Expand Up @@ -73,6 +76,7 @@ public class EntityFacade {
private final PrivateChatRoomMemberRepository privateChatRoomMemberRepository;
private final StudyChatRoomRepository studyChatRoomRepository;
private final UserStudySettingRepository userStudySettingRepository;
private final StudySettingRepository studySettingRepository;

public User getUser(Long userId) {
return userRepository.findById(userId)
Expand Down Expand Up @@ -156,4 +160,9 @@ public PrivateChatRoomMember getPrivateChatRoomMember(Long userId, Long privateC
return privateChatRoomMemberRepository.findByUserIdAndPrivateChatRoomId(userId, privateChatRoomId)
.orElseThrow(() -> new MosException(PrivateChatRoomMemberErrorCode.NOT_FOUND));
}

public StudySettings getStudySettings(Long studyId) {
return studySettingRepository.findByStudyId(studyId)
.orElseThrow(() -> new MosException(StudySettingsErrorCode.NOT_FOUND));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.mos.backend.studyschedules.entity.StudySchedule;
import com.mos.backend.studyschedules.entity.exception.StudyScheduleErrorCode;
import com.mos.backend.studyschedules.infrastructure.StudyScheduleRepository;
import com.mos.backend.studyschedules.infrastructure.dto.StudyScheduleWithAttendanceDto;
import com.mos.backend.studyschedules.presentation.req.StudyScheduleCreateReq;
import com.mos.backend.studyschedules.presentation.req.StudyScheduleUpdateReq;
import com.mos.backend.users.entity.User;
Expand Down Expand Up @@ -56,29 +57,38 @@ private static void validateEndDateTime(LocalDateTime startDateTime, LocalDateTi
public List<StudyScheduleRes> getMyStudySchedules(Long userId) {
User user = entityFacade.getUser(userId);

List<StudySchedule> studySchedules = studyScheduleRepository.findAllByActivatedUserId(user.getId());
List<StudyScheduleWithAttendanceDto> studySchedules = studyScheduleRepository.findAllByUserIdAndActivated(user.getId());
return convertToRes(studySchedules);
}

@PreAuthorize("@studySecurity.isMemberOrAdmin(#studyId)")
@Transactional(readOnly = true)
public List<StudyScheduleRes> getStudySchedules(Long studyId) {
public List<StudyScheduleRes> getStudySchedules(Long userId, Long studyId) {
Study study = entityFacade.getStudy(studyId);

List<StudySchedule> studySchedules = studyScheduleRepository.findByStudyId(study.getId());
List<StudyScheduleWithAttendanceDto> studySchedules = studyScheduleRepository.findByStudyIdWithAttendance(userId, study.getId());
return convertToRes(studySchedules);
}

private List<StudyScheduleRes> convertToRes(List<StudySchedule> studySchedules) {
return studySchedules.stream().map(studySchedule -> {
List<StudyCurriculumRes> studyCurriculumResList = studyCurriculumRepository.findAllByStudyScheduleId(studySchedule.getId()).stream()
.map(StudyCurriculumRes::from)
.toList();
return StudyScheduleRes.of(studySchedule, studyCurriculumResList);
}).toList();
private List<StudyScheduleRes> convertToRes(List<StudyScheduleWithAttendanceDto> dtos) {
return dtos.stream().map(dto -> {
List<StudyCurriculumRes> studyCurriculumResList = studyCurriculumRepository.findAllByStudyScheduleId(dto.getStudyScheduleId()).stream()
.map(StudyCurriculumRes::from)
.toList();
return StudyScheduleRes.of(
dto.getStudyScheduleId(),
dto.getTitle(),
dto.getDescription(),
dto.getStartDateTime(),
dto.getEndDateTime(),
dto.getStudyId(),
Objects.isNull(dto.getAttendanceStatus()) ? null : dto.getAttendanceStatus().getDescription(),
studyCurriculumResList
);
})
.toList();
}


@PreAuthorize("@studySecurity.isLeaderOrAdmin(#studyId)")
@Transactional
public void updateStudySchedule(Long studyId, Long studyScheduleId, StudyScheduleUpdateReq req) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.mos.backend.studyschedules.application.res;

import com.mos.backend.studyschedules.entity.StudySchedule;
import lombok.AllArgsConstructor;
import lombok.Getter;

Expand All @@ -18,16 +17,26 @@ public class StudyScheduleRes {

private Long studyId;

private String attendanceStatusDescription;

private List<StudyCurriculumRes> studyCurriculumResList;

public static StudyScheduleRes of(StudySchedule studySchedule, List<StudyCurriculumRes> studyCurriculumList) {
public static StudyScheduleRes of(Long studyScheduleId,
String title,
String description,
LocalDateTime startDateTime,
LocalDateTime endDateTime,
Long studyId,
String attendanceStatusDescription,
List<StudyCurriculumRes> studyCurriculumList) {
return new StudyScheduleRes(
studySchedule.getId(),
studySchedule.getTitle(),
studySchedule.getDescription(),
studySchedule.getStartDateTime(),
studySchedule.getEndDateTime(),
studySchedule.getStudy().getId(),
studyScheduleId,
title,
description,
startDateTime,
endDateTime,
studyId,
attendanceStatusDescription,
studyCurriculumList
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

import com.mos.backend.common.entity.BaseAuditableEntity;
import com.mos.backend.studies.entity.Study;
import jakarta.persistence.*;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -19,8 +26,6 @@
@Table(name = "study_schedules")
public class StudySchedule extends BaseAuditableEntity {

private static final int PRESENT_TIME = 10;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "study_schedule_id")
Expand Down Expand Up @@ -63,17 +68,4 @@ public void update(String title, String description, LocalDateTime startDateTime
this.startDateTime = startDateTime;
this.endDateTime = endDateTime;
}

public void complete() {
this.isCompleted = true;
}

public boolean isPresentTime() {
return LocalDateTime.now().isAfter(startDateTime.minusMinutes(PRESENT_TIME))
&& LocalDateTime.now().isBefore(startDateTime.plusMinutes(PRESENT_TIME));
}

public boolean isBeforePresentTime() {
return LocalDateTime.now().isBefore(startDateTime.minusMinutes(PRESENT_TIME));
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
package com.mos.backend.studyschedules.infrastructure;

import com.mos.backend.studyschedules.entity.StudySchedule;
import com.mos.backend.studyschedules.infrastructure.dto.StudyScheduleWithAttendanceDto;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

public interface StudyScheduleJpaRepository extends JpaRepository<StudySchedule, Long> {
List<StudySchedule> findByStudyId(Long studyId);
@Query("""
SELECT new com.mos.backend.studyschedules.infrastructure.dto.StudyScheduleWithAttendanceDto(
ss.id, ss.title, ss.description, ss.startDateTime, ss.endDateTime, ss.isCompleted, ss.study.id, a.attendanceStatus
)
FROM StudySchedule ss
LEFT JOIN Attendance a ON a.studySchedule.id = ss.id AND a.studyMember.user.id = :userId
WHERE ss.study.id = :studyId
""")
List<StudyScheduleWithAttendanceDto> findByStudyIdWithAttendance(Long userId, Long studyId);

@Query("""
SELECT ss
SELECT new com.mos.backend.studyschedules.infrastructure.dto.StudyScheduleWithAttendanceDto(
ss.id, ss.title, ss.description, ss.startDateTime, ss.endDateTime, ss.isCompleted, ss.study.id, a.attendanceStatus
)
FROM StudySchedule ss
JOIN ss.study s
JOIN StudyMember sm ON sm.study = s
LEFT JOIN Attendance a ON a.studySchedule.id = ss.id AND a.studyMember.user.id = :userId
WHERE sm.user.id = :userId AND sm.status = 'ACTIVATED'
""")
List<StudySchedule> findAllByActivatedUserId(Long userId);
List<StudyScheduleWithAttendanceDto> findAllByUserIdAndActivated(Long userId);

List<StudySchedule> findByStudyId(Long studyId);
}
Loading