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
@@ -0,0 +1,23 @@
package com.example.Piroin.project.domain.attendance.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;

import java.time.LocalDate;
import java.util.List;

@Getter
@Setter
@Schema(description = "요일별 출석 상태")
public class AttendanceDayStatusRes {

@Schema(description = "출석 날짜", example = "2026-06-23")
private LocalDate date;

@Schema(description = "요일", example = "TUESDAY")
private String day;

@Schema(description = "출석 차시별 상태 목록")
private List<AttendanceSlotRes> slots;
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@

@Getter
@Setter
@Schema(description = "사용자 출석 상태")
@Schema(description = "사용자 주차별 출석 상태")
public class AttendanceStatusRes {
@Schema(description = "출석 날짜", example = "2025-06-24")
private LocalDate date;

@Schema(description = "주차", example = "1")
private int week;

@Schema(description = "출석 차시별 상태 목록")
private List<AttendanceSlotRes> slots;
}
@Schema(description = "요일별 출석 상태 목록")
private List<AttendanceDayStatusRes> days;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.example.Piroin.project.domain.attendance.repository;

import com.example.Piroin.project.domain.attendance.entity.AttendanceCode;
import com.example.Piroin.project.domain.curriculum.entity.StudySession;
import com.example.Piroin.project.domain.user.entity.User;
import com.example.Piroin.project.domain.attendance.entity.Attendance;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -20,7 +21,6 @@ public interface AttendanceRepository extends JpaRepository<Attendance, Long> {
// 연관관계 필드명이 attendanceCode 라면 내부 ID인 Id를 조합하여 명명
Optional<Attendance> findByUserIdAndAttendanceCodeId(Long userId, Long attendanceCodeId);

//List<Attendance> findByUserIdAndStudySessionSessionDate(Integer userId, LocalDate date);

int countByUserAndStatusFalse(User user);

Expand Down Expand Up @@ -54,6 +54,7 @@ Optional<Attendance> findByUserIdAndAttendanceCodeId(

List<Attendance> findByAttendanceCodeId(Integer id);


// 특정 날짜에 발급된 출석 코드의 개수를 세는 메서드
//long countByAttendanceDate(String attendanceDate);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.example.Piroin.project.domain.attendance.service;

import com.example.Piroin.project.domain.assignment.repository.AssignmentRepository;
import com.example.Piroin.project.domain.curriculum.entity.StudySession;
import com.example.Piroin.project.domain.curriculum.exception.CurriculumException;
import com.example.Piroin.project.domain.curriculum.exception.code.CurriculumErrorCode;
import com.example.Piroin.project.domain.curriculum.repository.CurriculumRepository;
import com.example.Piroin.project.domain.deposit.entity.Deposit;
import com.example.Piroin.project.domain.deposit.repository.DepositRepository;
Expand All @@ -23,13 +26,11 @@
import com.example.Piroin.project.domain.assignment.repository.AssignmentItemRepository;
import com.example.Piroin.project.domain.attendance.dto.UpdateUserStatusReq;
import com.example.Piroin.project.domain.curriculum.enums.SessionDayPart;
import com.example.Piroin.project.domain.attendance.dto.AttendanceDayStatusRes;


import java.time.LocalDate;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

Expand All @@ -41,27 +42,29 @@ public class AttendanceService {
private final AttendanceCodeRepository attendanceCodeRepository;
private final UserRepository userRepository;
private final DepositService depositService;

private final CurriculumRepository curriculumRepository;

private final AssignmentItemRepository assignmentItemRepository;



// 1. 출석 시작 코드 (출석코드 생성 함수)
@Transactional
public AttendanceCode generateCodeAndCreateAttendances(LocalDate date) { // [수정] 세션 ID 대신 날짜를 직접 받음

// 1. [삭제] 더 이상 세션을 조회해서 날짜를 파싱할 필요가 없습니다. (curriculumRepository 조회 제거)
// 1-1) 해당 날짜에 커리큘럼이 있는지 확인
if (!curriculumRepository.existsBySessionDate(date)) {
throw new CurriculumException(
CurriculumErrorCode.ATTENDANCE_DATE_NOT_AVAILABLE
);
}

// 2. 해당 날짜에 생성된 출석 코드 개수 조회
// 1-2) 해당 날짜에 생성된 출석 코드 개수 조회.
long codeCountOfDay = attendanceCodeRepository.countByAttendanceDate(date);

if (codeCountOfDay >= 3) {
throw new IllegalStateException("하루에 최대 3회까지만 출석 코드를 생성할 수 있습니다.");
}

// 3. 기존 활성화된 코드들 만료 처리
// 1-3) 기존 활성화된 코드들 만료 처리
List<AttendanceCode> activeCodes = attendanceCodeRepository.findByIsExpiredFalse();
for (AttendanceCode activeCode : activeCodes) {
activeCode.expire();
Expand All @@ -79,11 +82,11 @@ public AttendanceCode generateCodeAndCreateAttendances(LocalDate date) { // [수
}


// 4. 4자리 랜덤 코드 생성 및 차수(Order) 계산
// 1-4) 4자리 랜덤 코드 생성 및 차수(Order) 계산
String code = String.valueOf(ThreadLocalRandom.current().nextInt(1000, 10000));
String attendanceOrder = String.valueOf(codeCountOfDay + 1); // 1회차, 2회차, 3회차

// 5. 새로운 AttendanceCode 생성 및 저장
// 1-5) 새로운 AttendanceCode 생성 및 저장
AttendanceCode attendanceCode = AttendanceCode.builder()
.attendanceDate(date) // [수정] 파라미터로 받은 날짜 주입
.attendanceOrder(attendanceOrder)
Expand All @@ -93,11 +96,10 @@ public AttendanceCode generateCodeAndCreateAttendances(LocalDate date) { // [수

attendanceCodeRepository.save(attendanceCode);

// 6. 모든 MEMBER 유저에 대해 '현재 생성된 출석 코드' 기준 초기 출석 데이터 생성
// 1-6) 모든 MEMBER 유저에 대해 '현재 생성된 출석 코드' 기준 초기 출석 데이터 생성
List<User> users = userRepository.findByRole(Role.MEMBER);

for (User user : users) {
// [확인] 이미 완벽하게 studySession 대신 attendanceCode를 주입하도록 잘 짜두셨습니다!
Attendance attendance = Attendance.builder()
.user(user)
.attendanceCode(attendanceCode)
Expand Down Expand Up @@ -233,103 +235,92 @@ public List<AttendanceSlotRes> findByUserIdAndDate(Integer userId, LocalDate dat
.toList();
}

// 6. 유저의 전체 출석 현황을 날짜별로 묶어서 조회하는 함수
// 6. 나의 전체 출석 현황 조회 서비스
public List<AttendanceStatusRes> findByUserId(Integer userId) {

List<Attendance> attendances =
attendanceRepository.findByUserId(Long.valueOf(userId));

// LocalDate 기준으로 그룹화
Map<LocalDate, List<Attendance>> grouped = attendances.stream()
.collect(Collectors.groupingBy(
attendance -> attendance.getAttendanceCode().getAttendanceDate()
));
// 날짜별 그룹화
Map<LocalDate, List<Attendance>> dateGrouped =
attendances.stream()
.collect(Collectors.groupingBy(
attendance ->
attendance.getAttendanceCode().getAttendanceDate()
));

// 주차별 그룹화
Map<Integer, List<AttendanceDayStatusRes>> weekGrouped =
new HashMap<>();

for (Map.Entry<LocalDate, List<Attendance>> entry : dateGrouped.entrySet()) {

LocalDate date = entry.getKey();

StudySession studySession =
curriculumRepository
.findFirstBySessionDate(date)
.orElseThrow(() ->
new RuntimeException("세션이 존재하지 않습니다.")
);

int week = studySession.getWeek().intValue();

List<AttendanceSlotRes> slots =
entry.getValue().stream()
.map(attendance ->
new AttendanceSlotRes(
attendance.getAttendanceCode().getId(),
attendance.getStatus()
)
)
.sorted(
Comparator.comparing(
AttendanceSlotRes::getAttendanceCodeId
)
)
.toList();

AttendanceDayStatusRes dayRes = new AttendanceDayStatusRes();
dayRes.setDate(date);
dayRes.setDay(date.getDayOfWeek().toString());
dayRes.setSlots(slots);

return grouped.entrySet().stream()

weekGrouped
.computeIfAbsent(week, k -> new ArrayList<>())
.add(dayRes);
}

return weekGrouped.entrySet().stream()
.map(entry -> {

LocalDate date = entry.getKey();
AttendanceStatusRes dto =
new AttendanceStatusRes();

List<AttendanceSlotRes> slots = entry.getValue().stream()
.map(attendance -> new AttendanceSlotRes(
attendance.getAttendanceCode().getId(),
attendance.getStatus()
))
.sorted(Comparator.comparing(AttendanceSlotRes::getAttendanceCodeId))
.toList();
dto.setWeek(entry.getKey());

AttendanceStatusRes dto = new AttendanceStatusRes();
dto.setDate(date);
dto.setSlots(slots);
dto.setDays(
entry.getValue().stream()
.sorted(
Comparator.comparing(
AttendanceDayStatusRes::getDate
)
)
.toList()
);

return dto;
})
.sorted(Comparator.comparing(AttendanceStatusRes::getDate).reversed())
.sorted(
Comparator.comparing(
AttendanceStatusRes::getWeek
)
)
.toList();
}
//
// // 6. 유저 상태 변경 (관리자)
// // 컨트롤러 부분은 출석만 받는데 여기는 출석&과제 둘 다 받아서 추후에 수정 예정
// @Transactional
// public boolean updateUserStatus(Integer userId, UpdateUserStatusReq req) {
// boolean updated = false;
//
// // 출석 상태 변경 코드
// if (req.getAttendanceId() != null && req.getAttendanceStatus() != null) {
// Attendance attendance = attendanceRepository.findById(req.getAttendanceId())
// .orElseThrow(() -> new IllegalArgumentException("출석 기록을 찾을 수 없습니다."));
//
// if (!attendance.getUser().getId().equals(userId)) {
// throw new IllegalArgumentException("요청된 사용자와 출석 기록의 사용자가 일치하지 않습니다.");
// }
//
// attendance.updateStatus(req.getAttendanceStatus());
// updated = true;
// }
//
// // 과제 상태 변경 코드
// if (req.getAssignmentItemId() != null && req.getAssignmentStatus() != null) {
// AssignmentItem assignmentItem = assignmentItemRepository.findById(Math.toIntExact(req.getAssignmentItemId()))
// .orElseThrow(() -> new IllegalArgumentException("과제 기록을 찾을 수 없습니다."));
//
// if (!assignmentItem.getUser().getId().equals(userId)) {
// throw new IllegalArgumentException("요청된 사용자와 과제 기록의 사용자가 일치하지 않습니다.");
// }
//
// assignmentItem.updateSubmitted(req.getAssignmentStatus());
// updated = true;
// }
//
// // 출석 변경 → 보증금 재계산 (과제 변경도 포함이 되어 있나..?)
// if (updated) {
// depositService.recalculateDeposit(Long.valueOf(userId));
// }
//
// return updated;
// }


}

/*
// 관리자가 유저의 출석 상태를 변경하는 함수(나중에 과제까지 같이 변경되도록 수정할 것)
@Transactional
public boolean updateAttendanceStatus(Long attendanceId, boolean status) {
Optional<Attendance> attendanceOpt = attendanceRepository.findById(attendanceId);

if (attendanceOpt.isEmpty()) {
return false;
}

// 출석 상태 변경
Attendance attendance = attendanceOpt.get();
attendance.setStatus(status);
attendanceRepository.save(attendance);

// 출석 변경 → 보증금 재계산
depositService.recalculateDeposit(attendance.getUser().getId());

return true;
}
}

*/
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ public enum CurriculumErrorCode {
HttpStatus.BAD_REQUEST,
"CURRICULUM405",
"해당 주차/요일의 세션이 존재하지 않습니다. 세션을 먼저 생성해주세요."
),

ATTENDANCE_DATE_NOT_AVAILABLE(
HttpStatus.BAD_REQUEST,
"CURRICULUM406",
"해당 날짜는 세션 진행일이 아닙니다. 세션 일정 또는 커리큘럼을 확인해주세요."
);

private final HttpStatus status;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

/*
StudySession(세션) DB 접근 인터페이스
Expand All @@ -25,6 +26,11 @@ public interface CurriculumRepository extends JpaRepository<StudySession, Long>

List<StudySession> findByWeekOrderBySessionDateAsc(Long week);

boolean existsBySessionDate(LocalDate sessionDate);

Optional<StudySession> findFirstBySessionDate(LocalDate sessionDate);


// @Query("""
// SELECT s
// FROM StudySession s
Expand Down
Loading