diff --git a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/controller/MockApplyController.java b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/controller/MockApplyController.java index 90c5666..c1cb3fe 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/controller/MockApplyController.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/controller/MockApplyController.java @@ -4,6 +4,7 @@ import com.jobdri.jobdri_api.domain.mockapply.dto.request.MockApplyCreateMockFromJobPostingRequest; import com.jobdri.jobdri_api.domain.mockapply.dto.request.MockApplyCreateMockRequest; import com.jobdri.jobdri_api.domain.mockapply.dto.response.MockApplyCreateResponse; +import com.jobdri.jobdri_api.domain.mockapply.dto.response.MockApplySequenceResponse; import com.jobdri.jobdri_api.domain.jobposting.dto.response.JobPostingResponse; import com.jobdri.jobdri_api.domain.mockapply.service.MockApplyService; import com.jobdri.jobdri_api.global.apiPayload.ApiResponse; @@ -191,4 +192,19 @@ public ApiResponse getMockApplyJobPosting( mockApplyService.getMockApplyJobPosting(userDetails.getUser(), mockApplyId) ); } + + @Operation( + summary = "같은 공고 기준 자소서 개수 및 순번 조회", + description = "현재 mockApply가 연결된 채용 공고 기준으로, 로그인 사용자가 생성한 자소서 총 개수와 현재 자소서 순번을 조회합니다." + ) + @GetMapping("/{mockApplyId}/sequence") + public ApiResponse getMockApplySequence( + @AuthenticationPrincipal UserDetailsImpl userDetails, + @PathVariable Long mockApplyId + ) { + return ApiResponse.onSuccess( + "자소서 순번 조회에 성공했습니다.", + mockApplyService.getMockApplySequence(userDetails.getUser(), mockApplyId) + ); + } } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/dto/response/MockApplySequenceResponse.java b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/dto/response/MockApplySequenceResponse.java new file mode 100644 index 0000000..d6a1ed8 --- /dev/null +++ b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/dto/response/MockApplySequenceResponse.java @@ -0,0 +1,9 @@ +package com.jobdri.jobdri_api.domain.mockapply.dto.response; + +public record MockApplySequenceResponse( + Long jobPostingId, + Long mockApplyId, + int totalCount, + int sequence +) { +} diff --git a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java index 1a9ade8..3ddd7a0 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java @@ -2,10 +2,30 @@ import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; public interface MockApplyRepository extends JpaRepository { List findAllByUserId(Long userId); List findAllByJobPostingId(Long jobPostingId); + long countByUserIdAndJobPostingId(Long userId, Long jobPostingId); + + @Query(""" + select count(ma) + from MockApply ma + where ma.user.id = :userId + and ma.jobPosting.id = :jobPostingId + and ( + ma.createdAt < :createdAt + or (ma.createdAt = :createdAt and ma.id <= :mockApplyId) + ) + """) + long countSequenceByUserIdAndJobPostingId( + @Param("userId") Long userId, + @Param("jobPostingId") Long jobPostingId, + @Param("createdAt") java.time.LocalDateTime createdAt, + @Param("mockApplyId") Long mockApplyId + ); } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java index 065a6c0..5b64b31 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java @@ -12,6 +12,7 @@ import com.jobdri.jobdri_api.domain.mockapply.dto.request.MockApplyCreateMockFromJobPostingRequest; import com.jobdri.jobdri_api.domain.mockapply.dto.request.MockApplyCreateMockRequest; import com.jobdri.jobdri_api.domain.mockapply.dto.response.MockApplyCreateResponse; +import com.jobdri.jobdri_api.domain.mockapply.dto.response.MockApplySequenceResponse; import com.jobdri.jobdri_api.domain.mockapply.entity.ApplyType; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; import com.jobdri.jobdri_api.domain.mockapply.repository.MockApplyRepository; @@ -89,6 +90,38 @@ public JobPostingResponse getMockApplyJobPosting(User user, Long mockApplyId) { return JobPostingResponse.from(mockApply.getJobPosting()); } + public MockApplySequenceResponse getMockApplySequence(User user, Long mockApplyId) { + User validatedUser = userService.validateUser(user); + MockApply mockApply = getOwnedMockApply(validatedUser, mockApplyId); + Long jobPostingId = mockApply.getJobPosting().getId(); + + int totalCount = Math.toIntExact( + mockApplyRepository.countByUserIdAndJobPostingId(validatedUser.getId(), jobPostingId) + ); + int sequence = Math.toIntExact( + mockApplyRepository.countSequenceByUserIdAndJobPostingId( + validatedUser.getId(), + jobPostingId, + mockApply.getCreatedAt(), + mockApply.getId() + ) + ); + + if (sequence < 1 || sequence > totalCount) { + throw new GeneralException( + GeneralErrorCode.MOCK_APPLY_NOT_FOUND, + "해당 공고에 연결된 모의 서류 지원 순서를 찾을 수 없습니다. mockApplyId=" + mockApplyId + ); + } + + return new MockApplySequenceResponse( + jobPostingId, + mockApply.getId(), + totalCount, + sequence + ); + } + private MockApply getOwnedMockApply(User user, Long mockApplyId) { MockApply mockApply = mockApplyRepository.findById(mockApplyId) .orElseThrow(() -> new GeneralException( diff --git a/src/test/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyServiceTest.java b/src/test/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyServiceTest.java index 3d280ef..388bc1d 100644 --- a/src/test/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyServiceTest.java +++ b/src/test/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyServiceTest.java @@ -14,6 +14,7 @@ import com.jobdri.jobdri_api.domain.jobposting.service.MockJobPostingGenerationService; import com.jobdri.jobdri_api.domain.mockapply.dto.request.MockApplyCreateMockRequest; import com.jobdri.jobdri_api.domain.mockapply.dto.response.MockApplyCreateResponse; +import com.jobdri.jobdri_api.domain.mockapply.dto.response.MockApplySequenceResponse; import com.jobdri.jobdri_api.domain.mockapply.entity.ApplyType; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApplyStatus; @@ -28,8 +29,10 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -152,6 +155,31 @@ void getMockApplyJobPosting() { assertThat(response.getCompanyName()).isEqualTo(jobPosting.getCompany().getName()); } + @Test + @DisplayName("같은 공고 기준 자소서 총 개수와 현재 순번을 조회한다") + void getMockApplySequence() { + User user = saveUser("sequence@example.com"); + JobPosting jobPosting = saveJobPosting(user, "백엔드 개발"); + MockApply first = mockApplyRepository.save(MockApply.create(user, jobPosting, ApplyType.MOCK)); + MockApply second = mockApplyRepository.save(MockApply.create(user, jobPosting, ApplyType.MOCK)); + MockApply third = mockApplyRepository.save(MockApply.create(user, jobPosting, ApplyType.ACTUAL)); + + LocalDateTime baseTime = LocalDateTime.of(2026, 1, 1, 12, 0); + ReflectionTestUtils.setField(first, "createdAt", baseTime); + ReflectionTestUtils.setField(second, "createdAt", baseTime.plusMinutes(1)); + ReflectionTestUtils.setField(third, "createdAt", baseTime.plusMinutes(2)); + mockApplyRepository.saveAndFlush(first); + mockApplyRepository.saveAndFlush(second); + mockApplyRepository.saveAndFlush(third); + + MockApplySequenceResponse response = mockApplyService.getMockApplySequence(user, second.getId()); + + assertThat(response.jobPostingId()).isEqualTo(jobPosting.getId()); + assertThat(response.mockApplyId()).isEqualTo(second.getId()); + assertThat(response.totalCount()).isEqualTo(3); + assertThat(response.sequence()).isEqualTo(2); + } + @Test @DisplayName("존재하지 않는 공고 ID로 ACTUAL 타입 지원 생성 시 예외를 던진다") void createActualApplyThrowsWhenJobPostingNotFound() {