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 @@ -27,6 +27,19 @@ public interface AttendanceRepository extends JpaRepository<Attendance, Long> {
// 1. 특정 출석 코드 ID에 해당하는 결석 데이터 조회
List<Attendance> findByAttendanceCodeIdAndStatusFalse(Integer attendanceCodeId);

// 이해도 체크 분모용 출석 인원. 세션 날짜 + 회차에서 실제 출석 완료(status=true)된 인원만 센다.
@Query("""
SELECT COUNT(a)
FROM Attendance a
WHERE a.attendanceCode.attendanceDate = :attendanceDate
AND a.attendanceCode.attendanceOrder = :attendanceOrder
AND a.status = true
""")
long countAttendedByDateAndOrder(
@Param("attendanceDate") LocalDate attendanceDate,
@Param("attendanceOrder") String attendanceOrder
);

// 2. 특정 유저 ID와 출석 코드의 날짜 조건으로 조회 (엔티티 그래프 참조: attendanceCode.attendanceDate)
@Query("SELECT a FROM Attendance a WHERE a.user.id = :userId AND a.attendanceCode.attendanceDate = :attendanceDate")
List<Attendance> findByUserIdAndDate(@Param("userId") Integer userId, @Param("attendanceDate") LocalDate attendanceDate);
Expand All @@ -47,5 +60,3 @@ Optional<Attendance> findByUserIdAndAttendanceCodeId(
// 현재 만료되지 않은(활성화된) 출석 코드 목록을 가져오는 메서드
//List<AttendanceCode> findByIsExpiredFalse();
}


Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.example.Piroin.project.domain.assignment.entity.AssignmentItem;
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 java.time.LocalDate;
Expand Down Expand Up @@ -116,6 +117,34 @@ public Optional<AttendanceCode> getActiveAttendanceCode() {
return attendanceCodeRepository.findFirstByIsExpiredFalseOrderByIdDesc();
}

// Q&A 이해도 체크 화면의 분모(13/29 중 29)를 계산한다.
@Transactional(readOnly = true)
public int countAttendedBySession(StudySession session) {
if (session == null) {
throw new IllegalArgumentException("세션 정보는 필수입니다.");
}

String attendanceOrder = resolveAttendanceOrder(session.getDayPart());
long attendedCount = attendanceRepository.countAttendedByDateAndOrder(
session.getSessionDate(),
attendanceOrder
);

return Math.toIntExact(attendedCount);
}

// 현재 정책: 오전 세션은 1회차, 오후 세션은 2회차 출석 인원을 이해도 체크 분모로 사용한다.
private String resolveAttendanceOrder(SessionDayPart dayPart) {
if (dayPart == null) {
throw new IllegalArgumentException("세션 오전/오후 정보는 필수입니다.");
}

return switch (dayPart) {
case AM -> "1";
case PM -> "2";
};
}

// 3. 출석 체크
@Transactional
public AttendanceMarkResponse markAttendance(Long userId, String inputCode) {
Expand Down Expand Up @@ -304,4 +333,3 @@ public boolean updateAttendanceStatus(Long attendanceId, boolean status) {
}

*/

Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,17 @@ public record UnderstandingSliceResponse(
) {
}

// 질문방 이해도 체크 바 응답. 프론트는 이 값들로 "이해했다 (13/29)"와 오른쪽 O/X 숫자를 그린다.
public record UnderstandingCheckResponse(
Long checkId,
String content,
// 화면의 "13/29" 중 13: O 응답 수 + X 응답 수
Integer respondedCount,
// 화면의 "13/29" 중 29: 해당 세션에 대응되는 출석 회차의 출석 인원
Integer attendanceCount,
// 오른쪽 O 뱃지 숫자
Integer understoodCount,
// 오른쪽 X 뱃지 숫자
Integer notUnderstoodCount,
LocalDateTime createdAt
) {
Expand Down Expand Up @@ -173,18 +180,32 @@ public record CommentCreatedEvent(
) {
}

// O/X 클릭 직후 응답. selectedChoice가 null이면 같은 선택지를 다시 눌러 취소된 상태다.
public record UnderstandingResponseResult(
Long checkId,
UnderstandResChoice selectedChoice,
// 화면의 "13/29" 중 13: O 응답 수 + X 응답 수
Integer respondedCount,
// 화면의 "13/29" 중 29: 해당 세션에 대응되는 출석 회차의 출석 인원
Integer attendanceCount,
// 오른쪽 O 뱃지 숫자
Integer understoodCount,
// 오른쪽 X 뱃지 숫자
Integer notUnderstoodCount
) {
}

// 운영진이 이해도 체크를 생성했을 때의 초기 응답. 생성 직후에는 O/X 응답자가 없어서 카운트가 0이다.
public record UnderstandingCheckCreateResponse(
Long checkId,
String content,
// 생성 직후에는 0
Integer respondedCount,
// 생성 응답에서는 질문방 조회 맥락이 아니므로 null로 내려간다.
Integer attendanceCount,
// 생성 직후에는 0
Integer understoodCount,
// 생성 직후에는 0
Integer notUnderstoodCount,
LocalDateTime createdAt
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.Piroin.project.domain.question.service;

import com.example.Piroin.project.domain.attendance.service.AttendanceService;
import com.example.Piroin.project.domain.curriculum.entity.StudySession;
import com.example.Piroin.project.domain.curriculum.repository.CurriculumRepository;
import com.example.Piroin.project.domain.question.dto.QuestionReqDTO;
Expand Down Expand Up @@ -44,6 +45,7 @@ public class QuestionService {
private final CurriculumRepository curriculumRepository;
private final UserRepository userRepository;
private final QuestionEventService questionEventService;
private final AttendanceService attendanceService;

// 질문 방 조회
@Transactional(readOnly = true)
Expand Down Expand Up @@ -331,7 +333,7 @@ public QuestionResDTO.UnderstandingCheckCreateResponse createUnderstandingCheck(
.build());

return new QuestionResDTO.UnderstandingCheckCreateResponse(
check.getId(), check.getTitle(), 0, 0, check.getCreatedAt()
check.getId(), check.getTitle(), 0, null, 0, 0, check.getCreatedAt()
);
}

Expand All @@ -349,7 +351,9 @@ public QuestionResDTO.UnderstandingResponseResult respondUnderstandingCheck(
validateCheckBelongsToSession(check, session);

UnderstandResChoice selectedChoice = applyUnderstandingResponse(check, loginUser, request.getChoice());
return toUnderstandingResponseResult(check, selectedChoice);
// O/X 클릭 직후 프론트가 13/29와 O/X 뱃지를 바로 갱신할 수 있도록 최신 분모도 함께 내려준다.
int attendanceCount = attendanceService.countAttendedBySession(session);
return toUnderstandingResponseResult(check, selectedChoice, attendanceCount);
}

// 공통 헬퍼 메서드
Expand Down Expand Up @@ -416,6 +420,7 @@ private UnderstandResChoice applyUnderstandingResponse(

if (response.hasChoice(requestedChoice)) {
understandingResponseRepository.delete(response);
// 같은 O/X 버튼을 다시 누르면 인스타 좋아요 취소처럼 응답을 삭제하고 selectedChoice는 null로 내려간다.
return null;
}

Expand All @@ -424,12 +429,22 @@ private UnderstandResChoice applyUnderstandingResponse(
}

private QuestionResDTO.UnderstandingResponseResult toUnderstandingResponseResult(
UnderstandingCheck check, UnderstandResChoice selectedChoice
UnderstandingCheck check, UnderstandResChoice selectedChoice, Integer attendanceCount
) {
// respondedCount는 프론트 화면의 "13/29" 중 13에 해당한다.
int understoodCount = understandingResponseRepository.countByCheckAndChoice(
check, UnderstandResChoice.UNDERSTOOD
);
int notUnderstoodCount = understandingResponseRepository.countByCheckAndChoice(
check, UnderstandResChoice.NOT_UNDERSTOOD
);

return new QuestionResDTO.UnderstandingResponseResult(
check.getId(), selectedChoice,
understandingResponseRepository.countByCheckAndChoice(check, UnderstandResChoice.UNDERSTOOD),
understandingResponseRepository.countByCheckAndChoice(check, UnderstandResChoice.NOT_UNDERSTOOD)
understoodCount + notUnderstoodCount,
attendanceCount,
understoodCount,
notUnderstoodCount
);
}

Expand All @@ -454,17 +469,35 @@ private QuestionResDTO.UnderstandingSliceResponse getUnderstandingSlice(StudySes
}

UnderstandingCheck current = understandingPage.getContent().get(0);
// attendanceCount는 프론트 화면의 "13/29" 중 29에 해당한다.
int attendanceCount = attendanceService.countAttendedBySession(session);
return new QuestionResDTO.UnderstandingSliceResponse(
toUnderstandingCheckResponse(current), understandingIndex, totalCount,
toUnderstandingCheckResponse(current, attendanceCount), understandingIndex, totalCount,
understandingIndex < totalCount - 1, understandingIndex > 0
);
}

private QuestionResDTO.UnderstandingCheckResponse toUnderstandingCheckResponse(UnderstandingCheck check) {
return toUnderstandingCheckResponse(check, null);
}

private QuestionResDTO.UnderstandingCheckResponse toUnderstandingCheckResponse(
UnderstandingCheck check, Integer attendanceCount
) {
// understoodCount/notUnderstoodCount는 오른쪽 O/X 뱃지 숫자로 그대로 사용한다.
int understoodCount = understandingResponseRepository.countByCheckAndChoice(
check, UnderstandResChoice.UNDERSTOOD
);
int notUnderstoodCount = understandingResponseRepository.countByCheckAndChoice(
check, UnderstandResChoice.NOT_UNDERSTOOD
);

return new QuestionResDTO.UnderstandingCheckResponse(
check.getId(), check.getTitle(),
understandingResponseRepository.countByCheckAndChoice(check, UnderstandResChoice.UNDERSTOOD),
understandingResponseRepository.countByCheckAndChoice(check, UnderstandResChoice.NOT_UNDERSTOOD),
understoodCount + notUnderstoodCount,
attendanceCount,
understoodCount,
notUnderstoodCount,
check.getCreatedAt()
);
}
Expand Down
Loading