diff --git a/src/main/java/com/example/solidconnection/admin/university/controller/AdminUnivApplyInfoController.java b/src/main/java/com/example/solidconnection/admin/university/controller/AdminUnivApplyInfoController.java index 3b1b0d18f..e0fdbcb70 100644 --- a/src/main/java/com/example/solidconnection/admin/university/controller/AdminUnivApplyInfoController.java +++ b/src/main/java/com/example/solidconnection/admin/university/controller/AdminUnivApplyInfoController.java @@ -1,5 +1,8 @@ package com.example.solidconnection.admin.university.controller; +import com.example.solidconnection.admin.university.dto.AdminUnivApplyInfoCreateRequest; +import com.example.solidconnection.admin.university.dto.AdminUnivApplyInfoResponse; +import com.example.solidconnection.admin.university.dto.AdminUnivApplyInfoUpdateRequest; import com.example.solidconnection.admin.university.dto.UnivApplyInfoFieldResponse; import com.example.solidconnection.admin.university.dto.UnivApplyInfoImportRequest; import com.example.solidconnection.admin.university.dto.UnivApplyInfoImportResponse; @@ -7,7 +10,10 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -25,10 +31,33 @@ public ResponseEntity getFields() { return ResponseEntity.ok(adminUnivApplyInfoService.getFields()); } - @PostMapping + @PostMapping("/import") public ResponseEntity importUnivApplyInfos( @Valid @RequestBody UnivApplyInfoImportRequest request ) { return ResponseEntity.ok(adminUnivApplyInfoService.importUnivApplyInfos(request)); } + + @PostMapping + public ResponseEntity createUnivApplyInfo( + @Valid @RequestBody AdminUnivApplyInfoCreateRequest request + ) { + return ResponseEntity.ok(adminUnivApplyInfoService.createUnivApplyInfo(request)); + } + + @PatchMapping("/{id}") + public ResponseEntity updateUnivApplyInfo( + @PathVariable long id, + @Valid @RequestBody AdminUnivApplyInfoUpdateRequest request + ) { + return ResponseEntity.ok(adminUnivApplyInfoService.updateUnivApplyInfo(id, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteUnivApplyInfo( + @PathVariable long id + ) { + adminUnivApplyInfoService.deleteUnivApplyInfo(id); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/com/example/solidconnection/admin/university/dto/AdminUnivApplyInfoCreateRequest.java b/src/main/java/com/example/solidconnection/admin/university/dto/AdminUnivApplyInfoCreateRequest.java new file mode 100644 index 000000000..d6fbf0690 --- /dev/null +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminUnivApplyInfoCreateRequest.java @@ -0,0 +1,23 @@ +package com.example.solidconnection.admin.university.dto; + +import com.example.solidconnection.university.domain.SemesterAvailableForDispatch; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import java.util.List; +import java.util.Map; + +public record AdminUnivApplyInfoCreateRequest( + @NotNull Long termId, + @NotNull Long homeUniversityId, + @NotNull Long hostUniversityId, + Integer studentCapacity, + SemesterAvailableForDispatch semesterAvailableForDispatch, + String semesterRequirement, + String detailsForLanguage, + String gpaRequirement, + String gpaRequirementCriteria, + String detailsForAccommodation, + Map extraInfo, + @Valid List<@NotNull AdminUnivApplyInfoLanguageRequirementRequest> languageRequirements +) { +} diff --git a/src/main/java/com/example/solidconnection/admin/university/dto/AdminUnivApplyInfoLanguageRequirementRequest.java b/src/main/java/com/example/solidconnection/admin/university/dto/AdminUnivApplyInfoLanguageRequirementRequest.java new file mode 100644 index 000000000..bd078fbf8 --- /dev/null +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminUnivApplyInfoLanguageRequirementRequest.java @@ -0,0 +1,11 @@ +package com.example.solidconnection.admin.university.dto; + +import com.example.solidconnection.university.domain.LanguageTestType; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record AdminUnivApplyInfoLanguageRequirementRequest( + @NotNull LanguageTestType languageTestType, + @NotBlank String minScore +) { +} diff --git a/src/main/java/com/example/solidconnection/admin/university/dto/AdminUnivApplyInfoResponse.java b/src/main/java/com/example/solidconnection/admin/university/dto/AdminUnivApplyInfoResponse.java new file mode 100644 index 000000000..706811393 --- /dev/null +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminUnivApplyInfoResponse.java @@ -0,0 +1,47 @@ +package com.example.solidconnection.admin.university.dto; + +import com.example.solidconnection.university.domain.SemesterAvailableForDispatch; +import com.example.solidconnection.university.domain.UnivApplyInfo; +import com.example.solidconnection.university.dto.LanguageRequirementResponse; +import java.util.List; +import java.util.Map; + +public record AdminUnivApplyInfoResponse( + long id, + long termId, + Long homeUniversityId, + long hostUniversityId, + String koreanName, + Integer studentCapacity, + SemesterAvailableForDispatch semesterAvailableForDispatch, + String semesterRequirement, + String detailsForLanguage, + String gpaRequirement, + String gpaRequirementCriteria, + String detailsForAccommodation, + Map extraInfo, + List languageRequirements +) { + + public static AdminUnivApplyInfoResponse from(UnivApplyInfo univApplyInfo) { + return new AdminUnivApplyInfoResponse( + univApplyInfo.getId(), + univApplyInfo.getTermId(), + univApplyInfo.getHomeUniversity() != null ? univApplyInfo.getHomeUniversity().getId() : null, + univApplyInfo.getUniversity().getId(), + univApplyInfo.getKoreanName(), + univApplyInfo.getStudentCapacity(), + univApplyInfo.getSemesterAvailableForDispatch(), + univApplyInfo.getSemesterRequirement(), + univApplyInfo.getDetailsForLanguage(), + univApplyInfo.getGpaRequirement(), + univApplyInfo.getGpaRequirementCriteria(), + univApplyInfo.getDetailsForAccommodation(), + univApplyInfo.getExtraInfo(), + univApplyInfo.getLanguageRequirements().stream() + .map(LanguageRequirementResponse::from) + .sorted() + .toList() + ); + } +} diff --git a/src/main/java/com/example/solidconnection/admin/university/dto/AdminUnivApplyInfoUpdateRequest.java b/src/main/java/com/example/solidconnection/admin/university/dto/AdminUnivApplyInfoUpdateRequest.java new file mode 100644 index 000000000..3d7624cc0 --- /dev/null +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminUnivApplyInfoUpdateRequest.java @@ -0,0 +1,20 @@ +package com.example.solidconnection.admin.university.dto; + +import com.example.solidconnection.university.domain.SemesterAvailableForDispatch; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import java.util.List; +import java.util.Map; + +public record AdminUnivApplyInfoUpdateRequest( + Integer studentCapacity, + SemesterAvailableForDispatch semesterAvailableForDispatch, + String semesterRequirement, + String detailsForLanguage, + String gpaRequirement, + String gpaRequirementCriteria, + String detailsForAccommodation, + Map extraInfo, + @Valid List<@NotNull AdminUnivApplyInfoLanguageRequirementRequest> languageRequirements +) { +} diff --git a/src/main/java/com/example/solidconnection/admin/university/service/AdminUnivApplyInfoService.java b/src/main/java/com/example/solidconnection/admin/university/service/AdminUnivApplyInfoService.java index 630c301dc..389c09031 100644 --- a/src/main/java/com/example/solidconnection/admin/university/service/AdminUnivApplyInfoService.java +++ b/src/main/java/com/example/solidconnection/admin/university/service/AdminUnivApplyInfoService.java @@ -3,17 +3,31 @@ import static com.example.solidconnection.common.exception.ErrorCode.HOME_UNIVERSITY_NOT_FOUND; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_INPUT; import static com.example.solidconnection.common.exception.ErrorCode.TERM_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_HAS_REFERENCES; +import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_NOT_FOUND; +import com.example.solidconnection.admin.university.dto.AdminUnivApplyInfoCreateRequest; +import com.example.solidconnection.admin.university.dto.AdminUnivApplyInfoResponse; +import com.example.solidconnection.admin.university.dto.AdminUnivApplyInfoUpdateRequest; import com.example.solidconnection.admin.university.dto.UnivApplyInfoFieldResponse; import com.example.solidconnection.admin.university.dto.UnivApplyInfoImportRequest; import com.example.solidconnection.admin.university.dto.UnivApplyInfoImportResponse; +import com.example.solidconnection.application.repository.ApplicationRepository; import com.example.solidconnection.cache.annotation.DefaultCacheOut; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.util.MarkdownTableParser; import com.example.solidconnection.term.repository.TermRepository; import com.example.solidconnection.university.domain.HomeUniversity; +import com.example.solidconnection.university.domain.HostUniversity; +import com.example.solidconnection.university.domain.LanguageRequirement; +import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.repository.HomeUniversityRepository; +import com.example.solidconnection.university.repository.HostUniversityRepository; +import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; +import com.example.solidconnection.university.repository.UnivApplyInfoRepository; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; import lombok.RequiredArgsConstructor; @@ -28,6 +42,10 @@ public class AdminUnivApplyInfoService { private final HomeUniversityRepository homeUniversityRepository; private final MarkdownTableParser markdownTableParser; private final AdminUnivApplyInfoRowSaver rowSaver; + private final UnivApplyInfoRepository univApplyInfoRepository; + private final HostUniversityRepository hostUniversityRepository; + private final LikedUnivApplyInfoRepository likedUnivApplyInfoRepository; + private final ApplicationRepository applicationRepository; public UnivApplyInfoFieldResponse getFields() { return UnivApplyInfoFieldResponse.of(); @@ -75,4 +93,106 @@ private HomeUniversity findHomeUniversity(Long homeUniversityId) { return homeUniversityRepository.findById(homeUniversityId) .orElseThrow(() -> new CustomException(HOME_UNIVERSITY_NOT_FOUND)); } + + @Transactional + @DefaultCacheOut( + key = {"univApplyInfoTextSearch", "university:recommend:general"}, + cacheManager = "customCacheManager", + prefix = true + ) + public AdminUnivApplyInfoResponse createUnivApplyInfo(AdminUnivApplyInfoCreateRequest request) { + validateTermExists(request.termId()); + HomeUniversity homeUniversity = findHomeUniversity(request.homeUniversityId()); + HostUniversity hostUniversity = findHostUniversity(request.hostUniversityId()); + + UnivApplyInfo univApplyInfo = new UnivApplyInfo( + null, + request.termId(), + homeUniversity, + hostUniversity.getKoreanName(), + request.studentCapacity(), + request.semesterAvailableForDispatch(), + request.semesterRequirement(), + request.detailsForLanguage(), + request.gpaRequirement(), + request.gpaRequirementCriteria(), + request.detailsForAccommodation(), + request.extraInfo(), + new HashSet<>(), + hostUniversity + ); + + UnivApplyInfo saved = univApplyInfoRepository.save(univApplyInfo); + + if (request.languageRequirements() != null) { + request.languageRequirements().forEach(lr -> { + LanguageRequirement languageRequirement = new LanguageRequirement( + null, lr.languageTestType(), lr.minScore(), saved + ); + saved.addLanguageRequirements(languageRequirement); + }); + } + + return AdminUnivApplyInfoResponse.from(saved); + } + + private HostUniversity findHostUniversity(Long hostUniversityId) { + return hostUniversityRepository.findById(hostUniversityId) + .orElseThrow(() -> new CustomException(UNIVERSITY_NOT_FOUND)); + } + + @Transactional + @DefaultCacheOut( + key = {"univApplyInfoTextSearch", "university:recommend:general"}, + cacheManager = "customCacheManager", + prefix = true + ) + public AdminUnivApplyInfoResponse updateUnivApplyInfo(long id, AdminUnivApplyInfoUpdateRequest request) { + UnivApplyInfo univApplyInfo = univApplyInfoRepository.findById(id) + .orElseThrow(() -> new CustomException(UNIV_APPLY_INFO_NOT_FOUND)); + + univApplyInfo.update( + request.studentCapacity(), + request.semesterAvailableForDispatch(), + request.semesterRequirement(), + request.detailsForLanguage(), + request.gpaRequirement(), + request.gpaRequirementCriteria(), + request.detailsForAccommodation(), + request.extraInfo() + ); + + if (request.languageRequirements() != null) { + univApplyInfo.clearLanguageRequirements(); + request.languageRequirements().forEach(lr -> { + LanguageRequirement languageRequirement = new LanguageRequirement( + null, lr.languageTestType(), lr.minScore(), univApplyInfo + ); + univApplyInfo.addLanguageRequirements(languageRequirement); + }); + } + + return AdminUnivApplyInfoResponse.from(univApplyInfo); + } + + @Transactional + @DefaultCacheOut( + key = {"univApplyInfoTextSearch", "university:recommend:general"}, + cacheManager = "customCacheManager", + prefix = true + ) + public void deleteUnivApplyInfo(long id) { + UnivApplyInfo univApplyInfo = univApplyInfoRepository.findById(id) + .orElseThrow(() -> new CustomException(UNIV_APPLY_INFO_NOT_FOUND)); + validateNoReferences(id); + univApplyInfoRepository.delete(univApplyInfo); + } + + private void validateNoReferences(long id) { + if (likedUnivApplyInfoRepository.existsByUnivApplyInfoId(id) + || applicationRepository.existsByChoicesUnivApplyInfoId(id)) { + throw new CustomException(UNIV_APPLY_INFO_HAS_REFERENCES); + } + } + } diff --git a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java index 0324cb46d..306158fc2 100644 --- a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java +++ b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java @@ -36,5 +36,13 @@ default Application getApplicationBySiteUserIdAndTermId(long siteUserId, long te .orElseThrow(() -> new CustomException(APPLICATION_NOT_FOUND)); } + @Query(""" + SELECT CASE WHEN COUNT(a) > 0 THEN true ELSE false END + FROM Application a + JOIN a.choices c + WHERE c.univApplyInfoId = :univApplyInfoId + """) + boolean existsByChoicesUnivApplyInfoId(@Param("univApplyInfoId") long univApplyInfoId); + void deleteAllBySiteUserId(long siteUserId); } diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index b9ff8979e..214da0838 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -36,6 +36,7 @@ public enum ErrorCode { // data not found UNIV_APPLY_INFO_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 대학교 지원 정보입니다."), UNIV_APPLY_INFO_NOT_FOUND_FOR_TERM(HttpStatus.NOT_FOUND.value(), "해당하는 대학교가 이번 모집 기간에 열리지 않았습니다."), + UNIV_APPLY_INFO_HAS_REFERENCES(HttpStatus.CONFLICT.value(), "해당 대학 지원 정보를 참조하는 데이터가 존재합니다."), APPLICATION_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "사용자의 대학 지원 정보를 찾을 수 없습니다."), USER_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "회원을 찾을 수 없습니다."), UNIVERSITY_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "대학교를 찾을 수 없습니다."), diff --git a/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java b/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java index c6ea2a2ae..4d41c1b5b 100644 --- a/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java +++ b/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java @@ -86,9 +86,12 @@ public ResponseEntity getUnivApplyInfoDetails( @GetMapping("/search/text") public ResponseEntity searchUnivApplyInfoByText( - @RequestParam(required = false) String value + @RequestParam(required = false) String value, + @RequestParam(required = false) Long homeUniversityId, + @RequestParam(required = false) Long termId ) { - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(value); + UnivApplyInfoPreviewResponses response = + univApplyInfoQueryService.searchUnivApplyInfoByText(value, homeUniversityId, termId); return ResponseEntity.ok(response); } } diff --git a/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java b/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java index e8db34703..1f9a7c210 100644 --- a/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java +++ b/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java @@ -84,6 +84,30 @@ public void addLanguageRequirements(LanguageRequirement languageRequirements) { this.languageRequirements.add(languageRequirements); } + public void update( + Integer studentCapacity, + SemesterAvailableForDispatch semesterAvailableForDispatch, + String semesterRequirement, + String detailsForLanguage, + String gpaRequirement, + String gpaRequirementCriteria, + String detailsForAccommodation, + Map extraInfo + ) { + if (studentCapacity != null) this.studentCapacity = studentCapacity; + if (semesterAvailableForDispatch != null) this.semesterAvailableForDispatch = semesterAvailableForDispatch; + if (semesterRequirement != null) this.semesterRequirement = semesterRequirement; + if (detailsForLanguage != null) this.detailsForLanguage = detailsForLanguage; + if (gpaRequirement != null) this.gpaRequirement = gpaRequirement; + if (gpaRequirementCriteria != null) this.gpaRequirementCriteria = gpaRequirementCriteria; + if (detailsForAccommodation != null) this.detailsForAccommodation = detailsForAccommodation; + if (extraInfo != null) this.extraInfo = extraInfo; + } + + public void clearLanguageRequirements() { + this.languageRequirements.clear(); + } + public void updateExtraInfo(Map extraInfo) { this.extraInfo = extraInfo; } diff --git a/src/main/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepository.java b/src/main/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepository.java index e5a94d69a..4c412b638 100644 --- a/src/main/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepository.java @@ -31,5 +31,7 @@ public interface LikedUnivApplyInfoRepository extends JpaRepository findAllByRegionCodeAndKeywordsAndTermId(String regionCode, List keywords, Long term); - List findAllByText(String text, Long termId); + List findAllByText(String text, Long termId, Long homeUniversityId); } diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java index cfb5e3a7a..5921441eb 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java @@ -84,7 +84,7 @@ private BooleanExpression termIdEq(QUnivApplyInfo univApplyInfo, Long givenTermI } @Override - public List findAllByText(String text, Long termId) { + public List findAllByText(String text, Long termId, Long homeUniversityId) { QUnivApplyInfo univApplyInfo = QUnivApplyInfo.univApplyInfo; QHostUniversity university = QHostUniversity.hostUniversity; QHomeUniversity homeUniversity = QHomeUniversity.homeUniversity; @@ -98,7 +98,10 @@ public List findAllByText(String text, Long termId) { .join(region).on(country.regionCode.eq(region.code)) .leftJoin(univApplyInfo.homeUniversity, homeUniversity).fetchJoin() .leftJoin(univApplyInfo.languageRequirements, languageRequirement).fetchJoin() - .where(termIdEq(univApplyInfo, termId)); + .where( + termIdEq(univApplyInfo, termId), + homeUniversityIdEq(homeUniversity, homeUniversityId) + ); // text 가 비어있다면 모든 대학 지원 정보를 id 오름차순으로 정렬하여 반환 if (text == null || text.isBlank()) { @@ -126,4 +129,11 @@ public List findAllByText(String text, Long termId) { .orderBy(rank.asc(), univApplyInfo.id.asc()) .fetch(); } + + private BooleanExpression homeUniversityIdEq(QHomeUniversity homeUniversity, Long homeUniversityId) { + if (homeUniversityId == null) { + return null; + } + return homeUniversity.id.eq(homeUniversityId); + } } diff --git a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java index 386e0ecf2..72bbeab00 100644 --- a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java +++ b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java @@ -43,16 +43,28 @@ public UnivApplyInfoDetailResponse getUnivApplyInfoDetail(Long univApplyInfoId) @Transactional(readOnly = true) // todo: 현재 레디스 관련 에러 발생중으로 임시 주석처리, 추후 원인 분석 후 적용 필요 - @ThunderingHerdCaching(key = "univApplyInfoTextSearch:{0}", cacheManager = "customCacheManager", ttlSec = 86400) - public UnivApplyInfoPreviewResponses searchUnivApplyInfoByText(String text) { - Term term = termRepository.findByIsCurrentTrue() - .orElseThrow(() -> new CustomException(CURRENT_TERM_NOT_FOUND)); - - List univApplyInfos = univApplyInfoRepository.findAllByText(text, term.getId()); + @ThunderingHerdCaching(key = "univApplyInfoTextSearch:{0}:{1}:{2}", cacheManager = "customCacheManager", ttlSec = 86400) + public UnivApplyInfoPreviewResponses searchUnivApplyInfoByText( + String text, + Long homeUniversityId, + Long termId + ) { + Term term = resolveTerm(termId); + List univApplyInfos = + univApplyInfoRepository.findAllByText(text, term.getId(), homeUniversityId); List responses = univApplyInfos.stream() .map(univApplyInfo -> UnivApplyInfoPreviewResponse.of(univApplyInfo, term.getName())) .toList(); return new UnivApplyInfoPreviewResponses(responses); } + + private Term resolveTerm(Long termId) { + if (termId != null) { + return termRepository.findById(termId) + .orElseThrow(() -> new CustomException(TERM_NOT_FOUND)); + } + return termRepository.findByIsCurrentTrue() + .orElseThrow(() -> new CustomException(CURRENT_TERM_NOT_FOUND)); + } } diff --git a/src/test/java/com/example/solidconnection/admin/university/service/AdminUnivApplyInfoServiceTest.java b/src/test/java/com/example/solidconnection/admin/university/service/AdminUnivApplyInfoServiceTest.java index e654072f7..9f8f2c6dd 100644 --- a/src/test/java/com/example/solidconnection/admin/university/service/AdminUnivApplyInfoServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/university/service/AdminUnivApplyInfoServiceTest.java @@ -6,23 +6,39 @@ import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.times; +import com.example.solidconnection.admin.university.dto.AdminUnivApplyInfoCreateRequest; +import com.example.solidconnection.admin.university.dto.AdminUnivApplyInfoLanguageRequirementRequest; +import com.example.solidconnection.admin.university.dto.AdminUnivApplyInfoResponse; +import com.example.solidconnection.admin.university.dto.AdminUnivApplyInfoUpdateRequest; import com.example.solidconnection.admin.university.dto.UnivApplyInfoFieldResponse; import com.example.solidconnection.admin.university.dto.UnivApplyInfoImportRequest; import com.example.solidconnection.admin.university.dto.UnivApplyInfoImportResponse; +import com.example.solidconnection.application.domain.Gpa; +import com.example.solidconnection.application.domain.LanguageTest; +import com.example.solidconnection.application.fixture.ApplicationFixture; import com.example.solidconnection.cache.manager.CustomCacheManager; import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.term.domain.Term; import com.example.solidconnection.term.fixture.TermFixture; import com.example.solidconnection.university.domain.HomeUniversity; +import com.example.solidconnection.university.domain.HostUniversity; import com.example.solidconnection.university.domain.LanguageTestType; +import com.example.solidconnection.university.domain.LikedUnivApplyInfo; +import com.example.solidconnection.university.domain.SemesterAvailableForDispatch; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.domain.UnivApplyInfoColumn; import com.example.solidconnection.university.fixture.HomeUniversityFixture; +import com.example.solidconnection.university.fixture.UnivApplyInfoFixtureBuilder; import com.example.solidconnection.university.fixture.UniversityFixture; import com.example.solidconnection.university.repository.LanguageRequirementRepository; +import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; import java.util.Arrays; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -32,7 +48,7 @@ import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; @TestContainerSpringBootTest -@DisplayName("UnivApplyInfo 임포트 서비스 테스트") +@DisplayName("UnivApplyInfo 서비스 테스트") class AdminUnivApplyInfoServiceTest { @Autowired @@ -44,6 +60,9 @@ class AdminUnivApplyInfoServiceTest { @Autowired private LanguageRequirementRepository languageRequirementRepository; + @Autowired + private LikedUnivApplyInfoRepository likedUnivApplyInfoRepository; + @Autowired private TermFixture termFixture; @@ -53,11 +72,21 @@ class AdminUnivApplyInfoServiceTest { @Autowired private UniversityFixture universityFixture; + @Autowired + private UnivApplyInfoFixtureBuilder univApplyInfoFixtureBuilder; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private ApplicationFixture applicationFixture; + @MockitoSpyBean private CustomCacheManager cacheManager; private Term term; private HomeUniversity homeUniversity; + private HostUniversity hostUniversity; private static final String 괌_대학_한국명 = "괌 대학"; private static final String 버지니아_대학_한국명 = "버지니아 공과 대학"; @@ -67,7 +96,7 @@ class AdminUnivApplyInfoServiceTest { void setUp() { term = termFixture.현재_학기("2025-2"); homeUniversity = homeUniversityFixture.인하대학교(); - universityFixture.괌_대학(); + hostUniversity = universityFixture.괌_대학(); universityFixture.버지니아_공과_대학(); } @@ -394,4 +423,224 @@ class UnivApplyInfo를_임포트한다 { assertThat(univApplyInfoRepository.findAll()).isEmpty(); } } + + @Nested + class 지원_정보_생성 { + + @Test + void 유효한_요청으로_지원_정보를_생성하면_성공한다() { + // given + AdminUnivApplyInfoCreateRequest request = new AdminUnivApplyInfoCreateRequest( + term.getId(), homeUniversity.getId(), hostUniversity.getId(), + 5, SemesterAvailableForDispatch.ONE_SEMESTER, + "1학기 이상", "TOEIC 700 이상", "3.0 이상", "4.5", + "기숙사 제공", null, List.of() + ); + + // when + AdminUnivApplyInfoResponse response = adminUnivApplyInfoService.createUnivApplyInfo(request); + + // then + assertAll( + () -> assertThat(response.id()).isPositive(), + () -> assertThat(response.termId()).isEqualTo(term.getId()), + () -> assertThat(response.homeUniversityId()).isEqualTo(homeUniversity.getId()), + () -> assertThat(response.hostUniversityId()).isEqualTo(hostUniversity.getId()), + () -> assertThat(response.studentCapacity()).isEqualTo(5), + () -> assertThat(univApplyInfoRepository.findById(response.id())).isPresent() + ); + } + + @Test + void 언어_요건을_포함하여_생성하면_언어_요건도_저장된다() { + // given + var languageRequests = List.of( + new AdminUnivApplyInfoLanguageRequirementRequest(LanguageTestType.TOEIC, "700") + ); + AdminUnivApplyInfoCreateRequest request = new AdminUnivApplyInfoCreateRequest( + term.getId(), homeUniversity.getId(), hostUniversity.getId(), + null, null, null, null, null, null, null, null, languageRequests + ); + + // when + AdminUnivApplyInfoResponse response = adminUnivApplyInfoService.createUnivApplyInfo(request); + + // then + assertThat(response.languageRequirements()) + .hasSize(1) + .anyMatch(lr -> lr.languageTestType() == LanguageTestType.TOEIC + && "700".equals(lr.minScore())); + } + + @Test + void 존재하지_않는_termId로_생성하면_예외가_발생한다() { + // given + AdminUnivApplyInfoCreateRequest request = new AdminUnivApplyInfoCreateRequest( + invalidId, homeUniversity.getId(), hostUniversity.getId(), + null, null, null, null, null, null, null, null, List.of() + ); + + // when & then + assertThatCode(() -> adminUnivApplyInfoService.createUnivApplyInfo(request)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.TERM_NOT_FOUND.getMessage()); + } + + @Test + void 존재하지_않는_homeUniversityId로_생성하면_예외가_발생한다() { + // given + AdminUnivApplyInfoCreateRequest request = new AdminUnivApplyInfoCreateRequest( + term.getId(), invalidId, hostUniversity.getId(), + null, null, null, null, null, null, null, null, List.of() + ); + + // when & then + assertThatCode(() -> adminUnivApplyInfoService.createUnivApplyInfo(request)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.HOME_UNIVERSITY_NOT_FOUND.getMessage()); + } + + @Test + void 존재하지_않는_hostUniversityId로_생성하면_예외가_발생한다() { + // given + AdminUnivApplyInfoCreateRequest request = new AdminUnivApplyInfoCreateRequest( + term.getId(), homeUniversity.getId(), invalidId, + null, null, null, null, null, null, null, null, List.of() + ); + + // when & then + assertThatCode(() -> adminUnivApplyInfoService.createUnivApplyInfo(request)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.UNIVERSITY_NOT_FOUND.getMessage()); + } + } + + @Nested + class 지원_정보_수정 { + + @Test + void 유효한_요청으로_지원_정보를_수정하면_성공한다() { + // given + UnivApplyInfo univApplyInfo = univApplyInfoFixtureBuilder.univApplyInfo() + .termId(term.getId()).koreanName("괌대학(A형)") + .university(hostUniversity).homeUniversity(homeUniversity).create(); + AdminUnivApplyInfoUpdateRequest request = new AdminUnivApplyInfoUpdateRequest( + 10, SemesterAvailableForDispatch.TWO_SEMESTER, + "2학기 이상", "TOEFL 80 이상", "3.5 이상", "4.5", + "기숙사 없음", Map.of("비고", "테스트"), List.of() + ); + + // when + AdminUnivApplyInfoResponse response = adminUnivApplyInfoService.updateUnivApplyInfo(univApplyInfo.getId(), request); + + // then + assertAll( + () -> assertThat(response.studentCapacity()).isEqualTo(10), + () -> assertThat(response.semesterAvailableForDispatch()).isEqualTo(SemesterAvailableForDispatch.TWO_SEMESTER), + () -> assertThat(response.extraInfo()).containsEntry("비고", "테스트") + ); + } + + @Test + void 수정_시_언어_요건이_기존_것과_교체된다() { + // given + UnivApplyInfo univApplyInfo = univApplyInfoFixtureBuilder.univApplyInfo() + .termId(term.getId()).koreanName("괌대학(A형)") + .university(hostUniversity).homeUniversity(homeUniversity).create(); + var newLanguageRequirements = List.of( + new AdminUnivApplyInfoLanguageRequirementRequest(LanguageTestType.TOEFL_IBT, "80") + ); + AdminUnivApplyInfoUpdateRequest request = new AdminUnivApplyInfoUpdateRequest( + null, null, null, null, null, null, null, null, newLanguageRequirements + ); + + // when + AdminUnivApplyInfoResponse response = adminUnivApplyInfoService.updateUnivApplyInfo(univApplyInfo.getId(), request); + + // then + assertThat(response.languageRequirements()) + .hasSize(1) + .anyMatch(lr -> lr.languageTestType() == LanguageTestType.TOEFL_IBT + && "80".equals(lr.minScore())); + } + + @Test + void 존재하지_않는_id로_수정하면_예외가_발생한다() { + // given + AdminUnivApplyInfoUpdateRequest request = new AdminUnivApplyInfoUpdateRequest( + null, null, null, null, null, null, null, null, List.of() + ); + + // when & then + assertThatCode(() -> adminUnivApplyInfoService.updateUnivApplyInfo(invalidId, request)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.UNIV_APPLY_INFO_NOT_FOUND.getMessage()); + } + } + + @Nested + class 지원_정보_삭제 { + + @Test + void 참조가_없는_지원_정보를_삭제하면_성공한다() { + // given + UnivApplyInfo univApplyInfo = univApplyInfoFixtureBuilder.univApplyInfo() + .termId(term.getId()).koreanName("괌대학(A형)") + .university(hostUniversity).homeUniversity(homeUniversity).create(); + + // when + adminUnivApplyInfoService.deleteUnivApplyInfo(univApplyInfo.getId()); + + // then + assertThat(univApplyInfoRepository.findById(univApplyInfo.getId())).isEmpty(); + } + + @Test + void 존재하지_않는_id로_삭제하면_예외가_발생한다() { + // when & then + assertThatCode(() -> adminUnivApplyInfoService.deleteUnivApplyInfo(invalidId)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.UNIV_APPLY_INFO_NOT_FOUND.getMessage()); + } + + @Test + void LikedUnivApplyInfo가_참조하는_지원_정보를_삭제하면_예외가_발생한다() { + // given + UnivApplyInfo univApplyInfo = univApplyInfoFixtureBuilder.univApplyInfo() + .termId(term.getId()).koreanName("괌대학(A형)") + .university(hostUniversity).homeUniversity(homeUniversity).create(); + SiteUser siteUser = siteUserFixture.사용자(); + likedUnivApplyInfoRepository.save( + LikedUnivApplyInfo.builder() + .siteUserId(siteUser.getId()) + .univApplyInfoId(univApplyInfo.getId()) + .build() + ); + + // when & then + assertThatCode(() -> adminUnivApplyInfoService.deleteUnivApplyInfo(univApplyInfo.getId())) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.UNIV_APPLY_INFO_HAS_REFERENCES.getMessage()); + } + + @Test + void ApplicationChoice가_참조하는_지원_정보를_삭제하면_예외가_발생한다() { + // given + UnivApplyInfo univApplyInfo = univApplyInfoFixtureBuilder.univApplyInfo() + .termId(term.getId()).koreanName("괌대학(A형)") + .university(hostUniversity).homeUniversity(homeUniversity).create(); + SiteUser siteUser = siteUserFixture.사용자(); + applicationFixture.지원서( + siteUser, "테스트닉네임", term.getId(), + new Gpa(4.0, 4.5, "url"), + new LanguageTest(LanguageTestType.TOEIC, "800", "url"), + List.of(univApplyInfo.getId()) + ); + + // when & then + assertThatCode(() -> adminUnivApplyInfoService.deleteUnivApplyInfo(univApplyInfo.getId())) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.UNIV_APPLY_INFO_HAS_REFERENCES.getMessage()); + } + } } diff --git a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java index 32e59121d..f39762ba8 100644 --- a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java @@ -101,7 +101,7 @@ class 대학_지원_정보_텍스트_검색 { UnivApplyInfo 메이지대학_지원_정보 = univApplyInfoFixture.메이지대학_지원_정보(term.getId()); // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(null); + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(null, null, null); // then assertThat(response.univApplyInfoPreviews()) @@ -120,7 +120,7 @@ class 대학_지원_정보_텍스트_검색 { UnivApplyInfo 대학지원정보_아 = univApplyInfoFixture.아칸소주립대학_지원_정보(term.getId()); // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text); + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text, null, null); // then assertThat(response.univApplyInfoPreviews()) @@ -139,8 +139,8 @@ class 대학_지원_정보_텍스트_검색 { UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(term.getId()); // when - UnivApplyInfoPreviewResponses firstResponse = univApplyInfoQueryService.searchUnivApplyInfoByText(text); - UnivApplyInfoPreviewResponses secondResponse = univApplyInfoQueryService.searchUnivApplyInfoByText(text); + UnivApplyInfoPreviewResponses firstResponse = univApplyInfoQueryService.searchUnivApplyInfoByText(text, null, null); + UnivApplyInfoPreviewResponses secondResponse = univApplyInfoQueryService.searchUnivApplyInfoByText(text, null, null); // then assertThatCode(() -> { @@ -148,7 +148,7 @@ class 대학_지원_정보_텍스트_검색 { List secondResponseIds = extractIds(secondResponse); assertThat(firstResponseIds).isEqualTo(secondResponseIds); }).doesNotThrowAnyException(); - then(univApplyInfoRepository).should(times(1)).findAllByText(text, term.getId()); + then(univApplyInfoRepository).should(times(1)).findAllByText(text, term.getId(), null); } private List extractIds(UnivApplyInfoPreviewResponses responses) { @@ -170,7 +170,7 @@ class 각각의_검색_대상에_대해_검색한다 { univApplyInfoFixture.괌대학_A_지원_정보(term.getId()); // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text); + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text, null, null); // then assertThat(response.univApplyInfoPreviews()) @@ -189,7 +189,7 @@ class 각각의_검색_대상에_대해_검색한다 { univApplyInfoFixture.메이지대학_지원_정보(term.getId()); // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text); + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text, null, null); // then assertThat(response.univApplyInfoPreviews()) @@ -208,7 +208,7 @@ class 각각의_검색_대상에_대해_검색한다 { univApplyInfoFixture.메이지대학_지원_정보(term.getId()); // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text); + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text, null, null); // then assertThat(response.univApplyInfoPreviews()) @@ -218,5 +218,71 @@ class 각각의_검색_대상에_대해_검색한다 { ); } } + + @Nested + class homeUniversityId_필터링 { + + @Test + void homeUniversityId를_지정하면_해당_국내대학의_지원_정보만_반환한다() { + // given + UnivApplyInfo 괌대학_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(term.getId()); + Long homeUniversityId = 괌대학_지원_정보.getHomeUniversity().getId(); + + // when + UnivApplyInfoPreviewResponses response = + univApplyInfoQueryService.searchUnivApplyInfoByText(null, homeUniversityId, null); + + // then + assertThat(response.univApplyInfoPreviews()) + .hasSize(1) + .allMatch(r -> r.id() == 괌대학_지원_정보.getId()); + } + + @Test + void homeUniversityId가_null이면_전체_지원_정보를_반환한다() { + // given + univApplyInfoFixture.괌대학_A_지원_정보(term.getId()); + univApplyInfoFixture.메이지대학_지원_정보(term.getId()); + + // when + UnivApplyInfoPreviewResponses response = + univApplyInfoQueryService.searchUnivApplyInfoByText(null, null, null); + + // then + assertThat(response.univApplyInfoPreviews()).hasSize(2); + } + } + + @Nested + class termId_필터링 { + + @Test + void termId를_지정하면_해당_학기의_지원_정보만_반환한다() { + // given + Term 다른학기 = termFixture.이전_학기("2024-2"); + univApplyInfoFixture.괌대학_A_지원_정보(term.getId()); + univApplyInfoFixture.메이지대학_지원_정보(다른학기.getId()); + + // when + UnivApplyInfoPreviewResponses response = + univApplyInfoQueryService.searchUnivApplyInfoByText(null, null, 다른학기.getId()); + + // then + assertThat(response.univApplyInfoPreviews()).hasSize(1); + } + + @Test + void termId가_null이면_현재_학기를_사용한다() { + // given + univApplyInfoFixture.괌대학_A_지원_정보(term.getId()); + + // when + UnivApplyInfoPreviewResponses response = + univApplyInfoQueryService.searchUnivApplyInfoByText(null, null, null); + + // then + assertThat(response.univApplyInfoPreviews()).hasSize(1); + } + } } }