Skip to content

Commit

Permalink
Merge pull request #241 from IT-Cotato/develop
Browse files Browse the repository at this point in the history
[Release] V2.2024.12.11.02
  • Loading branch information
Youthhing authored Dec 11, 2024
2 parents 329e683 + f5c739f commit 2e70ad0
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
import org.cotato.csquiz.api.attendance.dto.GenerationMemberAttendanceRecordResponse;
import org.cotato.csquiz.api.attendance.dto.UpdateAttendanceRecordRequest;
import org.cotato.csquiz.api.attendance.dto.UpdateAttendanceRequest;
import org.cotato.csquiz.domain.attendance.service.AttendanceAdminService;
import org.cotato.csquiz.domain.attendance.service.AttendanceExcelService;
import org.cotato.csquiz.domain.attendance.service.AttendanceRecordService;
import org.cotato.csquiz.domain.attendance.service.AttendanceService;
import org.cotato.csquiz.domain.attendance.util.AttendanceExcelHeaderUtil;
import org.springframework.http.HttpHeaders;
Expand All @@ -36,7 +37,8 @@
@RequestMapping("/v2/api/attendances")
public class AttendanceController {

private final AttendanceAdminService attendanceAdminService;
private final AttendanceExcelService attendanceExcelService;
private final AttendanceRecordService attendanceRecordService;
private final AttendanceService attendanceService;

@Operation(summary = "출석 단건 조회")
Expand All @@ -48,7 +50,7 @@ public ResponseEntity<AttendanceResponse> getAttendance(@PathVariable("attendanc
@Operation(summary = "출석 정보 변경 API")
@PatchMapping
public ResponseEntity<Void> updateAttendance(@RequestBody @Valid UpdateAttendanceRequest request) {
attendanceAdminService.updateAttendanceByAttendanceId(request);
attendanceService.updateAttendance(request.attendanceId(), request.location(), request.attendTime().attendanceDeadLine(), request.attendTime().lateDeadLine());
return ResponseEntity.noContent().build();
}

Expand All @@ -63,22 +65,22 @@ public ResponseEntity<AttendanceTimeResponse> findAttendanceTimeInfo(@RequestPar
public ResponseEntity<List<GenerationMemberAttendanceRecordResponse>> findAttendanceRecords(
@RequestParam(name = "generationId") Long generationId
) {
return ResponseEntity.ok().body(attendanceAdminService.findAttendanceRecords(generationId));
return ResponseEntity.ok().body(attendanceRecordService.findAttendanceRecords(generationId));
}

@Operation(summary = "회원 출결사항 출석 단위 조회 API")
@GetMapping("/{attendance-id}/records")
public ResponseEntity<List<AttendanceRecordResponse>> findAttendanceRecordsByAttendance(
@PathVariable("attendance-id") Long attendanceId) {
return ResponseEntity.ok().body(attendanceAdminService.findAttendanceRecordsByAttendance(attendanceId));
return ResponseEntity.ok().body(attendanceRecordService.findAttendanceRecordsByAttendance(attendanceId));
}

@Operation(summary = "회원 출결사항 수정 API")
@PatchMapping("/{attendance-id}/records")
public ResponseEntity<Void> updateAttendanceRecords(
@PathVariable("attendance-id") Long attendanceId,
@RequestBody @Valid UpdateAttendanceRecordRequest request) {
attendanceAdminService.updateAttendanceRecords(attendanceId, request.memberId(), request.result());
attendanceRecordService.updateAttendanceRecords(attendanceId, request.memberId(), request.result());
return ResponseEntity.noContent().build();
}

Expand All @@ -95,8 +97,8 @@ public ResponseEntity<AttendancesResponse> findAttendancesByGeneration(
public ResponseEntity<byte[]> downloadAttendanceRecordsAsExcelBySessions(
@RequestParam(name = "attendanceIds") List<Long> attendanceIds) {

byte[] excelFile = attendanceAdminService.createExcelForSessionAttendance(attendanceIds);
String finalFileName = attendanceAdminService.getEncodedFileName(attendanceIds);
byte[] excelFile = attendanceExcelService.createExcelForSessionAttendance(attendanceIds);
String finalFileName = attendanceExcelService.getEncodedFileName(attendanceIds);

HttpHeaders headers = AttendanceExcelHeaderUtil.createExcelDownloadHeaders(finalFileName);

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

import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import java.util.List;
import org.cotato.csquiz.api.attendance.dto.AttendanceTimeResponse;
Expand All @@ -9,15 +10,20 @@
import org.cotato.csquiz.domain.generation.entity.SessionImage;

public record SessionWithAttendanceResponse(
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
Long sessionId,
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
Integer sessionNumber,
String title,
List<SessionListImageInfoResponse> sessionImages,
String description,
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
Long generationId,
String placeName,
LocalDateTime sessionDateTime,
SessionContents sessionContents,
boolean isOffline,
boolean isOnline,
AttendanceTimeResponse attendance
) {
public static SessionWithAttendanceResponse of(Session session, List<SessionImage> sessionImages, Attendance attendance) {
Expand All @@ -31,6 +37,8 @@ public static SessionWithAttendanceResponse of(Session session, List<SessionImag
session.getPlaceName(),
session.getSessionDateTime(),
session.getSessionContents(),
session.getSessionType().hasOffline(),
session.getSessionType().hasOnline(),
AttendanceTimeResponse.from(attendance)
);
}
Expand All @@ -46,6 +54,8 @@ public static SessionWithAttendanceResponse of(Session session, List<SessionImag
session.getPlaceName(),
session.getSessionDateTime(),
session.getSessionContents(),
session.getSessionType().hasOffline(),
session.getSessionType().hasOnline(),
null
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,136 +1,38 @@
package org.cotato.csquiz.domain.attendance.service;

import jakarta.persistence.EntityNotFoundException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.cotato.csquiz.api.attendance.dto.AttendanceDeadLineDto;
import org.cotato.csquiz.api.attendance.dto.AttendanceRecordResponse;
import org.cotato.csquiz.api.attendance.dto.AttendanceStatistic;
import org.cotato.csquiz.api.attendance.dto.GenerationMemberAttendanceRecordResponse;
import org.cotato.csquiz.api.attendance.dto.UpdateAttendanceRequest;
import org.cotato.csquiz.common.error.ErrorCode;
import org.cotato.csquiz.common.error.exception.AppException;
import org.cotato.csquiz.domain.attendance.embedded.Location;
import org.cotato.csquiz.domain.attendance.entity.Attendance;
import org.cotato.csquiz.domain.attendance.entity.AttendanceRecord;
import org.cotato.csquiz.domain.attendance.enums.AttendanceResult;
import org.cotato.csquiz.domain.attendance.enums.AttendanceType;
import org.cotato.csquiz.domain.attendance.repository.AttendanceRecordRepository;
import org.cotato.csquiz.domain.attendance.repository.AttendanceRepository;
import org.cotato.csquiz.domain.attendance.util.AttendanceExcelUtil;
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.cotato.csquiz.domain.generation.entity.Generation;
import org.cotato.csquiz.domain.generation.entity.Session;
import org.cotato.csquiz.domain.generation.enums.SessionType;
import org.cotato.csquiz.domain.generation.repository.SessionRepository;
import org.cotato.csquiz.domain.generation.service.component.GenerationReader;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@Slf4j
public class AttendanceAdminService {
public class AttendanceExcelService {

private final GenerationReader generationReader;
private final AttendanceRepository attendanceRepository;
private final AttendanceRecordRepository attendanceRecordRepository;
private final AttendanceRecordService attendanceRecordService;
private final SessionRepository sessionRepository;
private final MemberService memberService;

@Transactional
public void addAttendance(Session session, Location location, LocalDateTime attendanceDeadline,
LocalDateTime lateDeadline) {
AttendanceUtil.validateAttendanceTime(session.getSessionDateTime(), attendanceDeadline, lateDeadline);
if (session.hasOfflineSession()) {
checkLocation(location);
}
Attendance attendance = Attendance.builder()
.session(session)
.location(location)
.attendanceDeadLine(attendanceDeadline)
.lateDeadLine(lateDeadline)
.build();

attendanceRepository.save(attendance);
}

private void checkLocation(Location location) {
if (location == null) {
throw new AppException(ErrorCode.INVALID_LOCATION);
}
}

@Transactional
public void updateAttendanceByAttendanceId(UpdateAttendanceRequest request) {
Attendance attendance = attendanceRepository.findById(request.attendanceId())
.orElseThrow(() -> new EntityNotFoundException("해당 출석 정보가 존재하지 않습니다"));
Session attendanceSession = sessionRepository.findById(attendance.getSessionId())
.orElseThrow(() -> new EntityNotFoundException("출석과 연결된 세션을 찾을 수 없습니다"));

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

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

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

attendance.updateDeadLine(attendanceDeadLine.attendanceDeadLine(),
attendanceDeadLine.lateDeadLine());
attendance.updateLocation(location);

attendanceRecordService.updateAttendanceStatus(attendanceSession, attendance);
}

@Transactional
public void updateAttendanceRecords(Long attendanceId, Long memberId, AttendanceResult attendanceResult) {
Attendance attendance = attendanceRepository.findById(attendanceId)
.orElseThrow(() -> new EntityNotFoundException("해당 출석이 존재하지 않습니다"));

AttendanceRecord attendanceRecord = attendanceRecordRepository.findByMemberIdAndAttendanceId(memberId, attendanceId)
.orElseGet(() -> AttendanceRecord.absentRecord(attendance, memberId));

// Todo https://github.com/IT-Cotato/COTATO-BE/issues/204
attendanceRecord.updateAttendanceResult(attendanceResult);

attendanceRecordRepository.save(attendanceRecord);
}

public List<GenerationMemberAttendanceRecordResponse> findAttendanceRecords(Long generationId) {
List<Long> sessionIds = sessionRepository.findAllByGenerationId(generationId).stream().map(Session::getId).toList();
List<Attendance> attendances = attendanceRepository.findAllBySessionIdsInQuery(sessionIds);
Generation generation = generationReader.findById(generationId);

return attendanceRecordService.generateAttendanceResponses(attendances, generation);
}

public List<AttendanceRecordResponse> findAttendanceRecordsByAttendance(Long attendanceId) {
Attendance attendance = attendanceRepository.findById(attendanceId)
.orElseThrow(() -> new EntityNotFoundException("해당 출석이 존재하지 않습니다"));
Session session = sessionRepository.findById(attendance.getSessionId())
.orElseThrow(() -> new EntityNotFoundException("해당 세션을 찾을 수 없습니다."));

return attendanceRecordService.generateSingleAttendanceResponses(attendance, session.getGeneration());
}

public byte[] createExcelForSessionAttendance(List<Long> attendanceIds) {
List<Member> activeMembers = memberService.findActiveMember();
Map<Long, String> memberNameByMemberId = activeMembers.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.cotato.csquiz.domain.generation.entity.Session;
import org.cotato.csquiz.domain.generation.repository.GenerationMemberRepository;
import org.cotato.csquiz.domain.generation.repository.SessionRepository;
import org.cotato.csquiz.domain.generation.service.component.GenerationReader;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -48,9 +49,14 @@ public class AttendanceRecordService {
private final RequestAttendanceService requestAttendanceService;
private final SessionRepository sessionRepository;
private final MemberReader memberReader;
private final GenerationReader generationReader;
private final GenerationMemberRepository generationMemberRepository;

public List<GenerationMemberAttendanceRecordResponse> generateAttendanceResponses(List<Attendance> attendances, Generation generation) {
public List<GenerationMemberAttendanceRecordResponse> findAttendanceRecords(Long generationId) {
List<Long> sessionIds = sessionRepository.findAllByGenerationId(generationId).stream().map(Session::getId).toList();
List<Attendance> attendances = attendanceRepository.findAllBySessionIdsInQuery(sessionIds);
Generation generation = generationReader.findById(generationId);

List<Long> attendanceIds = attendances.stream().map(Attendance::getId).toList();

Map<Long, List<AttendanceRecord>> recordsByMemberId = attendanceRecordRepository.findAllByAttendanceIdsInQuery(attendanceIds).stream()
Expand All @@ -65,8 +71,13 @@ public List<GenerationMemberAttendanceRecordResponse> generateAttendanceResponse
.toList();
}

public List<AttendanceRecordResponse> generateSingleAttendanceResponses(Attendance attendance, Generation generation) {
Map<Long, Member> memberById = memberReader.findAllGenerationMember(generation).stream()
public List<AttendanceRecordResponse> findAttendanceRecordsByAttendance(Long attendanceId) {
Attendance attendance = attendanceRepository.findById(attendanceId)
.orElseThrow(() -> new EntityNotFoundException("해당 출석이 존재하지 않습니다"));
Session session = sessionRepository.findById(attendance.getSessionId())
.orElseThrow(() -> new EntityNotFoundException("해당 세션을 찾을 수 없습니다."));

Map<Long, Member> memberById = memberReader.findAllGenerationMember(session.getGeneration()).stream()
.collect(Collectors.toMap(Member::getId, Function.identity()));

Map<Long, AttendanceResult> attendanceResultByMemberId = attendanceRecordRepository.findAllByAttendanceIdAndMemberIdIn(
Expand All @@ -78,6 +89,21 @@ public List<AttendanceRecordResponse> generateSingleAttendanceResponses(Attendan
.toList();
}

// Todo: 엑셀 코드 수정하면서 같이 제거
public List<GenerationMemberAttendanceRecordResponse> generateAttendanceResponses(List<Attendance> attendances, Generation generation) {
List<Long> attendanceIds = attendances.stream().map(Attendance::getId).toList();
Map<Long, List<AttendanceRecord>> recordsByMemberId = attendanceRecordRepository.findAllByAttendanceIdsInQuery(attendanceIds).stream()
.collect(Collectors.groupingBy(AttendanceRecord::getMemberId));

return memberReader.findAllGenerationMember(generation).stream()
.sorted(Comparator.comparing(Member::getName))
.map(member -> GenerationMemberAttendanceRecordResponse.of(
member,
AttendanceStatistic.of(recordsByMemberId.getOrDefault(member.getId(), List.of()), attendances.size())
))
.toList();
}

@Transactional
public AttendResponse submitRecord(AttendanceParams request, final Long memberId) {
Attendance attendance = attendanceRepository.findById(request.attendanceId())
Expand Down Expand Up @@ -176,4 +202,18 @@ public void updateUnrecordedAttendanceRecord(Long sessionId) {

attendanceRecordRepository.saveAll(unrecordedMemberIds);
}

@Transactional
public void updateAttendanceRecords(Long attendanceId, Long memberId, AttendanceResult attendanceResult) {
Attendance attendance = attendanceRepository.findById(attendanceId)
.orElseThrow(() -> new EntityNotFoundException("해당 출석이 존재하지 않습니다"));

AttendanceRecord attendanceRecord = attendanceRecordRepository.findByMemberIdAndAttendanceId(memberId, attendanceId)
.orElseGet(() -> AttendanceRecord.absentRecord(attendance, memberId));

// Todo https://github.com/IT-Cotato/COTATO-BE/issues/204
attendanceRecord.updateAttendanceResult(attendanceResult);

attendanceRecordRepository.save(attendanceRecord);
}
}
Loading

0 comments on commit 2e70ad0

Please sign in to comment.