diff --git a/src/main/java/com/gdsc_knu/official_homepage/controller/ApplicationController.java b/src/main/java/com/gdsc_knu/official_homepage/controller/ApplicationController.java index 168fde83..00b4dda2 100644 --- a/src/main/java/com/gdsc_knu/official_homepage/controller/ApplicationController.java +++ b/src/main/java/com/gdsc_knu/official_homepage/controller/ApplicationController.java @@ -28,9 +28,12 @@ public class ApplicationController { 최종제출 상태의 지원서는 조회가 불가능합니다. """) - public ResponseEntity getApplication(@TokenMember JwtMemberDetail jwtMemberDetail, @RequestParam String name, @RequestParam String studentNumber) { + public ResponseEntity getApplication(@TokenMember JwtMemberDetail jwtMemberDetail, + @RequestParam String name, + @RequestParam String studentNumber, + @RequestParam Long classYearId) { return ResponseEntity.ok() - .body(applicationService.getApplication(jwtMemberDetail.getEmail(), name, studentNumber)); + .body(applicationService.getApplication(jwtMemberDetail.getEmail(), name, studentNumber, classYearId)); } @PostMapping diff --git a/src/main/java/com/gdsc_knu/official_homepage/controller/admin/AdminApplicationController.java b/src/main/java/com/gdsc_knu/official_homepage/controller/admin/AdminApplicationController.java index a9973888..89811858 100644 --- a/src/main/java/com/gdsc_knu/official_homepage/controller/admin/AdminApplicationController.java +++ b/src/main/java/com/gdsc_knu/official_homepage/controller/admin/AdminApplicationController.java @@ -45,15 +45,16 @@ public ResponseEntity> getTrackStatistic() { 마크된것만 조회하려면 isMarked=true로 설정하세요. - isMarked를 비워두거나 false로 설정하면 전체가 조회됩니다.(track도 마찬가지로 비워두면 전체)""") + isMarked를 비워두거나 false로 설정하면 전체가 조회됩니다.(track, classYearId도 마찬가지로 비워두면 전체)""") public ResponseEntity> getApplicationListByOption( @RequestParam(value = "track", required = false) Track track, @RequestParam(value = "isMarked", required = false, defaultValue = "false") Boolean isMarked, @RequestParam(value = "page", defaultValue = "0") int page, - @RequestParam(value = "size", defaultValue = "10") int size) + @RequestParam(value = "size", defaultValue = "10") int size, + @RequestParam(value = "classYearId", required = false) Long classYearId) { - return ResponseEntity.ok().body(applicationService.getApplicationsByOption(page, size, track, isMarked)); + return ResponseEntity.ok().body(applicationService.getApplicationsByOption(page, size, track, isMarked, classYearId)); } @@ -112,7 +113,4 @@ public ResponseEntity noteApplication(@RequestParam("id") Long id, applicationService.noteApplication(id, request.getNote(), request.getVersion()); return new ResponseEntity<>(HttpStatus.OK); } - - - } diff --git a/src/main/java/com/gdsc_knu/official_homepage/controller/admin/AdminClassYearController.java b/src/main/java/com/gdsc_knu/official_homepage/controller/admin/AdminClassYearController.java new file mode 100644 index 00000000..51653d0b --- /dev/null +++ b/src/main/java/com/gdsc_knu/official_homepage/controller/admin/AdminClassYearController.java @@ -0,0 +1,55 @@ +package com.gdsc_knu.official_homepage.controller.admin; + +import com.gdsc_knu.official_homepage.dto.admin.application.AdminApplicationRequest; +import com.gdsc_knu.official_homepage.dto.admin.application.AdminApplicationResponse; +import com.gdsc_knu.official_homepage.service.admin.AdminClassYearService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "Admin ClassYear", description = "기수 관리 관련 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("api/admin/class-year") +public class AdminClassYearController { + private final AdminClassYearService classYearService; + + @GetMapping() + @Operation(summary="기수 목록 조회 API", description = "기수 목록을 조회합니다.") + public ResponseEntity> getClassYearList() { + return ResponseEntity.ok(classYearService.getClassYearList()); + } + + @GetMapping("/{id}") + @Operation(summary="기수 단건 조회 API", description = "기수를 단건 조회합니다.") + public ResponseEntity getClassYear(@PathVariable("id") Long id) { + return ResponseEntity.ok(classYearService.getClassYear(id)); + } + + @PostMapping() + @Operation(summary="기수 추가 API", description = "기수를 추가합니다.") + public ResponseEntity addClassYear(@RequestBody AdminApplicationRequest.ClassYearRequest classYearRequest) { + classYearService.addClassYear(classYearRequest); + return new ResponseEntity<>(HttpStatus.CREATED); + } + + @PutMapping() + @Operation(summary="기수 수정 API", description = "기수의 정보를 수정합니다.") + public ResponseEntity updateClassYear(@RequestParam("id") Long id, + @RequestBody AdminApplicationRequest.ClassYearRequest classYearRequest) { + classYearService.updateClassYear(id, classYearRequest); + return new ResponseEntity<>(HttpStatus.OK); + } + + @DeleteMapping() + @Operation(summary="기수 삭제 API", description = "기수를 삭제 합니다.") + public ResponseEntity deleteClassYear(@RequestParam("id") Long id) { + classYearService.deleteClassYear(id); + return new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/src/main/java/com/gdsc_knu/official_homepage/dto/admin/application/AdminApplicationRequest.java b/src/main/java/com/gdsc_knu/official_homepage/dto/admin/application/AdminApplicationRequest.java index fcee19a2..fec2da9b 100644 --- a/src/main/java/com/gdsc_knu/official_homepage/dto/admin/application/AdminApplicationRequest.java +++ b/src/main/java/com/gdsc_knu/official_homepage/dto/admin/application/AdminApplicationRequest.java @@ -1,9 +1,17 @@ package com.gdsc_knu.official_homepage.dto.admin.application; - +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.gdsc_knu.official_homepage.entity.ClassYear; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDateTime; + public class AdminApplicationRequest { @Getter @AllArgsConstructor @@ -12,4 +20,28 @@ public static class Append { private String note; private Integer version; } -} + + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class ClassYearRequest { + private String name; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + private LocalDateTime applyStartDateTime; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + private LocalDateTime applyEndDateTime; + + public ClassYear toEntity() { + return ClassYear.builder() + .name(name) + .applicationStartDateTime(applyStartDateTime) + .applicationEndDateTime(applyEndDateTime) + .build(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gdsc_knu/official_homepage/dto/admin/application/AdminApplicationResponse.java b/src/main/java/com/gdsc_knu/official_homepage/dto/admin/application/AdminApplicationResponse.java index 26dfb272..3196b481 100644 --- a/src/main/java/com/gdsc_knu/official_homepage/dto/admin/application/AdminApplicationResponse.java +++ b/src/main/java/com/gdsc_knu/official_homepage/dto/admin/application/AdminApplicationResponse.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.gdsc_knu.official_homepage.entity.ClassYear; import com.gdsc_knu.official_homepage.entity.application.Application; import com.gdsc_knu.official_homepage.entity.application.ApplicationAnswer; import lombok.AllArgsConstructor; @@ -60,7 +61,7 @@ public static Overview from(Application application){ return Overview.builder() .id(application.getId()) .name(application.getName()) - .submittedAt(application.getModifiedAt()) + .submittedAt(application.getSubmittedAt()) .studentNumber(application.getStudentNumber()) .major(application.getMajor()) .track(application.getTrack().name()) @@ -104,7 +105,7 @@ public static Detail from(Application application){ .phoneNumber(application.getPhoneNumber()) .email(application.getEmail()) .track(application.getTrack().name()) - .submittedAt(application.getModifiedAt()) + .submittedAt(application.getSubmittedAt()) .techStack(application.getTechStack()) .link(application.getLinks()) .isMarked(application.isMarked()) @@ -147,4 +148,30 @@ public static Result from(HttpStatus status, String message) { .build(); } } + + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class ClassYearResponse { + private Long id; + private String name; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + private LocalDateTime applyStartDateTime; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + private LocalDateTime applyEndDateTime; + + public static AdminApplicationResponse.ClassYearResponse from(ClassYear classYear) { + return AdminApplicationResponse.ClassYearResponse.builder() + .id(classYear.getId()) + .name(classYear.getName()) + .applyStartDateTime(classYear.getApplicationStartDateTime()) + .applyEndDateTime(classYear.getApplicationEndDateTime()) + .build(); + } + } } diff --git a/src/main/java/com/gdsc_knu/official_homepage/dto/application/ApplicationModel.java b/src/main/java/com/gdsc_knu/official_homepage/dto/application/ApplicationModel.java new file mode 100644 index 00000000..d3b729b8 --- /dev/null +++ b/src/main/java/com/gdsc_knu/official_homepage/dto/application/ApplicationModel.java @@ -0,0 +1,30 @@ +package com.gdsc_knu.official_homepage.dto.application; + +import com.gdsc_knu.official_homepage.entity.enumeration.ApplicationStatus; +import com.gdsc_knu.official_homepage.entity.enumeration.Track; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@AllArgsConstructor +@Builder +@NoArgsConstructor +public class ApplicationModel { + private String techStack; + private String links; + private ApplicationStatus applicationStatus; + private Track track; + private List answers; + + public ApplicationModel(ApplicationRequest applicationRequest) { + this.techStack = applicationRequest.getTechStack(); + this.links = applicationRequest.getLinks(); + this.applicationStatus = applicationRequest.getApplicationStatus(); + this.track = applicationRequest.getTrack(); + this.answers = applicationRequest.getAnswers(); + } +} diff --git a/src/main/java/com/gdsc_knu/official_homepage/dto/application/ApplicationRequest.java b/src/main/java/com/gdsc_knu/official_homepage/dto/application/ApplicationRequest.java index 7a7a3d35..937ffd26 100644 --- a/src/main/java/com/gdsc_knu/official_homepage/dto/application/ApplicationRequest.java +++ b/src/main/java/com/gdsc_knu/official_homepage/dto/application/ApplicationRequest.java @@ -16,6 +16,9 @@ @Builder @Getter public class ApplicationRequest { + @NotNull(message = "기수 정보는 필수입니다.") + private Long classYearId; + @NotNull(message = "잘못된 입력 형식입니다.") private String techStack; diff --git a/src/main/java/com/gdsc_knu/official_homepage/entity/ClassYear.java b/src/main/java/com/gdsc_knu/official_homepage/entity/ClassYear.java new file mode 100644 index 00000000..645de9ed --- /dev/null +++ b/src/main/java/com/gdsc_knu/official_homepage/entity/ClassYear.java @@ -0,0 +1,34 @@ +package com.gdsc_knu.official_homepage.entity; + +import com.gdsc_knu.official_homepage.entity.application.Application; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ClassYear { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String name; + + @Column(nullable = false) + private LocalDateTime applicationStartDateTime; + + @Column(nullable = false) + private LocalDateTime applicationEndDateTime; + + public void update(String name, LocalDateTime applicationStartDateTime, LocalDateTime applicationEndDateTime) { + this.name = name; + this.applicationStartDateTime = applicationStartDateTime; + this.applicationEndDateTime = applicationEndDateTime; + } +} diff --git a/src/main/java/com/gdsc_knu/official_homepage/entity/application/Application.java b/src/main/java/com/gdsc_knu/official_homepage/entity/application/Application.java index 4a77cc29..fbb52f11 100644 --- a/src/main/java/com/gdsc_knu/official_homepage/entity/application/Application.java +++ b/src/main/java/com/gdsc_knu/official_homepage/entity/application/Application.java @@ -1,8 +1,8 @@ package com.gdsc_knu.official_homepage.entity.application; import com.gdsc_knu.official_homepage.dto.application.ApplicationAnswerDTO; -import com.gdsc_knu.official_homepage.dto.application.ApplicationRequest; -import com.gdsc_knu.official_homepage.entity.BaseTimeEntity; +import com.gdsc_knu.official_homepage.dto.application.ApplicationModel; +import com.gdsc_knu.official_homepage.entity.ClassYear; import com.gdsc_knu.official_homepage.entity.Member; import com.gdsc_knu.official_homepage.entity.enumeration.ApplicationStatus; import com.gdsc_knu.official_homepage.entity.enumeration.Track; @@ -11,6 +11,7 @@ import jakarta.persistence.*; import lombok.*; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -21,7 +22,7 @@ @Builder @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Application extends BaseTimeEntity { +public class Application { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -31,21 +32,24 @@ public class Application extends BaseTimeEntity { private String name; - @Column(unique = true) private String studentNumber; private String major; - @Column(unique = true) private String email; - @Column(unique = true) private String phoneNumber; private String techStack; private String links; + private LocalDateTime submittedAt; + + @ManyToOne + @JoinColumn(name = "class_year_id") + private ClassYear classYear; + @Enumerated(EnumType.STRING) private ApplicationStatus applicationStatus; @@ -65,35 +69,36 @@ public class Application extends BaseTimeEntity { @OneToMany(mappedBy = "application", cascade = CascadeType.ALL, orphanRemoval = true) private List answers = new ArrayList<>(); - // TODO : presentation 계층과 의존 제거 - public Application(Member member, ApplicationRequest applicationRequest) { + public Application(Member member, ApplicationModel applicationModel) { this.name = member.getName(); this.studentNumber = member.getStudentNumber(); this.major = member.getMajor(); this.email = member.getEmail(); this.phoneNumber = member.getPhoneNumber(); - this.techStack = applicationRequest.getTechStack(); - this.links = applicationRequest.getLinks(); - this.applicationStatus = applicationRequest.getApplicationStatus(); - this.track = applicationRequest.getTrack(); - this.answers = applicationRequest.getAnswers().stream() + this.techStack = applicationModel.getTechStack(); + this.links = applicationModel.getLinks(); + this.applicationStatus = applicationModel.getApplicationStatus(); + this.track = applicationModel.getTrack(); + this.answers = applicationModel.getAnswers().stream() .map(answers -> ApplicationAnswer.builder() .questionNumber(answers.getQuestionNumber()) .answer(answers.getAnswer()) .build()).toList(); + this.submittedAt = LocalDateTime.now(); } - public void updateApplication(Member member, ApplicationRequest applicationRequest) { + public void updateApplication(Member member, ApplicationModel applicationModel) { this.name = member.getName(); this.studentNumber = member.getStudentNumber(); this.major = member.getMajor(); this.email = member.getEmail(); this.phoneNumber = member.getPhoneNumber(); - this.techStack = applicationRequest.getTechStack(); - this.links = applicationRequest.getLinks(); - this.applicationStatus = applicationRequest.getApplicationStatus(); - this.track = applicationRequest.getTrack(); - updateNewAnswers(applicationRequest.getAnswers()); + this.techStack = applicationModel.getTechStack(); + this.links = applicationModel.getLinks(); + this.applicationStatus = applicationModel.getApplicationStatus(); + this.track = applicationModel.getTrack(); + updateNewAnswers(applicationModel.getAnswers()); + this.submittedAt = LocalDateTime.now(); } private void updateNewAnswers(List newAnswersDTO) { @@ -131,4 +136,8 @@ public void saveNote(String note, Integer version) { throw new CustomException(ErrorCode.CONCURRENT_FAILED); this.note = note; } + + public void updateClassYear(ClassYear classYear) { + this.classYear = classYear; + } } diff --git a/src/main/java/com/gdsc_knu/official_homepage/exception/ErrorCode.java b/src/main/java/com/gdsc_knu/official_homepage/exception/ErrorCode.java index b92cb91d..ee3646ab 100644 --- a/src/main/java/com/gdsc_knu/official_homepage/exception/ErrorCode.java +++ b/src/main/java/com/gdsc_knu/official_homepage/exception/ErrorCode.java @@ -26,8 +26,13 @@ public enum ErrorCode { // Application APPLICATION_NOT_FOUND(404,HttpStatus.NOT_FOUND, "지원서를 찾을 수 없습니다","A404"), - CONFLICT(409, HttpStatus.CONFLICT, "이미 최종 제출된 지원서 입니다.","A409"), + APPLICATION_DUPLICATED(409, HttpStatus.CONFLICT, "이미 작성 중이거나 최종 제출한 지원서가 존재합니다.","A409"), INVALID_APPLICATION_STATE(400, HttpStatus.BAD_REQUEST,"지원서 상태가 유효하지 않습니다.","A400"), + APPLICATION_DEADLINE_EXPIRED(400, HttpStatus.BAD_REQUEST,"지원 기간이 만료되었습니다.","AA400"), + CLASS_YEAR_NOT_FOUND(404, HttpStatus.NOT_FOUND,"존재하지 않는 기수입니다.","AC404"), + INVALID_CLASS_YEAR(400, HttpStatus.BAD_REQUEST,"기수 정보 설정이 잘못 되었습니다.","AC400"), + CLASS_YEAR_DUPLICATED(409, HttpStatus.CONFLICT,"이미 존재하는 기수 이름입니다.","AC409"), + APPLICATION_FORBIDDEN(403, HttpStatus.FORBIDDEN, "지원서에 접근할 수 있는 권한이 없습니다.","A403"), CONCURRENT_FAILED(409, HttpStatus.CONFLICT, "다른 사용자가 수정중입니다.","AN409"), // Team diff --git a/src/main/java/com/gdsc_knu/official_homepage/repository/application/ApplicationQueryFactory.java b/src/main/java/com/gdsc_knu/official_homepage/repository/application/ApplicationQueryFactory.java index 36b54c83..13f5b245 100644 --- a/src/main/java/com/gdsc_knu/official_homepage/repository/application/ApplicationQueryFactory.java +++ b/src/main/java/com/gdsc_knu/official_homepage/repository/application/ApplicationQueryFactory.java @@ -6,5 +6,5 @@ import org.springframework.data.domain.Pageable; public interface ApplicationQueryFactory { - Page findAllApplicationsByOption(Pageable pageable, Track track, Boolean isMarked); + Page findAllApplicationsByOption(Pageable pageable, Track track, Boolean isMarked, Long classYearId); } diff --git a/src/main/java/com/gdsc_knu/official_homepage/repository/application/ApplicationQueryFactoryImpl.java b/src/main/java/com/gdsc_knu/official_homepage/repository/application/ApplicationQueryFactoryImpl.java index 25f4475b..b6d74eef 100644 --- a/src/main/java/com/gdsc_knu/official_homepage/repository/application/ApplicationQueryFactoryImpl.java +++ b/src/main/java/com/gdsc_knu/official_homepage/repository/application/ApplicationQueryFactoryImpl.java @@ -20,12 +20,13 @@ public class ApplicationQueryFactoryImpl implements ApplicationQueryFactory{ private final JPAQueryFactory jpaQueryFactory; @Override - public Page findAllApplicationsByOption(Pageable pageable, Track track, Boolean isMarked) { + public Page findAllApplicationsByOption(Pageable pageable, Track track, Boolean isMarked, Long classYearId) { List applications = jpaQueryFactory .selectFrom(QApplication.application) .where(QApplication.application.applicationStatus.ne(ApplicationStatus.TEMPORAL) .and(eqTrack(track)) - .and(eqIsMarked(isMarked))) + .and(eqIsMarked(isMarked)) + .and(eqClassYear(classYearId))) .orderBy(QApplication.application.id.desc()) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) @@ -50,4 +51,8 @@ private BooleanExpression eqTrack(Track track) { private BooleanExpression eqIsMarked(Boolean isMarked) { return (isMarked == null || !isMarked) ? null : QApplication.application.isMarked.eq(true); } + + private BooleanExpression eqClassYear(Long classYearId) { + return classYearId == null ? null : QApplication.application.classYear.id.eq(classYearId); + } } diff --git a/src/main/java/com/gdsc_knu/official_homepage/repository/application/ApplicationRepository.java b/src/main/java/com/gdsc_knu/official_homepage/repository/application/ApplicationRepository.java index 8a5e37b9..bf9d002f 100644 --- a/src/main/java/com/gdsc_knu/official_homepage/repository/application/ApplicationRepository.java +++ b/src/main/java/com/gdsc_knu/official_homepage/repository/application/ApplicationRepository.java @@ -16,7 +16,7 @@ @Repository public interface ApplicationRepository extends JpaRepository, ApplicationQueryFactory{ - Optional findByNameAndStudentNumber(String name, String studentNumber); + Optional findByNameAndStudentNumberAndClassYearId(String name, String studentNumber, Long classYearId); Optional findByStudentNumber(String studentNumber); @Query("SELECT a " + "FROM Application a LEFT JOIN FETCH a.answers "+ diff --git a/src/main/java/com/gdsc_knu/official_homepage/repository/application/ClassYearRepository.java b/src/main/java/com/gdsc_knu/official_homepage/repository/application/ClassYearRepository.java new file mode 100644 index 00000000..40fd5a15 --- /dev/null +++ b/src/main/java/com/gdsc_knu/official_homepage/repository/application/ClassYearRepository.java @@ -0,0 +1,10 @@ +package com.gdsc_knu.official_homepage.repository.application; + +import com.gdsc_knu.official_homepage.entity.ClassYear; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface ClassYearRepository extends JpaRepository { + Optional findByName(String name); +} diff --git a/src/main/java/com/gdsc_knu/official_homepage/service/admin/AdminApplicationService.java b/src/main/java/com/gdsc_knu/official_homepage/service/admin/AdminApplicationService.java index 10481120..1ee7a477 100644 --- a/src/main/java/com/gdsc_knu/official_homepage/service/admin/AdminApplicationService.java +++ b/src/main/java/com/gdsc_knu/official_homepage/service/admin/AdminApplicationService.java @@ -1,17 +1,21 @@ package com.gdsc_knu.official_homepage.service.admin; import com.gdsc_knu.official_homepage.dto.PagingResponse; +import com.gdsc_knu.official_homepage.dto.admin.application.AdminApplicationRequest; import com.gdsc_knu.official_homepage.dto.admin.application.AdminApplicationResponse; import com.gdsc_knu.official_homepage.dto.admin.application.ApplicationStatisticType; import com.gdsc_knu.official_homepage.dto.admin.application.ApplicationTrackType; +import com.gdsc_knu.official_homepage.entity.ClassYear; import com.gdsc_knu.official_homepage.entity.application.Application; import com.gdsc_knu.official_homepage.entity.enumeration.ApplicationStatus; import com.gdsc_knu.official_homepage.entity.enumeration.Track; import com.gdsc_knu.official_homepage.exception.CustomException; import com.gdsc_knu.official_homepage.exception.ErrorCode; import com.gdsc_knu.official_homepage.repository.application.ApplicationRepository; +import com.gdsc_knu.official_homepage.repository.application.ClassYearRepository; import com.gdsc_knu.official_homepage.service.MailService; import lombok.RequiredArgsConstructor; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.orm.ObjectOptimisticLockingFailureException; @@ -19,7 +23,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionTemplate; - import java.util.Arrays; import java.util.List; import java.util.Map; @@ -31,6 +34,7 @@ public class AdminApplicationService { private final ApplicationRepository applicationRepository; private final MailService mailService; private final TransactionTemplate transactionTemplate; + private final ClassYearRepository classYearRepository; @@ -67,9 +71,9 @@ private void addTotalCount(Map trackCountMap){ @Transactional(readOnly = true) - public PagingResponse getApplicationsByOption(int page, int size, Track track, Boolean isMarked){ + public PagingResponse getApplicationsByOption(int page, int size, Track track, Boolean isMarked, Long classYearId){ Page applicationPage - = applicationRepository.findAllApplicationsByOption(PageRequest.of(page,size), track, isMarked); + = applicationRepository.findAllApplicationsByOption(PageRequest.of(page,size), track, isMarked, classYearId); return PagingResponse.from(applicationPage, AdminApplicationResponse.Overview::from); } diff --git a/src/main/java/com/gdsc_knu/official_homepage/service/admin/AdminClassYearService.java b/src/main/java/com/gdsc_knu/official_homepage/service/admin/AdminClassYearService.java new file mode 100644 index 00000000..c5759990 --- /dev/null +++ b/src/main/java/com/gdsc_knu/official_homepage/service/admin/AdminClassYearService.java @@ -0,0 +1,65 @@ +package com.gdsc_knu.official_homepage.service.admin; + +import com.gdsc_knu.official_homepage.dto.admin.application.AdminApplicationRequest; +import com.gdsc_knu.official_homepage.dto.admin.application.AdminApplicationResponse; +import com.gdsc_knu.official_homepage.entity.ClassYear; +import com.gdsc_knu.official_homepage.exception.CustomException; +import com.gdsc_knu.official_homepage.exception.ErrorCode; +import com.gdsc_knu.official_homepage.repository.application.ClassYearRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class AdminClassYearService { + private final ClassYearRepository classYearRepository; + + @Transactional(readOnly = true) + public List getClassYearList() { + return classYearRepository.findAll().stream().map(AdminApplicationResponse.ClassYearResponse::from).toList(); + } + + @Transactional(readOnly = true) + public AdminApplicationResponse.ClassYearResponse getClassYear(Long id) { + ClassYear classYear = classYearRepository.findById(id) + .orElseThrow(() -> new CustomException(ErrorCode.CLASS_YEAR_NOT_FOUND)); + return AdminApplicationResponse.ClassYearResponse.from(classYear); + } + + @Transactional + public void addClassYear(AdminApplicationRequest.ClassYearRequest request) { + validateClassYear(request); + classYearRepository.save(request.toEntity()); + } + + @Transactional + public void updateClassYear(Long id, AdminApplicationRequest.ClassYearRequest request) { + ClassYear classYear = classYearRepository.findById(id) + .orElseThrow(() -> new CustomException(ErrorCode.CLASS_YEAR_NOT_FOUND)); + validateClassYear(request); + classYear.update(request.getName(), request.getApplyStartDateTime(), request.getApplyEndDateTime()); + } + + @Transactional + public void deleteClassYear(Long id) { + try { + classYearRepository.deleteById(id); + } catch (EmptyResultDataAccessException e) { + throw new CustomException(ErrorCode.CLASS_YEAR_NOT_FOUND); + } + } + + private void validateClassYear(AdminApplicationRequest.ClassYearRequest request) { + classYearRepository.findByName(request.getName()) + .ifPresent(classYear -> { + throw new CustomException(ErrorCode.CLASS_YEAR_DUPLICATED); + }); + if (request.getApplyStartDateTime().isAfter(request.getApplyEndDateTime()) || request.getApplyEndDateTime().isBefore(request.getApplyStartDateTime())) { + throw new CustomException(ErrorCode.INVALID_CLASS_YEAR); + } + } +} diff --git a/src/main/java/com/gdsc_knu/official_homepage/service/application/ApplicationService.java b/src/main/java/com/gdsc_knu/official_homepage/service/application/ApplicationService.java index 0ce425a0..c2618a94 100644 --- a/src/main/java/com/gdsc_knu/official_homepage/service/application/ApplicationService.java +++ b/src/main/java/com/gdsc_knu/official_homepage/service/application/ApplicationService.java @@ -4,7 +4,7 @@ import com.gdsc_knu.official_homepage.dto.application.ApplicationResponse; public interface ApplicationService { - ApplicationResponse getApplication(String email, String name, String studentNumber); + ApplicationResponse getApplication(String email, String name, String studentNumber, Long classYearId); Long saveApplication(String email, ApplicationRequest applicationRequest); diff --git a/src/main/java/com/gdsc_knu/official_homepage/service/application/ApplicationServiceImpl.java b/src/main/java/com/gdsc_knu/official_homepage/service/application/ApplicationServiceImpl.java index 33c9b72a..cca1896c 100644 --- a/src/main/java/com/gdsc_knu/official_homepage/service/application/ApplicationServiceImpl.java +++ b/src/main/java/com/gdsc_knu/official_homepage/service/application/ApplicationServiceImpl.java @@ -1,23 +1,29 @@ package com.gdsc_knu.official_homepage.service.application; import com.gdsc_knu.official_homepage.dto.application.ApplicationRequest; +import com.gdsc_knu.official_homepage.dto.application.ApplicationModel; import com.gdsc_knu.official_homepage.dto.application.ApplicationResponse; +import com.gdsc_knu.official_homepage.entity.ClassYear; import com.gdsc_knu.official_homepage.entity.Member; import com.gdsc_knu.official_homepage.entity.application.Application; import com.gdsc_knu.official_homepage.entity.enumeration.ApplicationStatus; import com.gdsc_knu.official_homepage.exception.CustomException; import com.gdsc_knu.official_homepage.exception.ErrorCode; import com.gdsc_knu.official_homepage.repository.application.ApplicationRepository; +import com.gdsc_knu.official_homepage.repository.application.ClassYearRepository; import com.gdsc_knu.official_homepage.repository.member.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; + @Service @RequiredArgsConstructor public class ApplicationServiceImpl implements ApplicationService { private final ApplicationRepository applicationRepository; private final MemberRepository memberRepository; + private final ClassYearRepository classYearRepository; /** * 본인의 지원서 조회, 다른 사람 지원서 조회 시 예외 발생 @@ -25,16 +31,16 @@ public class ApplicationServiceImpl implements ApplicationService { * @param name 이름 * @param studentNumber 학번 * @return ApplicationResponse - * @throws CustomException ErrorCode.FORBIDDEN + * @throws CustomException ErrorCode.APPLICATION_FORBIDDEN */ @Override @Transactional(readOnly = true) - public ApplicationResponse getApplication(String email, String name, String studentNumber) { + public ApplicationResponse getApplication(String email, String name, String studentNumber, Long classYearId) { Member member = validateMember(email); if (!member.getStudentNumber().equals(studentNumber) || !member.getName().equals(name)) { - throw new CustomException(ErrorCode.FORBIDDEN, "본인의 지원서만 접근 가능합니다."); + throw new CustomException(ErrorCode.APPLICATION_FORBIDDEN); } - Application application = validateApplicationAccess(name, studentNumber); + Application application = validateApplicationAccess(name, studentNumber, classYearId); return new ApplicationResponse(application); } @@ -43,19 +49,23 @@ public ApplicationResponse getApplication(String email, String name, String stud * @param email 이메일 * @param applicationRequest (테크스택, 링크, 지원서 상태, 트랙, 답변) * @return Long 지원서 id - * @throws CustomException ErrorCode.CONFLICT + * @throws CustomException ErrorCode.APPLICATION_DUPLICATED */ @Override @Transactional public Long saveApplication(String email, ApplicationRequest applicationRequest) { validateApplicationStatus(applicationRequest.getApplicationStatus()); + validateApplicationDeadline(applicationRequest.getClassYearId()); Member member = validateMember(email); - applicationRepository.findByNameAndStudentNumber(member.getName(), member.getStudentNumber()) - .ifPresent(application -> { - throw new CustomException(ErrorCode.CONFLICT, "이미 작성한 지원서가 존재합니다."); - }); + applicationRepository.findByNameAndStudentNumberAndClassYearId(member.getName(), member.getStudentNumber(), applicationRequest.getClassYearId()) + .ifPresent(application -> { + throw new CustomException(ErrorCode.APPLICATION_DUPLICATED); + }); member.updateTrack(applicationRequest.getTrack()); - return applicationRepository.save(new Application(member, applicationRequest)).getId(); + Application application = new Application(member, new ApplicationModel(applicationRequest)); + application.updateClassYear(classYearRepository.findById(applicationRequest.getClassYearId()) + .orElseThrow(() -> new CustomException(ErrorCode.CLASS_YEAR_NOT_FOUND))); + return applicationRepository.save(application).getId(); } /** @@ -68,29 +78,33 @@ public Long saveApplication(String email, ApplicationRequest applicationRequest) @Transactional public Long updateApplication(String email, ApplicationRequest applicationRequest) { validateApplicationStatus(applicationRequest.getApplicationStatus()); + validateApplicationDeadline(applicationRequest.getClassYearId()); Member member = validateMember(email); - Application application = validateApplicationAccess(member.getName(), member.getStudentNumber()); - application.updateApplication(member, applicationRequest); - applicationRepository.save(application); + Application application = validateApplicationAccess(member.getName(), member.getStudentNumber(), applicationRequest.getClassYearId()); + application.updateApplication(member, new ApplicationModel(applicationRequest)); + application.updateClassYear(classYearRepository.findById(applicationRequest.getClassYearId()) + .orElseThrow(() -> new CustomException(ErrorCode.CLASS_YEAR_NOT_FOUND))); return application.getId(); } /** - * 지원서 접근 가능 상태를 판단 (이미 최종 제출 된 지원서는 접근 불가, 예외 발생) + * 입력된 지원서 상태의 유효성을 판단 * @param applicationStatus 지원서 상태 - * @throws CustomException ErrorCode.INVALID_INPUT + * @throws CustomException ErrorCode.INVALID_APPLICATION_STATE */ private void validateApplicationStatus(ApplicationStatus applicationStatus) { - if (applicationStatus.equals(ApplicationStatus.REJECTED) || applicationStatus.equals(ApplicationStatus.APPROVED)) { - throw new CustomException(ErrorCode.INVALID_INPUT, "올바르지 않은 지원서 요청입니다."); + try { + ApplicationStatus.valueOf(applicationStatus.toString()); + } catch (IllegalArgumentException e) { + throw new CustomException(ErrorCode.INVALID_APPLICATION_STATE); } } /** * 이메일로 회원 존재 여부 조회 (존재하지 않는 멤버라면 예외 발생) - * @param email - 이메일 + * @param email 이메일 * @return Member - * @throws CustomException ErrorCode.NOT_FOUND + * @throws CustomException ErrorCode.USER_NOT_FOUND */ private Member validateMember(String email) { return memberRepository.findByEmail(email) @@ -102,14 +116,23 @@ private Member validateMember(String email) { * @param name 이름 * @param studentNumber 학번 * @return Application - * @throws CustomException ErrorCode.NOT_FOUND(지원서가 존재하지 않음), ErrorCode.CONFLICT(지원서가 최종제출 됨) + * @throws CustomException ErrorCode.APPLICATION_NOT_FOUND(지원서가 존재하지 않음), ErrorCode.APPLICATION_DUPLICATED(지원서가 최종제출 됨) */ - private Application validateApplicationAccess(String name, String studentNumber) { - Application application = applicationRepository.findByNameAndStudentNumber(name, studentNumber) - .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND, "회원님의 지원서가 존재하지 않습니다.")); + private Application validateApplicationAccess(String name, String studentNumber, Long classYearId) { + Application application = applicationRepository.findByNameAndStudentNumberAndClassYearId(name, studentNumber, classYearId) + .orElseThrow(() -> new CustomException(ErrorCode.APPLICATION_NOT_FOUND)); if (!application.getApplicationStatus().equals(ApplicationStatus.TEMPORAL)) { - throw new CustomException(ErrorCode.CONFLICT); + throw new CustomException(ErrorCode.APPLICATION_DUPLICATED); } return application; } + + private void validateApplicationDeadline(Long classYearId) { + LocalDateTime now = LocalDateTime.now(); + ClassYear classYear = classYearRepository.findById(classYearId) + .orElseThrow(() -> new CustomException(ErrorCode.CLASS_YEAR_NOT_FOUND)); + if (now.isBefore(classYear.getApplicationStartDateTime()) || now.isAfter(classYear.getApplicationEndDateTime())) { + throw new CustomException(ErrorCode.APPLICATION_DEADLINE_EXPIRED); + } + } } diff --git a/src/test/java/com/gdsc_knu/official_homepage/ClearDatabase.java b/src/test/java/com/gdsc_knu/official_homepage/ClearDatabase.java index c505d68e..15cad0da 100644 --- a/src/test/java/com/gdsc_knu/official_homepage/ClearDatabase.java +++ b/src/test/java/com/gdsc_knu/official_homepage/ClearDatabase.java @@ -29,5 +29,6 @@ public void each(String table) { em.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate(); em.createNativeQuery(String.format("TRUNCATE TABLE %s", table)).executeUpdate(); em.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate(); + em.createNativeQuery(String.format("ALTER TABLE %s ALTER COLUMN id RESTART WITH 1", table)).executeUpdate(); } } diff --git a/src/test/java/com/gdsc_knu/official_homepage/application/ApplicationTestEntityFactory.java b/src/test/java/com/gdsc_knu/official_homepage/application/ApplicationTestEntityFactory.java index e7f22805..9bc625a5 100644 --- a/src/test/java/com/gdsc_knu/official_homepage/application/ApplicationTestEntityFactory.java +++ b/src/test/java/com/gdsc_knu/official_homepage/application/ApplicationTestEntityFactory.java @@ -1,13 +1,24 @@ package com.gdsc_knu.official_homepage.application; +import com.gdsc_knu.official_homepage.entity.ClassYear; import com.gdsc_knu.official_homepage.entity.application.Application; import com.gdsc_knu.official_homepage.entity.enumeration.ApplicationStatus; import com.gdsc_knu.official_homepage.entity.enumeration.Track; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; public class ApplicationTestEntityFactory { + public static ClassYear createClassYear(Long id) { + LocalDateTime now = LocalDateTime.now(); + return ClassYear.builder() + .id(id) + .name(String.format("test%s기", id)) + .applicationStartDateTime(now) + .applicationEndDateTime(now.plusDays(1)) + .build(); + } public static Application createApplication(Long id, Track track, ApplicationStatus status) { return Application.builder() .id(id) @@ -26,4 +37,19 @@ public static List createApplicationList(int startNum, int count, T } return applicationList; } + + public static List createClassYearList(int startNum, int count) { + List classYearList = new ArrayList<>(); + for (int i=startNum; i applications, List classYears) { + for (int i = 0; i < applications.size(); i++) { + int classYearIdx = i % classYears.size(); + applications.get(i).updateClassYear(classYears.get(classYearIdx)); + } + } } diff --git a/src/test/java/com/gdsc_knu/official_homepage/application/repository/AdminApplicationRepositoryTest.java b/src/test/java/com/gdsc_knu/official_homepage/application/repository/AdminApplicationRepositoryTest.java index 256ad96d..1858496f 100644 --- a/src/test/java/com/gdsc_knu/official_homepage/application/repository/AdminApplicationRepositoryTest.java +++ b/src/test/java/com/gdsc_knu/official_homepage/application/repository/AdminApplicationRepositoryTest.java @@ -1,10 +1,14 @@ package com.gdsc_knu.official_homepage.application.repository; +import com.gdsc_knu.official_homepage.ClearDatabase; import com.gdsc_knu.official_homepage.config.QueryDslConfig; import com.gdsc_knu.official_homepage.dto.admin.application.ApplicationStatisticType; import com.gdsc_knu.official_homepage.dto.admin.application.ApplicationTrackType; +import com.gdsc_knu.official_homepage.entity.ClassYear; import com.gdsc_knu.official_homepage.entity.application.Application; import com.gdsc_knu.official_homepage.repository.application.ApplicationRepository; +import com.gdsc_knu.official_homepage.repository.application.ClassYearRepository; +import jakarta.persistence.EntityManager; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -13,25 +17,33 @@ import org.springframework.context.annotation.Import; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.test.annotation.Rollback; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.gdsc_knu.official_homepage.application.ApplicationTestEntityFactory.createApplicationList; +import static com.gdsc_knu.official_homepage.application.ApplicationTestEntityFactory.*; import static com.gdsc_knu.official_homepage.entity.enumeration.ApplicationStatus.*; import static com.gdsc_knu.official_homepage.entity.enumeration.Track.*; import static org.assertj.core.api.Assertions.assertThat; @DataJpaTest -@Import(QueryDslConfig.class) +@Import({QueryDslConfig.class, ClearDatabase.class}) public class AdminApplicationRepositoryTest { @Autowired private ApplicationRepository applicationRepository; + @Autowired private ClassYearRepository classYearRepository; + @Autowired private ClearDatabase clearDatabase; + @AfterEach + @Transactional + @Rollback void tearDown() { - applicationRepository.deleteAll(); + clearDatabase.each("application"); + clearDatabase.each("class_year"); } @Test @@ -50,6 +62,10 @@ void getStatistic() { List allApplications = Stream.of(temporal, save, approve, reject) .flatMap(List::stream) .toList(); + int classYearStart = 1; + List allClassYears = createClassYearList(classYearStart, classYearStart+countPerStatus); + classYearRepository.saveAll(allClassYears); + setClassYear(allApplications, allClassYears); applicationRepository.saveAll(allApplications); // when @@ -76,6 +92,10 @@ void getGroupByTrack() { List allApplications = Stream.of(temporal, ai, backend) .flatMap(List::stream) .toList(); + int classYearStart = 1; + List allClassYears = createClassYearList(classYearStart, classYearStart+countPerStatus); + classYearRepository.saveAll(allClassYears); + setClassYear(allApplications, allClassYears); applicationRepository.saveAll(allApplications); // when @@ -102,11 +122,15 @@ void findAllApplicationsByOption() { List allApplications = Stream.of(temporal, ai, backend) .flatMap(List::stream) .toList(); + int classYearStart = 1; + List allClassYears = createClassYearList(classYearStart, classYearStart+countPerStatus); + classYearRepository.saveAll(allClassYears); + setClassYear(allApplications, allClassYears); applicationRepository.saveAll(allApplications); // when PageRequest pageRequest = PageRequest.of(0,3); - Page applicationPage = applicationRepository.findAllApplicationsByOption(pageRequest, BACK_END, false); + Page applicationPage = applicationRepository.findAllApplicationsByOption(pageRequest, BACK_END, false, null); // then assertThat(applicationPage).hasSize(2).allSatisfy(application -> { @@ -130,11 +154,15 @@ void findAllApplicationsByOptionWithNull() { List allApplications = Stream.of(temporal, ai, backend) .flatMap(List::stream) .toList(); + int classYearStart = 1; + List allClassYears = createClassYearList(classYearStart, classYearStart+countPerStatus); + classYearRepository.saveAll(allClassYears); + setClassYear(allApplications, allClassYears); applicationRepository.saveAll(allApplications); // when PageRequest pageRequest = PageRequest.of(0,5); - Page applicationPage = applicationRepository.findAllApplicationsByOption(pageRequest,null, null); + Page applicationPage = applicationRepository.findAllApplicationsByOption(pageRequest,null, null, null); // then assertThat(applicationPage).hasSize(4).anySatisfy( diff --git a/src/test/java/com/gdsc_knu/official_homepage/application/service/AdminApplicationServiceTest.java b/src/test/java/com/gdsc_knu/official_homepage/application/service/AdminApplicationServiceTest.java index 9274896c..e79b7449 100644 --- a/src/test/java/com/gdsc_knu/official_homepage/application/service/AdminApplicationServiceTest.java +++ b/src/test/java/com/gdsc_knu/official_homepage/application/service/AdminApplicationServiceTest.java @@ -1,12 +1,15 @@ package com.gdsc_knu.official_homepage.application.service; import com.gdsc_knu.official_homepage.ClearDatabase; +import com.gdsc_knu.official_homepage.config.QueryDslConfig; +import com.gdsc_knu.official_homepage.entity.ClassYear; import com.gdsc_knu.official_homepage.entity.application.Application; import com.gdsc_knu.official_homepage.entity.enumeration.ApplicationStatus; import com.gdsc_knu.official_homepage.entity.enumeration.Track; import com.gdsc_knu.official_homepage.exception.CustomException; import com.gdsc_knu.official_homepage.exception.ErrorCode; import com.gdsc_knu.official_homepage.repository.application.ApplicationRepository; +import com.gdsc_knu.official_homepage.repository.application.ClassYearRepository; import com.gdsc_knu.official_homepage.service.MailService; import com.gdsc_knu.official_homepage.service.admin.AdminApplicationService; import jakarta.persistence.EntityManager; @@ -17,6 +20,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -26,8 +30,8 @@ import java.util.concurrent.Executors; import java.util.stream.Stream; -import static com.gdsc_knu.official_homepage.application.ApplicationTestEntityFactory.createApplication; -import static com.gdsc_knu.official_homepage.application.ApplicationTestEntityFactory.createApplicationList; +import static com.gdsc_knu.official_homepage.application.ApplicationTestEntityFactory.*; +import static com.gdsc_knu.official_homepage.application.ApplicationTestEntityFactory.setClassYear; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.*; @@ -38,6 +42,7 @@ public class AdminApplicationServiceTest { @Autowired private AdminApplicationService applicationService; @Autowired private ApplicationRepository applicationRepository; + @Autowired private ClassYearRepository classYearRepository; @Autowired private ClearDatabase clearDatabase; @MockBean private MailService mailService; @@ -45,6 +50,7 @@ public class AdminApplicationServiceTest { @AfterEach void tearDown() { clearDatabase.each("application"); + clearDatabase.each("class_year"); } @@ -54,6 +60,9 @@ void tearDown() { void updateStatus() { // given Application application = createApplication(null, Track.AI, ApplicationStatus.SAVED); + ClassYear classYear = createClassYear(1L); + classYearRepository.save(classYear); + application.updateClassYear(classYear); applicationRepository.save(application); doThrow(CustomException.class).when(mailService).sendEach(application); // when @@ -82,6 +91,10 @@ void getTrackStatistic() { List allApplications = Stream.of(ai, backend, frontend, temporal) .flatMap(List::stream) .toList(); + int classYearStart = 1; + List allClassYears = createClassYearList(classYearStart, classYearStart+countPerStatus); + classYearRepository.saveAll(allClassYears); + setClassYear(allApplications, allClassYears); applicationRepository.saveAll(allApplications); // when Map statistic = applicationService.getTrackStatistic(); @@ -100,6 +113,9 @@ void getTrackStatistic() { void updateNoteFailed() { // given Application application = createApplication(null, Track.AI, ApplicationStatus.SAVED); + ClassYear classYear = createClassYear(1L); + classYearRepository.save(classYear); + application.updateClassYear(classYear); applicationRepository.save(application); // 다른 사용자에 의해 변경 application.saveNote("1",application.getVersion()); @@ -118,7 +134,10 @@ void updateNoteFailed() { @DisplayName("동시에 지원서를 수정하는 경우 처음 시도만 남고 오류를 반환한다.") void updateNoteConcurrentFailed() throws InterruptedException { Application application = createApplication(null, Track.AI, ApplicationStatus.SAVED); - applicationRepository.saveAndFlush(application);; + ClassYear classYear = createClassYear(1L); + classYearRepository.save(classYear); + application.updateClassYear(classYear); + applicationRepository.saveAndFlush(application); int threadCount = 2; CountDownLatch latch = new CountDownLatch(threadCount);