diff --git a/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java b/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java index 2d1cef6..c812cb2 100644 --- a/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java +++ b/src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java @@ -38,7 +38,8 @@ public enum ErrorCode { RECRUITMENT_NOT_ACTIVE(400, "C029", "모집 기간이 아닙니다."), NOT_FOUND_PARTICIPANT(400, "C030", "참가자를 찾을 수 없습니다."), EXECUTIVE_NOT_FOUND(400, "C031", "임원진을 찾을 수 없습니다."), - ; + GENERATION_NOT_FOUND(400, "C032", "저장된 기수를 찾을 수 없습니다."), + INVALID_GENERATION_FORMAT(400, "C033", "유효하지 않은 기수 형식입니다. (예: '1기')"); private final int status; private final String code; diff --git a/src/main/java/dmu/dasom/api/domain/member/dto/SignupRequestDto.java b/src/main/java/dmu/dasom/api/domain/member/dto/SignupRequestDto.java index 62b395e..94c1994 100644 --- a/src/main/java/dmu/dasom/api/domain/member/dto/SignupRequestDto.java +++ b/src/main/java/dmu/dasom/api/domain/member/dto/SignupRequestDto.java @@ -22,10 +22,15 @@ public class SignupRequestDto { @Schema(description = "비밀번호", example = "password", minLength = 8, maxLength = 128) private String password; - public Member toEntity(final String password) { + @Length(max = 4) + @Schema(description = "기수 (선택)", example = "34기", nullable = true) + private String generation; + + public Member toEntity(final String password, final String generation) { return Member.builder() .email(this.email) .password(password) + .generation(generation) .build(); } } diff --git a/src/main/java/dmu/dasom/api/domain/member/entity/Member.java b/src/main/java/dmu/dasom/api/domain/member/entity/Member.java index b2c6c9a..aacaa48 100644 --- a/src/main/java/dmu/dasom/api/domain/member/entity/Member.java +++ b/src/main/java/dmu/dasom/api/domain/member/entity/Member.java @@ -33,4 +33,7 @@ public class Member extends BaseEntity { @Enumerated(EnumType.STRING) private Role role = Role.ROLE_MEMBER; -} + // 기수 정보를 저장할 필드 추가 + @Column(name = "generation", length = 4, nullable = false) + private String generation; +} \ No newline at end of file diff --git a/src/main/java/dmu/dasom/api/domain/member/service/MemberServiceImpl.java b/src/main/java/dmu/dasom/api/domain/member/service/MemberServiceImpl.java index eef3d68..2094c5a 100644 --- a/src/main/java/dmu/dasom/api/domain/member/service/MemberServiceImpl.java +++ b/src/main/java/dmu/dasom/api/domain/member/service/MemberServiceImpl.java @@ -2,6 +2,7 @@ import dmu.dasom.api.domain.common.exception.CustomException; import dmu.dasom.api.domain.common.exception.ErrorCode; +import dmu.dasom.api.domain.recruit.service.RecruitService; import dmu.dasom.api.domain.member.dto.SignupRequestDto; import dmu.dasom.api.domain.member.entity.Member; import dmu.dasom.api.domain.member.repository.MemberRepository; @@ -24,6 +25,7 @@ public class MemberServiceImpl implements MemberService { private final BCryptPasswordEncoder encoder; private final MemberRepository memberRepository; + private final RecruitService recruitService; private final JwtUtil jwtUtil; // 이메일로 사용자 조회 @@ -45,9 +47,13 @@ public void signUp(final SignupRequestDto request) { // 이미 가입된 이메일인지 확인 if (checkByEmail(request.getEmail())) throw new CustomException(ErrorCode.SIGNUP_FAILED); + //기수는 선택적으로 가져오며, 없을 경우 신입 부붠 처리하여, 모집일정의 기수 사용 + String generation = (request.getGeneration() != null && !request.getGeneration().isEmpty()) + ? request.getGeneration() + : recruitService.getCurrentGeneration(); - // 비밀번호 암호화 후 저장 - memberRepository.save(request.toEntity(encoder.encode(request.getPassword()))); + // 비밀번호 암호화 후 저장, 기수도 같이 기입 + memberRepository.save(request.toEntity(encoder.encode(request.getPassword()), generation)); } // 토큰 갱신 diff --git a/src/main/java/dmu/dasom/api/domain/recruit/entity/Recruit.java b/src/main/java/dmu/dasom/api/domain/recruit/entity/Recruit.java index 6d39414..29366ab 100644 --- a/src/main/java/dmu/dasom/api/domain/recruit/entity/Recruit.java +++ b/src/main/java/dmu/dasom/api/domain/recruit/entity/Recruit.java @@ -37,7 +37,19 @@ public void updateTime(final LocalTime time) { this.value = time.format(TIME_FORMATTER); } + + // 기수 업데이트 + public void updateGeneration(final String generation) { + this.value = generation; // ex. "34기" + } + public RecruitConfigResponseDto toResponse() { + if(this.key == ConfigKey.GENERATION){ + return RecruitConfigResponseDto.builder() + .key(key) + .value(value) + .build(); + } LocalDateTime dateTime = LocalDateTime.parse(this.value, DATE_TIME_FORMATTER); return RecruitConfigResponseDto.builder() .key(key) diff --git a/src/main/java/dmu/dasom/api/domain/recruit/enums/ConfigKey.java b/src/main/java/dmu/dasom/api/domain/recruit/enums/ConfigKey.java index 5bc3a4e..a224918 100644 --- a/src/main/java/dmu/dasom/api/domain/recruit/enums/ConfigKey.java +++ b/src/main/java/dmu/dasom/api/domain/recruit/enums/ConfigKey.java @@ -18,6 +18,7 @@ public enum ConfigKey { INTERVIEW_TIME_END, // 면접 종료 시간 // 2차 합격 발표일 (시간 포함) - INTERVIEW_PASS_ANNOUNCEMENT // 2차 합격 발표일 + INTERVIEW_PASS_ANNOUNCEMENT, // 2차 합격 발표일 + GENERATION //현재 모집중인 기수 정보 } diff --git a/src/main/java/dmu/dasom/api/domain/recruit/service/RecruitService.java b/src/main/java/dmu/dasom/api/domain/recruit/service/RecruitService.java index ac272dd..cb9a150 100644 --- a/src/main/java/dmu/dasom/api/domain/recruit/service/RecruitService.java +++ b/src/main/java/dmu/dasom/api/domain/recruit/service/RecruitService.java @@ -15,6 +15,10 @@ public interface RecruitService { void modifyRecruitSchedule(final RecruitScheduleModifyRequestDto requestDto); + void modifyGeneration(String newGeneration); + + String getCurrentGeneration(); + String generateReservationCode(String studentNo, String contactLastDigits); LocalDateTime getResultAnnouncementSchedule(ResultCheckType type); diff --git a/src/main/java/dmu/dasom/api/domain/recruit/service/RecruitServiceImpl.java b/src/main/java/dmu/dasom/api/domain/recruit/service/RecruitServiceImpl.java index 7dbd6ce..cfe4241 100644 --- a/src/main/java/dmu/dasom/api/domain/recruit/service/RecruitServiceImpl.java +++ b/src/main/java/dmu/dasom/api/domain/recruit/service/RecruitServiceImpl.java @@ -25,13 +25,13 @@ public class RecruitServiceImpl implements RecruitService { private final RecruitRepository recruitRepository; - // 모집 일정 설정 조회 + // 모집 설정 조회 @Override public List getRecruitSchedule() { return findAll().stream() - .map(config -> config.getKey() == ConfigKey.INTERVIEW_TIME_START || config.getKey() == ConfigKey.INTERVIEW_TIME_END - ? config.toTimeResponse() : config.toResponse()) - .toList(); + .map(config -> config.getKey() == ConfigKey.INTERVIEW_TIME_START || config.getKey() == ConfigKey.INTERVIEW_TIME_END + ? config.toTimeResponse() : config.toResponse()) + .toList(); } // 모집 일정 설정 수정 @@ -50,6 +50,20 @@ public void modifyRecruitSchedule(final RecruitScheduleModifyRequestDto request) config.updateDateTime(dateTime); } + + //기수 수정 + @Override + @Transactional + public void modifyGeneration(String newGeneration) { + final Recruit config = findByKey(ConfigKey.GENERATION); + config.updateGeneration(newGeneration); + } + // 기수 조회 + @Override + public String getCurrentGeneration() { + Recruit generationConfig = findByKey(ConfigKey.GENERATION); + return generationConfig.getValue(); + } // 모집 기간 여부 확인 @Override public boolean isRecruitmentActive() { diff --git a/src/main/java/dmu/dasom/api/global/admin/controller/AdminRecruitController.java b/src/main/java/dmu/dasom/api/global/admin/controller/AdminRecruitController.java index 86ac9a0..a2ceb7f 100644 --- a/src/main/java/dmu/dasom/api/global/admin/controller/AdminRecruitController.java +++ b/src/main/java/dmu/dasom/api/global/admin/controller/AdminRecruitController.java @@ -57,6 +57,19 @@ public ResponseEntity modifyRecruitSchedule(@Valid @RequestBody final Recr .build(); } + @Operation(summary = "모집 기수 변경") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "모집 기수 수정 성공"), + @ApiResponse(responseCode = "400", description = "잘못된 요청") + }) + @PatchMapping("/generation") + public ResponseEntity modifyGeneration(@Valid @RequestBody String request) { + recruitService.modifyGeneration(request); + return ResponseEntity.ok() + .build(); + } + + @Operation(summary = "면접 일정 생성", description = "새로운 면접 일정을 생성합니다.") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "면접 일정 생성 성공"), diff --git a/src/test/java/dmu/dasom/api/domain/member/MemberServiceTest.java b/src/test/java/dmu/dasom/api/domain/member/MemberServiceTest.java index 9ba112c..89724b3 100644 --- a/src/test/java/dmu/dasom/api/domain/member/MemberServiceTest.java +++ b/src/test/java/dmu/dasom/api/domain/member/MemberServiceTest.java @@ -6,6 +6,7 @@ import dmu.dasom.api.domain.member.entity.Member; import dmu.dasom.api.domain.member.repository.MemberRepository; import dmu.dasom.api.domain.member.service.MemberServiceImpl; +import dmu.dasom.api.domain.recruit.service.RecruitService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -13,6 +14,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.test.util.ReflectionTestUtils; import java.util.Optional; @@ -28,6 +30,9 @@ class MemberServiceTest { @Mock MemberRepository memberRepository; + @Mock + RecruitService recruitService; // RecruitService 주입 + @InjectMocks private MemberServiceImpl memberService; @@ -92,23 +97,49 @@ void checkByEmail_false() { assertFalse(result); } + @Test - @DisplayName("회원가입 - 성공") - void signUp_success() { - // given - SignupRequestDto request = mock(SignupRequestDto.class); - when(request.getEmail()).thenReturn("test@example.com"); - when(request.getPassword()).thenReturn("password"); + @DisplayName("회원가입 - 기수 선택값 전달 시 사용") + void signUp_withGenerationProvided() { + // 실제 DTO 객체 사용 + SignupRequestDto request = new SignupRequestDto(); + // Reflection 또는 생성자/Setter로 값 설정 + ReflectionTestUtils.setField(request, "email", "test@example.com"); + ReflectionTestUtils.setField(request, "password", "password"); + ReflectionTestUtils.setField(request, "generation", "35기"); + when(encoder.encode("password")).thenReturn("encodedPassword"); when(memberRepository.existsByEmail("test@example.com")).thenReturn(false); - // when memberService.signUp(request); - // then - verify(memberRepository, times(1)).save(any()); + verify(memberRepository, times(1)).save(argThat(member -> + "35기".equals(member.getGeneration()) + )); + verify(recruitService, never()).getCurrentGeneration(); } + @Test + @DisplayName("회원가입 - 기수 선택값 없으면 기본값 사용") + void signUp_withGenerationDefault() { + SignupRequestDto request = new SignupRequestDto(); + ReflectionTestUtils.setField(request, "email", "test@example.com"); + ReflectionTestUtils.setField(request, "password", "password"); + ReflectionTestUtils.setField(request, "generation", null); + + when(encoder.encode("password")).thenReturn("encodedPassword"); + when(memberRepository.existsByEmail("test@example.com")).thenReturn(false); + when(recruitService.getCurrentGeneration()).thenReturn("34기"); + + memberService.signUp(request); + + verify(memberRepository, times(1)).save(argThat(member -> + "34기".equals(member.getGeneration()) + )); + verify(recruitService, times(1)).getCurrentGeneration(); + } + + @Test @DisplayName("회원가입 - 실패") void signUp_fail() { diff --git a/src/test/java/dmu/dasom/api/domain/recruit/RecruitServiceTest.java b/src/test/java/dmu/dasom/api/domain/recruit/RecruitServiceTest.java index 205f4a5..e91580a 100644 --- a/src/test/java/dmu/dasom/api/domain/recruit/RecruitServiceTest.java +++ b/src/test/java/dmu/dasom/api/domain/recruit/RecruitServiceTest.java @@ -33,7 +33,6 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; -import static org.mockito.Mockito.mock; @ExtendWith(MockitoExtension.class) class RecruitServiceTest { @@ -102,6 +101,36 @@ void modifyRecruitSchedule_fail() { assertEquals(ErrorCode.INVALID_TIME_FORMAT, exception.getErrorCode()); } + @Test + @DisplayName("모집 기수 수정") + void modifyGeneration_success() { + // given + Recruit generationRecruit = mock(Recruit.class); + String newGeneration = "35기"; + when(recruitRepository.findByKey(ConfigKey.GENERATION)).thenReturn(Optional.of(generationRecruit)); + + // when + recruitService.modifyGeneration(newGeneration); + + // then + verify(generationRecruit, times(1)).updateGeneration(newGeneration); + } + + @Test + @DisplayName("기수 조회") + void getCurrentGeneration_success() { + // given + Recruit generationRecruit = mock(Recruit.class); + when(recruitRepository.findByKey(ConfigKey.GENERATION)).thenReturn(Optional.of(generationRecruit)); + when(generationRecruit.getValue()).thenReturn("34기"); + // when + String currentGeneration = recruitService.getCurrentGeneration(); + // then + assertEquals("34기", currentGeneration); + verify(recruitRepository, times(1)).findByKey(ConfigKey.GENERATION); + verify(generationRecruit, times(1)).getValue(); + } + @Test @DisplayName("면접 일정 생성 - 성공") void createInterviewSlots_success() { @@ -196,4 +225,5 @@ void reserveInterviewSlot_fail_alreadyReserved() { assertEquals(ErrorCode.ALREADY_RESERVED, exception.getErrorCode()); } + }