Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
611a1e7
MOSU-103 feat: 신청 관련 ErrorCode 추가
chominju02 Jul 18, 2025
b2a4e5e
MOSU-103 feat: 신청 multi-insert 구현
chominju02 Jul 18, 2025
246b64d
MOSU-103 feat: ApplicationRequest의 ApplicationSchoolRequest List 타입으로 변경
chominju02 Jul 18, 2025
f7b08ed
MOSU-103 feat: 신청내역 조회, 중복 신청 확인 기능 구현
chominju02 Jul 18, 2025
3837664
MOSU-103 feat: 신청 학교 조건에 맞는 학교 존재 여부 확인
chominju02 Jul 18, 2025
0445ece
MOSU-103 refactor: DB I/O 최소화를 위한 신청 로직 개선
chominju02 Jul 18, 2025
483491e
MOSU-103 refactor: exception 타입 변경
chominju02 Jul 18, 2025
8ca5801
Refactor: Remove unused entities, repositories, and DTOs related to a…
chominju02 Jul 19, 2025
7b8580f
Refactor: Comment out the entire AdminService class and its dependencies
chominju02 Jul 19, 2025
edd934f
Refactor: Simplify application logic and enhance exam application han…
chominju02 Jul 19, 2025
30ee50e
Refactor: Add SchoolQuotaCacheManager class for managing school quota…
chominju02 Jul 19, 2025
f940120
Refactor: Simplify attachment entity creation in EventAttachmentService
chominju02 Jul 19, 2025
041ab2d
feat: Implement ExamService for managing exam registrations and queries
chominju02 Jul 19, 2025
10dffb2
feat: Add ExamApplicationService for handling exam application regist…
chominju02 Jul 19, 2025
53c22bf
feat: Add RegisterExamApplicationEvent for exam application registrat…
chominju02 Jul 19, 2025
8722cad
Refactor: Remove unused S3Properties and simplify attachment entity c…
chominju02 Jul 19, 2025
b14e032
Refactor: Remove unused S3Properties and simplify attachment entity c…
chominju02 Jul 19, 2025
f9509d4
Refactor: Remove unused S3Properties and simplify attachment entity c…
chominju02 Jul 19, 2025
5750e8a
feat: Implement LunchEventService for creating lunch events
chominju02 Jul 19, 2025
043bb76
feat: Add LunchService for retrieving lunch information by exam ID
chominju02 Jul 19, 2025
e7a5e2e
Refactor: Remove unused S3Properties and simplify attachment entity c…
chominju02 Jul 19, 2025
b6fe98d
Refactor: Simplify NotifyVariableFactory by removing redundant code a…
chominju02 Jul 19, 2025
bdb6679
Refactor: Remove unused ApplicationSchoolNotifyProjection import and …
chominju02 Jul 20, 2025
e78280a
Refactor: Comment out unused ApplicationSchoolJpaRepository and simpl…
chominju02 Jul 20, 2025
7b13765
Refactor: Comment out unused imports and class in ApplicationQueryRep…
chominju02 Jul 20, 2025
c2f77a5
Refactor: Comment out unused imports and class in RefundQueryReposito…
chominju02 Jul 20, 2025
fcb0880
Refactor: Add DTOs for exam and application handling, including ExamT…
chominju02 Jul 20, 2025
c183617
Refactor: Rename guardianPhoneNumber to parentPhoneNumber in Applicat…
chominju02 Jul 20, 2025
af98f93
Refactor: Implement LunchController, LunchEventListener, and related …
chominju02 Jul 20, 2025
992bd04
Refactor: Update AddressResponse to use AddressJpaVO from the exam do…
chominju02 Jul 20, 2025
29260f8
Refactor: Add entities and repositories for exam and exam application…
chominju02 Jul 20, 2025
5e41049
Refactor: Add LunchJpaEntity and LunchJpaRepository for lunch managem…
chominju02 Jul 20, 2025
e49fc92
Refactor: Remove unused classes and commented code from various files…
chominju02 Jul 20, 2025
2ef9ad2
Refactor: Introduce ExamQuotaCacheManager for managing exam-related q…
chominju02 Jul 20, 2025
8552c3a
Refactor: Enhance ExamApplicationService with subject management and …
chominju02 Jul 21, 2025
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
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ dependencies {
implementation 'org.apache.poi:poi-ooxml:5.4.0'

implementation 'org.springframework.boot:spring-boot-starter-webflux'

runtimeOnly 'com.h2database:h2'

}

