-
Notifications
You must be signed in to change notification settings - Fork 2
MOSU-121 refator: 신청 관련 기능 리팩토링 (구현 중, 닫지 말아주세요) #124
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
Changes from all commits
cc0b84b
e527e28
4b24c81
95698e3
9174212
e427bdf
bf9279a
56eab5f
4983a02
f7153f5
b96d776
163e4c4
4c1202f
b4e6183
a2726d8
2a97477
f59e3cb
3aa6f2e
6b23a5e
ddc4eaa
907b71d
823d296
b301068
42c98e2
34f5872
edb2276
925d36f
ca76588
df3457a
a162d72
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,27 +1,35 @@ | ||||||||||
| package life.mosu.mosuserver.application.application; | ||||||||||
|
|
||||||||||
| import java.util.HashSet; | ||||||||||
| import java.util.List; | ||||||||||
| import java.util.Map; | ||||||||||
| import java.util.Set; | ||||||||||
| import java.util.function.Function; | ||||||||||
| import java.util.stream.Collectors; | ||||||||||
| import life.mosu.mosuserver.application.examapplication.ExamApplicationService; | ||||||||||
| import life.mosu.mosuserver.application.examapplication.dto.RegisterExamApplicationEvent; | ||||||||||
| import life.mosu.mosuserver.domain.application.ApplicationJpaEntity; | ||||||||||
| import life.mosu.mosuserver.domain.application.ApplicationJpaRepository; | ||||||||||
| import life.mosu.mosuserver.domain.application.ExamTicketImageJpaEntity; | ||||||||||
| import life.mosu.mosuserver.domain.application.ExamTicketImageJpaRepository; | ||||||||||
| import life.mosu.mosuserver.domain.application.Subject; | ||||||||||
| import life.mosu.mosuserver.domain.exam.ExamJpaEntity; | ||||||||||
| import life.mosu.mosuserver.domain.exam.ExamJpaRepository; | ||||||||||
| import life.mosu.mosuserver.domain.examapplication.ExamApplicationJpaEntity; | ||||||||||
| import life.mosu.mosuserver.domain.examapplication.ExamApplicationJpaRepository; | ||||||||||
| import life.mosu.mosuserver.domain.examapplication.ExamSubjectJpaEntity; | ||||||||||
| import life.mosu.mosuserver.domain.examapplication.ExamSubjectJpaRepository; | ||||||||||
| import life.mosu.mosuserver.domain.payment.PaymentJpaEntity; | ||||||||||
| import life.mosu.mosuserver.domain.payment.PaymentJpaRepository; | ||||||||||
| import life.mosu.mosuserver.global.exception.CustomRuntimeException; | ||||||||||
| import life.mosu.mosuserver.global.exception.ErrorCode; | ||||||||||
| import life.mosu.mosuserver.global.util.FileRequest; | ||||||||||
| import life.mosu.mosuserver.infra.respository.ExamApplicationBulkRepository; | ||||||||||
| import life.mosu.mosuserver.presentation.application.dto.ApplicationRequest; | ||||||||||
| import life.mosu.mosuserver.presentation.application.dto.ApplicationResponse; | ||||||||||
| import life.mosu.mosuserver.presentation.application.dto.CreateApplicationResponse; | ||||||||||
| import life.mosu.mosuserver.presentation.application.dto.ExamApplicationRequest; | ||||||||||
| import life.mosu.mosuserver.presentation.application.dto.ExamWithSubjects; | ||||||||||
| import life.mosu.mosuserver.presentation.application.dto.ExamApplicationResponse; | ||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||
| import org.springframework.stereotype.Service; | ||||||||||
|
|
@@ -39,18 +47,34 @@ public class ApplicationService { | |||||||||
| private final ExamSubjectJpaRepository examSubjectJpaRepository; | ||||||||||
| private final ExamApplicationJpaRepository examApplicationJpaRepository; | ||||||||||
| private final ExamJpaRepository examJpaRepository; | ||||||||||
| private final ExamApplicationBulkRepository examApplicationBulkRepository; | ||||||||||
| private final PaymentJpaRepository paymentJpaRepository; | ||||||||||
|
|
||||||||||
|
|
||||||||||
| @Transactional | ||||||||||
| public ApplicationResponse apply(Long userId, ApplicationRequest request) { | ||||||||||
| public CreateApplicationResponse apply(Long userId, ApplicationRequest request) { | ||||||||||
|
|
||||||||||
| // 중복 신청 검증 | ||||||||||
| List<Long> examIds = request.examApplication().stream() | ||||||||||
| List<ExamApplicationRequest> examApplicationRequests = request.examApplication(); | ||||||||||
| Set<Subject> subjects = request.validatedSubjects(); | ||||||||||
|
|
||||||||||
| List<Long> examIds = examApplicationRequests.stream() | ||||||||||
| .map(ExamApplicationRequest::examId) | ||||||||||
| .toList(); | ||||||||||
|
|
||||||||||
| // examId 가 동일 하냐? | ||||||||||
| //다른 exam 인데 시간이 같나? | ||||||||||
| //exam_id lunch_id 쌍으로 포함된 exam_application 이 존재하냐 | ||||||||||
| // 중복 신청 검증 | ||||||||||
| Set<Long> examIdSet = new HashSet<>(examIds); | ||||||||||
| if (examIds.size() != examIdSet.size()) { | ||||||||||
| throw new RuntimeException("같은 시험을 신청할 수 없습니다."); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // 신청을 1개 이상 신청했는지 검증 | ||||||||||
| if (examIds.isEmpty()) { | ||||||||||
| throw new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_NOT_FOUND); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| //해당 시험이 진짜 있는 일정인지, lunch 가 없는 시험인데 lunch 를 신청했는지 | ||||||||||
| validateExamIdsAndLunchSelection(examApplicationRequests); | ||||||||||
|
|
||||||||||
| boolean isDuplicate = applicationJpaRepository.existsByUserIdAndExamIds(userId, examIds); | ||||||||||
| if (isDuplicate) { | ||||||||||
| throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_DUPLICATED); | ||||||||||
|
|
@@ -63,82 +87,163 @@ public ApplicationResponse apply(Long userId, ApplicationRequest request) { | |||||||||
| List<ExamApplicationJpaEntity> examApplicationEntities = examApplicationService.register( | ||||||||||
| RegisterExamApplicationEvent.of(request.examApplication(), applicationId) | ||||||||||
| ); | ||||||||||
| examApplicationJpaRepository.saveAll(examApplicationEntities); | ||||||||||
|
|
||||||||||
| List<ExamSubjectJpaEntity> allExamSubjects = examApplicationEntities.stream() | ||||||||||
| .flatMap(examApplication -> { | ||||||||||
| Long examApplicationId = examApplication.getId(); | ||||||||||
| return request.validatedSubjects().stream() | ||||||||||
| .map(subject -> ExamSubjectJpaEntity.create(examApplicationId, | ||||||||||
| subject)); | ||||||||||
| }) | ||||||||||
| .toList(); | ||||||||||
|
|
||||||||||
| examSubjectJpaRepository.saveAll(allExamSubjects); | ||||||||||
| // 시험 신청 목록과 과목 multi-insert | ||||||||||
| examApplicationBulkRepository.saveAllExamApplicationsWithSubjects(examApplicationEntities, | ||||||||||
| subjects); | ||||||||||
|
|
||||||||||
| // 수험표 저장 | ||||||||||
| FileRequest fileReq = request.admissionTicket(); | ||||||||||
| if (fileReq.fileName() != null && fileReq.s3Key() != null) { | ||||||||||
| ExamTicketImageJpaEntity examTicketImage = fileReq | ||||||||||
| .toExamTicketImageEntity(applicationId); | ||||||||||
|
|
||||||||||
| examTicketImageJpaRepository.save(examTicketImage); | ||||||||||
| } | ||||||||||
| return ApplicationResponse.of(applicationId); | ||||||||||
| return CreateApplicationResponse.of(applicationId); | ||||||||||
| } | ||||||||||
|
|
||||||||||
|
|
||||||||||
| // 전체 신청 내역 조회 | ||||||||||
| // TODO: 테스트 필요 | ||||||||||
| @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) | ||||||||||
| public List<ApplicationResponse> getApplications(Long userId) { | ||||||||||
| List<ApplicationJpaEntity> applications = applicationJpaRepository.findAllByUserId(userId); | ||||||||||
|
|
||||||||||
| List<ApplicationJpaEntity> applications = getUserApplications(userId); | ||||||||||
|
|
||||||||||
| List<ExamApplicationJpaEntity> examApplications = getExamApplications(applications); | ||||||||||
|
|
||||||||||
| Map<Long, ExamJpaEntity> examMap = getExamMap(examApplications); | ||||||||||
|
|
||||||||||
| Map<Long, List<ExamSubjectJpaEntity>> subjectMap = getSubjectMap(examApplications); | ||||||||||
|
|
||||||||||
| Map<Long, List<ExamApplicationResponse>> examResponsesGroupedByApplicationId = | ||||||||||
| groupExamResponsesByApplication(examApplications, examMap, subjectMap); | ||||||||||
|
|
||||||||||
| return applications.stream() | ||||||||||
| .map(app -> ApplicationResponse.of( | ||||||||||
| app.getId(), | ||||||||||
| examResponsesGroupedByApplicationId.getOrDefault(app.getId(), List.of()) | ||||||||||
| )) | ||||||||||
| .toList(); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private void validateExamIdsAndLunchSelection(List<ExamApplicationRequest> requests) { | ||||||||||
|
|
||||||||||
| List<Long> examIds = requests.stream() | ||||||||||
| .map(ExamApplicationRequest::examId).toList(); | ||||||||||
|
Comment on lines
+132
to
+133
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current logic for validating exam existence might be flawed if the input
Suggested change
|
||||||||||
|
|
||||||||||
| List<ExamJpaEntity> existingExams = examJpaRepository.findAllById(examIds); | ||||||||||
| if (existingExams.size() != examIds.size()) { | ||||||||||
| throw new CustomRuntimeException(ErrorCode.EXAM_NOT_FOUND); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| Set<Long> examsWithoutLunch = existingExams.stream() | ||||||||||
| .filter(exam -> exam.getLunchName() == null) | ||||||||||
| .map(ExamJpaEntity::getId) | ||||||||||
| .collect(Collectors.toSet()); | ||||||||||
|
|
||||||||||
| boolean invalidLunchSelection = requests.stream() | ||||||||||
| .anyMatch(req -> examsWithoutLunch.contains(req.examId()) && req.isLunchChecked()); | ||||||||||
|
|
||||||||||
| if (invalidLunchSelection) { | ||||||||||
| throw new CustomRuntimeException(ErrorCode.LUNCH_SELECTION_INVALID); | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private List<ApplicationJpaEntity> getUserApplications(Long userId) { | ||||||||||
| List<ApplicationJpaEntity> applications = applicationJpaRepository.findAllByUserId(userId); | ||||||||||
| if (applications.isEmpty()) { | ||||||||||
| throw new CustomRuntimeException(ErrorCode.APPLICATION_LIST_NOT_FOUND); | ||||||||||
| } | ||||||||||
| return applications; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return applications.stream() | ||||||||||
| .map(application -> { | ||||||||||
| // 해당 신청의 시험 신청들 조회 | ||||||||||
| List<ExamApplicationJpaEntity> examApplications = | ||||||||||
| examApplicationJpaRepository.findByApplicationId(application.getId()); | ||||||||||
|
|
||||||||||
| if (examApplications.isEmpty()) { | ||||||||||
| throw new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_NOT_FOUND); | ||||||||||
| } | ||||||||||
| // ExamWithSubjects 리스트 생성 | ||||||||||
| List<ExamWithSubjects> exams = examApplications.stream() | ||||||||||
| .map(examApplication -> { | ||||||||||
| // 시험 정보 조회 | ||||||||||
| ExamJpaEntity exam = examJpaRepository.findById( | ||||||||||
| examApplication.getExamId()) | ||||||||||
| .orElseThrow(() -> new CustomRuntimeException( | ||||||||||
| ErrorCode.EXAM_NOT_FOUND)); | ||||||||||
|
|
||||||||||
| // 과목 정보 조회 | ||||||||||
| List<ExamSubjectJpaEntity> examSubjects = | ||||||||||
| examSubjectJpaRepository.findByExamApplicationId( | ||||||||||
| examApplication.getId()); | ||||||||||
| Set<String> subjects = examSubjects.stream() | ||||||||||
| .map(examSubject -> examSubject.getSubject() | ||||||||||
| .getSubjectName()) | ||||||||||
| .collect(Collectors.toSet()); | ||||||||||
|
|
||||||||||
| // ExamWithSubjects 생성 | ||||||||||
| return new ExamWithSubjects( | ||||||||||
| examApplication.getId(), | ||||||||||
| exam.getArea().getAreaName(), | ||||||||||
| exam.getExamDate(), | ||||||||||
| exam.getSchoolName(), | ||||||||||
| null, | ||||||||||
| examApplication.getExamNumber() != null | ||||||||||
| ? examApplication.getExamNumber() : "", | ||||||||||
| subjects | ||||||||||
| ); | ||||||||||
| }) | ||||||||||
| .toList(); | ||||||||||
|
|
||||||||||
| return ApplicationResponse.of(application.getId(), exams); | ||||||||||
| }) | ||||||||||
| private List<ExamApplicationJpaEntity> getExamApplications( | ||||||||||
| List<ApplicationJpaEntity> applications) { | ||||||||||
| List<Long> applicationIds = applications.stream() | ||||||||||
| .map(ApplicationJpaEntity::getId) | ||||||||||
| .toList(); | ||||||||||
|
|
||||||||||
| List<ExamApplicationJpaEntity> examApplications = | ||||||||||
| examApplicationJpaRepository.findByApplicationIdIn(applicationIds); | ||||||||||
|
|
||||||||||
| if (examApplications.isEmpty()) { | ||||||||||
| throw new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_NOT_FOUND); | ||||||||||
| } | ||||||||||
| return examApplications; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private Map<Long, ExamJpaEntity> getExamMap(List<ExamApplicationJpaEntity> examApplications) { | ||||||||||
| List<Long> examIds = examApplications.stream() | ||||||||||
| .map(ExamApplicationJpaEntity::getExamId) | ||||||||||
| .distinct() | ||||||||||
| .toList(); | ||||||||||
|
|
||||||||||
| return examJpaRepository.findByIdIn(examIds).stream() | ||||||||||
| .collect(Collectors.toMap(ExamJpaEntity::getId, Function.identity())); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private Map<Long, PaymentJpaEntity> getPaymentMap( | ||||||||||
| List<ExamApplicationJpaEntity> examApplications) { | ||||||||||
| List<Long> examApplicationIds = examApplications.stream() | ||||||||||
| .map(ExamApplicationJpaEntity::getId) | ||||||||||
| .toList(); | ||||||||||
|
|
||||||||||
| return paymentJpaRepository.findByExamApplicationIdIn(examApplicationIds).stream() | ||||||||||
| .collect(Collectors.toMap(PaymentJpaEntity::getExamApplicationId, | ||||||||||
| Function.identity())); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private Map<Long, List<ExamSubjectJpaEntity>> getSubjectMap( | ||||||||||
| List<ExamApplicationJpaEntity> examApplications) { | ||||||||||
| List<Long> examApplicationIds = examApplications.stream() | ||||||||||
| .map(ExamApplicationJpaEntity::getId) | ||||||||||
| .toList(); | ||||||||||
|
|
||||||||||
| return examSubjectJpaRepository.findByExamApplicationIdIn(examApplicationIds).stream() | ||||||||||
| .collect(Collectors.groupingBy(ExamSubjectJpaEntity::getExamApplicationId)); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private Map<Long, List<ExamApplicationResponse>> groupExamResponsesByApplication( | ||||||||||
| List<ExamApplicationJpaEntity> examApplications, | ||||||||||
| Map<Long, ExamJpaEntity> examMap, | ||||||||||
| Map<Long, List<ExamSubjectJpaEntity>> subjectMap | ||||||||||
| ) { | ||||||||||
| Map<Long, PaymentJpaEntity> paymentMap = getPaymentMap(examApplications); | ||||||||||
|
|
||||||||||
| return examApplications.stream() | ||||||||||
| .map(examApplication -> { | ||||||||||
| Long examApplicationId = examApplication.getId(); | ||||||||||
| ExamJpaEntity exam = examMap.get(examApplication.getExamId()); | ||||||||||
|
|
||||||||||
| Set<String> subjects = subjectMap.getOrDefault(examApplicationId, List.of()) | ||||||||||
| .stream() | ||||||||||
| .map(es -> es.getSubject().getSubjectName()) | ||||||||||
| .collect(Collectors.toSet()); | ||||||||||
|
|
||||||||||
| PaymentJpaEntity payment = paymentMap.get(examApplicationId); | ||||||||||
| String paymentStatus = | ||||||||||
| (payment != null) ? payment.getPaymentStatus().name() : null; | ||||||||||
| Integer totalAmount = (payment != null && payment.getPaymentAmount() != null) | ||||||||||
| ? payment.getPaymentAmount().getTotalAmount() | ||||||||||
| : 0; | ||||||||||
|
|
||||||||||
| ExamApplicationResponse response = ExamApplicationResponse.of( | ||||||||||
| examApplicationId, | ||||||||||
| examApplication.getCreatedAt(), | ||||||||||
| paymentStatus, | ||||||||||
| totalAmount, | ||||||||||
| exam.getSchoolName(), | ||||||||||
| exam.getExamDate(), | ||||||||||
| subjects, | ||||||||||
| examApplication.getIsLunchChecked() ? exam.getLunchName() : "신청 안 함" | ||||||||||
| ); | ||||||||||
|
|
||||||||||
| return Map.entry(examApplication.getApplicationId(), response); | ||||||||||
| }) | ||||||||||
| .collect(Collectors.groupingBy( | ||||||||||
| Map.Entry::getKey, | ||||||||||
| Collectors.mapping(Map.Entry::getValue, Collectors.toList()) | ||||||||||
| )); | ||||||||||
| } | ||||||||||
| } | ||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use consistent exception handling.
Good fix for the duplicate exam ID validation issue. However, use
CustomRuntimeExceptionfor consistency with the rest of the codebase.Note: You'll need to add the corresponding error code to the ErrorCode enum.
📝 Committable suggestion
🤖 Prompt for AI Agents