tasks.named('test') {
Expand Down
3 changes: 0 additions & 3 deletions src/main/java/life/mosu/mosuserver/MosuServerApplication.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package life.mosu.mosuserver;

import life.mosu.mosuserver.infra.notify.NotifyEventPublisher;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
Expand All @@ -10,8 +9,6 @@
@RequiredArgsConstructor
public class MosuServerApplication implements CommandLineRunner {

private final NotifyEventPublisher publisher;

public static void main(String[] args) {
SpringApplication.run(MosuServerApplication.class, args);
}
Expand Down
110 changes: 55 additions & 55 deletions src/main/java/life/mosu/mosuserver/application/admin/AdminService.java
Original file line number Diff line number Diff line change
@@ -1,55 +1,55 @@
package life.mosu.mosuserver.application.admin;

import java.util.List;
import life.mosu.mosuserver.domain.admin.ApplicationQueryRepositoryImpl;
import life.mosu.mosuserver.domain.admin.RefundQueryRepositoryImpl;
import life.mosu.mosuserver.domain.admin.StudentQueryRepositoryImpl;
import life.mosu.mosuserver.presentation.admin.dto.ApplicationExcelDto;
import life.mosu.mosuserver.presentation.admin.dto.ApplicationFilter;
import life.mosu.mosuserver.presentation.admin.dto.ApplicationListResponse;
import life.mosu.mosuserver.presentation.admin.dto.RefundListResponse;
import life.mosu.mosuserver.presentation.admin.dto.SchoolLunchResponse;
import life.mosu.mosuserver.presentation.admin.dto.StudentExcelDto;
import life.mosu.mosuserver.presentation.admin.dto.StudentFilter;
import life.mosu.mosuserver.presentation.admin.dto.StudentListResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class AdminService {

private final StudentQueryRepositoryImpl studentQueryRepository;
private final ApplicationQueryRepositoryImpl applicationQueryRepository;
private final RefundQueryRepositoryImpl refundQueryRepository;

public Page<StudentListResponse> getStudents(StudentFilter filter, Pageable pageable) {
return studentQueryRepository.searchAllStudents(filter, pageable);
}

public List<StudentExcelDto> getStudentExcelData() {
return studentQueryRepository.searchAllStudentsForExcel();
}

public List<SchoolLunchResponse> getLunchCounts() {
return applicationQueryRepository.searchAllSchoolLunches();
}

public Page<ApplicationListResponse> getApplications(ApplicationFilter filter,
Pageable pageable) {
return applicationQueryRepository.searchAllApplications(filter, pageable);
}

public List<ApplicationExcelDto> getApplicationExcelData() {
return applicationQueryRepository.searchAllApplicationsForExcel();
}

public Page<RefundListResponse> getRefunds(Pageable pageable) {
return refundQueryRepository.searchAllRefunds(pageable);
}

}
//package life.mosu.mosuserver.application.admin;
//
//import java.util.List;
//import life.mosu.mosuserver.domain.admin.ApplicationQueryRepositoryImpl;
//import life.mosu.mosuserver.domain.admin.RefundQueryRepositoryImpl;
//import life.mosu.mosuserver.domain.admin.StudentQueryRepositoryImpl;
//import life.mosu.mosuserver.presentation.admin.dto.ApplicationExcelDto;
//import life.mosu.mosuserver.presentation.admin.dto.ApplicationFilter;
//import life.mosu.mosuserver.presentation.admin.dto.ApplicationListResponse;
//import life.mosu.mosuserver.presentation.admin.dto.RefundListResponse;
//import life.mosu.mosuserver.presentation.admin.dto.SchoolLunchResponse;
//import life.mosu.mosuserver.presentation.admin.dto.StudentExcelDto;
//import life.mosu.mosuserver.presentation.admin.dto.StudentFilter;
//import life.mosu.mosuserver.presentation.admin.dto.StudentListResponse;
//import lombok.RequiredArgsConstructor;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.data.domain.Page;
//import org.springframework.data.domain.Pageable;
//import org.springframework.stereotype.Service;
//
//@Slf4j
//@Service
//@RequiredArgsConstructor
//public class AdminService {
//
// private final StudentQueryRepositoryImpl studentQueryRepository;
// private final ApplicationQueryRepositoryImpl applicationQueryRepository;
// private final RefundQueryRepositoryImpl refundQueryRepository;
//
// public Page<StudentListResponse> getStudents(StudentFilter filter, Pageable pageable) {
// return studentQueryRepository.searchAllStudents(filter, pageable);
// }
//
// public List<StudentExcelDto> getStudentExcelData() {
// return studentQueryRepository.searchAllStudentsForExcel();
// }
//
// public List<SchoolLunchResponse> getLunchCounts() {
// return applicationQueryRepository.searchAllSchoolLunches();
// }
//
// public Page<ApplicationListResponse> getApplications(ApplicationFilter filter,
// Pageable pageable) {
// return applicationQueryRepository.searchAllApplications(filter, pageable);
// }
//
// public List<ApplicationExcelDto> getApplicationExcelData() {
// return applicationQueryRepository.searchAllApplicationsForExcel();
// }
//
// public Page<RefundListResponse> getRefunds(Pageable pageable) {
// return refundQueryRepository.searchAllRefunds(pageable);
// }
//
//}
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
package life.mosu.mosuserver.application.application;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import life.mosu.mosuserver.domain.application.AdmissionTicketImageJpaEntity;
import life.mosu.mosuserver.domain.application.AdmissionTicketImageJpaRepository;
import java.util.stream.Collectors;
import life.mosu.mosuserver.application.exam.ExamQuotaCacheManager;
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.applicationschool.ApplicationSchoolJpaEntity;
import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaRepository;
import life.mosu.mosuserver.domain.school.SchoolJpaEntity;
import life.mosu.mosuserver.domain.school.SchoolJpaRepository;
import life.mosu.mosuserver.domain.school.SchoolQueryRepositoryImpl;
import life.mosu.mosuserver.domain.application.ExamTicketImageJpaEntity;
import life.mosu.mosuserver.domain.application.ExamTicketImageJpaRepository;
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.lunch.LunchJpaEntity;
import life.mosu.mosuserver.domain.lunch.LunchJpaRepository;
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.presentation.application.dto.ApplicationRequest;
import life.mosu.mosuserver.presentation.application.dto.ApplicationResponse;
import life.mosu.mosuserver.presentation.application.dto.ApplicationSchoolRequest;
import life.mosu.mosuserver.presentation.application.dto.ExamApplicationRequest;
import life.mosu.mosuserver.presentation.application.dto.ExamWithSubjects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
Expand All @@ -30,119 +37,120 @@
public class ApplicationService {

private final ApplicationJpaRepository applicationJpaRepository;
private final ApplicationSchoolJpaRepository applicationSchoolJpaRepository;
private final AdmissionTicketImageJpaRepository admissionTicketImageJpaRepository;
private final SchoolQueryRepositoryImpl schoolQueryRepository;
private final SchoolJpaRepository schoolJpaRepository;
private final ExamTicketImageJpaRepository examTicketImageJpaRepository;
private final ExamApplicationService examApplicationService;
private final ExamQuotaCacheManager examQuotaCacheManager;
private final ExamSubjectJpaRepository examSubjectJpaRepository;
private final ExamApplicationJpaRepository examApplicationJpaRepository;
private final ExamJpaRepository examJpaRepository;
private final LunchJpaRepository lunchJpaRepository;

// 신청
@Transactional
public ApplicationResponse apply(Long userId, ApplicationRequest request) {
Set<ApplicationSchoolRequest> schoolRequests = request.schools();
List<ApplicationSchoolJpaEntity> savedEntities = new ArrayList<>();

ApplicationJpaEntity application = applicationJpaRepository.save(request.toEntity(userId));
Long applicationId = application.getId();

//수험표 저장
admissionTicketImageJpaRepository.save(
createAdmissionTicketImageIfPresent(request.admissionTicket(), applicationId));

for (ApplicationSchoolRequest schoolRequest : schoolRequests) {
//해당 학교가 존재하는 학교인지
Long schoolId = schoolJpaRepository.findBySchoolNameAndAreaAndExamDate(
schoolRequest.schoolName(),
schoolRequest.validatedArea(schoolRequest.area()),
schoolRequest.examDate())
.orElseThrow(() -> new CustomRuntimeException(ErrorCode.SCHOOL_NOT_FOUND))
.getId();

//해당 학교를 이미 신청하였는지
if (applicationSchoolJpaRepository.existsByUserIdAndSchoolId(userId, schoolId)) {
throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_ALREADY_APPLIED);
}

//해당 학교를 찾을 수 있는지
SchoolJpaEntity school = schoolJpaRepository.findById(schoolId)
.orElseThrow(() -> new CustomRuntimeException(ErrorCode.SCHOOL_NOT_FOUND));

//해당 학교를 신청했음을 저장하기
ApplicationSchoolJpaEntity applicationSchool = schoolRequest.toEntity(userId,
applicationId, school);

//반환용 리스트에 저장하기
savedEntities.add(applicationSchoolJpaRepository.save(applicationSchool));

// 중복 신청 검증
List<Long> examIds = request.examApplication().stream()
.map(ExamApplicationRequest::examId)
.toList();

boolean isDuplicate = applicationJpaRepository.existsByUserIdAndExamIds(userId, examIds);
if (isDuplicate) {
throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_DUPLICATED);
}

return ApplicationResponse.of(applicationId, savedEntities);
ApplicationJpaEntity application = request.toApplicationJpaEntity(userId);
ApplicationJpaEntity savedApplication = applicationJpaRepository.save(application);
Long applicationId = savedApplication.getId();

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);

FileRequest fileReq = request.admissionTicket();
if (fileReq.fileName() != null && fileReq.s3Key() != null) {
ExamTicketImageJpaEntity examTicketImage = fileReq
.toExamTicketImageEntity(applicationId);

examTicketImageJpaRepository.save(examTicketImage);
}
return ApplicationResponse.of(applicationId);
}

//신청 - 리팩토링
// @Transactional
// public ApplicationResponse apply(Long userId, ApplicationRequest request) {
// Set<ApplicationSchoolRequest> schoolRequests = request.schools();
//// List<ApplicationSchoolJpaEntity> savedEntities = new ArrayList<>();
//
// ApplicationJpaEntity application = applicationJpaRepository.save(request.toEntity(userId));
// Long applicationId = application.getId();
//
// //수험표 저장
// admissionTicketImageJpaRepository.save(
// createAdmissionTicketImageIfPresent(request.admissionTicket(), applicationId));
//
// List<Triple<String, Area, LocalDate>> conditions = schoolRequests.stream()
// .map(school -> Triple.of(
// school.schoolName(),
// school.validatedArea(school.area()),
// school.examDate()
// ))
// .toList();
//
// //이름, 지역, 날짜 조건에 맞는 학교가 있는지
// List<Long> schoolIds = schoolQueryRepository.findSchoolsByConditions(conditions);
// if (schoolIds.size() != conditions.size()) {
// throw new CustomRuntimeException(ErrorCode.SCHOOL_NOT_FOUND);
// }
//
// //해당 학교를 이미 신청하였는지
// if (applicationSchoolJpaRepository.existsByUserIdAndSchoolIds(userId, schoolIds)) {
// throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_ALREADY_APPLIED);
// }
//

/// / //해당 학교를 찾을 수 있는지 / SchoolJpaEntity school =
/// schoolJpaRepository.findById(schoolId) / .orElseThrow(() -> new
/// CustomRuntimeException(ErrorCode.SCHOOL_NOT_FOUND));
//
// //해당 학교를 신청했음을 저장하기
// ApplicationSchoolJpaEntity applicationSchool = schoolRequest.toEntity(userId,
// applicationId, school);
//
// //반환용 리스트에 저장하기
// savedEntities.add(applicationSchoolJpaRepository.save(applicationSchool));
//
// return ApplicationResponse.of(applicationId, savedEntities);
// }

// 전체 신청 내역 조회
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public List<ApplicationResponse> getApplications(Long userId) {
List<ApplicationJpaEntity> applications = applicationJpaRepository.findAllByUserId(userId);

if (applications.isEmpty()) {
throw new CustomRuntimeException(ErrorCode.APPLICATION_LIST_NOT_FOUND);
}

return applications.stream()
.map(application -> {
List<ApplicationSchoolJpaEntity> schools = applicationSchoolJpaRepository.findAllByApplicationId(
application.getId());
return ApplicationResponse.of(application.getId(), schools);
// 해당 신청의 시험 신청들 조회
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));

// 점심 정보 조회 (점심은 null일 수 있음)
LunchJpaEntity lunch = null;
if (examApplication.getLunchId() != null) {
lunch = lunchJpaRepository.findById(
examApplication.getLunchId())
.orElseThrow(() -> new CustomRuntimeException(
ErrorCode.LUNCH_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(),
lunch != null ? lunch.getName() : "신청 안 함",
examApplication.getExamNumber() != null
? examApplication.getExamNumber() : "",
subjects
);
})
.toList();

return ApplicationResponse.of(application.getId(), exams);
})
.toList();
}

private AdmissionTicketImageJpaEntity createAdmissionTicketImageIfPresent(
FileRequest fileRequest, Long applicationId) {
return admissionTicketImageJpaRepository.save(
fileRequest.toAdmissionTicketImageEntity(fileRequest.fileName(),
fileRequest.s3Key(), applicationId));
}

}
Loading