Skip to content

Commit dbd1eaa

Browse files
authored
[FEAT] 임시 저장된 지원서 상세조회
* refactor: 사용하지 않는 코드 삭제 * refactor: DateTimeUtil 추가 및 공통화 * feat: 임시 저장된 지원서 상세 조회 * add: AdminTempApplyService Test Code 작성 * fix: 날짜 데이터 포멧 수정 * refactor: 지원서 상세 조회 Repository 메소드 수정 * fix: 불필요한 테스트 코드 삭제
1 parent 246537b commit dbd1eaa

9 files changed

Lines changed: 292 additions & 20 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package org.ject.support.common.util;
2+
3+
import java.time.LocalDate;
4+
import java.time.LocalDateTime;
5+
import java.time.LocalTime;
6+
import java.time.format.DateTimeFormatter;
7+
import java.util.Optional;
8+
9+
public final class DateTimeUtil {
10+
11+
private static final String[] DAY_OF_WEEK_NAMES = {"월", "화", "수", "목", "금", "토", "일"};
12+
public static final DateTimeFormatter DEFAULT_DATETIME_FORMATTER =
13+
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
14+
public static final DateTimeFormatter DEFAULT_DATE_FORMATTER =
15+
DateTimeFormatter.ofPattern("yyyy-MM-dd");
16+
public static final DateTimeFormatter DEFAULT_TIME_FORMATTER =
17+
DateTimeFormatter.ofPattern("HH:mm");
18+
19+
private DateTimeUtil() {
20+
// 생성 제한
21+
}
22+
23+
/**
24+
* LocalDateTime → 기본 포맷(yyyy-MM-dd HH:mm)
25+
*/
26+
public static String format(LocalDateTime dateTime) {
27+
return format(dateTime, DEFAULT_DATETIME_FORMATTER);
28+
}
29+
30+
/**
31+
* LocalDate → 기본 포맷(yyyy-MM-dd)
32+
*/
33+
public static String format(LocalDate date) {
34+
return format(date, DEFAULT_DATE_FORMATTER);
35+
}
36+
37+
/**
38+
* LocalTime → 기본 포맷(HH:mm)
39+
*/
40+
public static String format(LocalTime time) {
41+
return format(time, DEFAULT_TIME_FORMATTER);
42+
}
43+
44+
/**
45+
* LocalDateTime → 지정 포맷
46+
*/
47+
public static String format(LocalDateTime dateTime, DateTimeFormatter formatter) {
48+
return Optional.ofNullable(dateTime)
49+
.map(dt -> dt.format(formatter))
50+
.orElse("");
51+
}
52+
53+
/**
54+
* LocalDate → 지정 포맷
55+
*/
56+
public static String format(LocalDate date, DateTimeFormatter formatter) {
57+
return Optional.ofNullable(date)
58+
.map(d -> d.format(formatter))
59+
.orElse("");
60+
}
61+
62+
/**
63+
* LocalTime → 지정 포맷
64+
*/
65+
public static String format(LocalTime time, DateTimeFormatter formatter) {
66+
return Optional.ofNullable(time)
67+
.map(t -> t.format(formatter))
68+
.orElse("");
69+
}
70+
71+
/**
72+
* 날짜 + 요일 한글 표시 (예: "2025년 10월 21일(화) 13:45")
73+
*/
74+
public static String formatWithDayOfWeek(LocalDateTime dateTime) {
75+
return Optional.ofNullable(dateTime)
76+
.map(dt -> {
77+
String dayOfWeek = DAY_OF_WEEK_NAMES[dt.getDayOfWeek().getValue() - 1];
78+
String datePart = dt.format(DateTimeFormatter.ofPattern("yyyy년 MM월 dd일"));
79+
String timePart = dt.format(DateTimeFormatter.ofPattern("HH:mm"));
80+
return String.format("%s(%s) %s", datePart, dayOfWeek, timePart);
81+
})
82+
.orElse("");
83+
}
84+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.ject.support.domain.admin.controller;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.tags.Tag;
5+
import org.ject.support.domain.apply.dto.TempApplyDetailResponse;
6+
import org.springframework.web.bind.annotation.PathVariable;
7+
8+
@Tag(name = "Admin", description = "임시 저장된 지원서 관리")
9+
public interface AdminTempApplyApiSpec {
10+
11+
@Operation(
12+
summary = "임시 저장된 지원서 상세 조회",
13+
description = "관리자가 지원자의 임시 저장된 지원서의 정보를 조회합니다."
14+
)
15+
TempApplyDetailResponse getTempApplyDetail(@PathVariable("tempApplyId") Long tempApplyId);
16+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.ject.support.domain.admin.controller;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.ject.support.domain.admin.service.AdminTempApplyService;
5+
import org.ject.support.domain.apply.dto.TempApplyDetailResponse;
6+
import org.springframework.security.access.prepost.PreAuthorize;
7+
import org.springframework.web.bind.annotation.GetMapping;
8+
import org.springframework.web.bind.annotation.PathVariable;
9+
import org.springframework.web.bind.annotation.RequestMapping;
10+
import org.springframework.web.bind.annotation.RestController;
11+
12+
@RestController
13+
@RequiredArgsConstructor
14+
@RequestMapping("/admin/temp/apply")
15+
public class AdminTempApplyController implements AdminTempApplyApiSpec {
16+
17+
private final AdminTempApplyService adminTempApplyService;
18+
19+
@GetMapping("/{tempApplyId}")
20+
@PreAuthorize("hasRole('ROLE_ADMIN')")
21+
public TempApplyDetailResponse getTempApplyDetail(@PathVariable("tempApplyId") Long tempApplyId) {
22+
return adminTempApplyService.getTempApplyDetail(tempApplyId);
23+
}
24+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.ject.support.domain.admin.service;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.ject.support.common.util.String2MapSerializer;
5+
import org.ject.support.domain.apply.domain.ApplicationForm;
6+
import org.ject.support.domain.apply.domain.Apply;
7+
import org.ject.support.domain.apply.dto.ApplyPortfolioDto;
8+
import org.ject.support.domain.apply.dto.TempApplyDetailResponse;
9+
import org.ject.support.domain.apply.exception.ApplyErrorCode;
10+
import org.ject.support.domain.apply.exception.ApplyException;
11+
import org.ject.support.domain.apply.repository.ApplyRepository;
12+
import org.springframework.stereotype.Service;
13+
import org.springframework.transaction.annotation.Transactional;
14+
15+
@Service
16+
@RequiredArgsConstructor
17+
@Transactional(readOnly = true)
18+
public class AdminTempApplyService {
19+
20+
private final ApplyRepository applyRepository;
21+
private final String2MapSerializer string2MapSerializer;
22+
23+
public TempApplyDetailResponse getTempApplyDetail(Long tempApplyId) {
24+
Apply apply = applyRepository.findByIdAndStatusWithMember(tempApplyId, Apply.Status.TEMP_SAVED)
25+
.orElseThrow(() -> new ApplyException(ApplyErrorCode.NOT_FOUND_TEMP_APPLICATION_FORM));
26+
27+
ApplicationForm tempApplicationForm = apply.getApplicationForm();
28+
29+
return TempApplyDetailResponse.from(
30+
apply,
31+
string2MapSerializer.serializeAsMap(tempApplicationForm.getContent()),
32+
tempApplicationForm.getPortfolios()
33+
.stream()
34+
.map(ApplyPortfolioDto::from)
35+
.toList());
36+
}
37+
}

src/main/java/org/ject/support/domain/apply/dto/SubmitApplicationRequest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package org.ject.support.domain.apply.dto;
22

3-
import org.ject.support.domain.apply.dto.ApplyPortfolioDto;
4-
53
import java.util.List;
64
import java.util.Map;
75

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.ject.support.domain.apply.dto;
2+
3+
import org.ject.support.common.util.DateTimeUtil;
4+
import org.ject.support.domain.apply.domain.Apply;
5+
import org.ject.support.domain.member.JobFamily;
6+
7+
import java.util.List;
8+
import java.util.Map;
9+
10+
public record TempApplyDetailResponse(
11+
Long applyId,
12+
String name,
13+
String phoneNumber,
14+
String email,
15+
JobFamily memberJobFamily,
16+
String semesterName,
17+
String savedAt,
18+
String lastModifiedAt,
19+
JobFamily recruitJobFamily,
20+
TempApplicationFormResponse tempApplicationFormResponse
21+
) {
22+
23+
public static TempApplyDetailResponse from(
24+
Apply apply,
25+
Map<String, String> answers,
26+
List<ApplyPortfolioDto> portfolios
27+
) {
28+
return new TempApplyDetailResponse(
29+
apply.getId(),
30+
apply.getMember().getName(),
31+
apply.getMember().getPhoneNumber(),
32+
apply.getMember().getEmail(),
33+
apply.getMember().getJobFamily(),
34+
apply.getRecruit().getSemester().getName(),
35+
DateTimeUtil.formatWithDayOfWeek(apply.getCreatedAt()),
36+
DateTimeUtil.formatWithDayOfWeek(apply.getUpdatedAt()),
37+
apply.getRecruit().getJobFamily(),
38+
TempApplicationFormResponse.from(answers, portfolios)
39+
);
40+
}
41+
}

src/main/java/org/ject/support/domain/apply/service/RemindApplyService.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.ject.support.domain.apply.service;
22

33
import lombok.RequiredArgsConstructor;
4+
import org.ject.support.common.util.DateTimeUtil;
45
import org.ject.support.domain.apply.domain.Apply;
56
import org.ject.support.domain.apply.repository.ApplyRepository;
67
import org.ject.support.domain.member.repository.MemberRepository;
@@ -10,7 +11,6 @@
1011
import org.springframework.stereotype.Service;
1112

1213
import java.time.LocalDateTime;
13-
import java.time.format.DateTimeFormatter;
1414
import java.util.List;
1515
import java.util.Map;
1616

@@ -43,14 +43,6 @@ public void remindApply(Long recruitId) {
4343
emailSendService.sendBulkTemplatedEmail(
4444
EMAIL_SEND_GROUP,
4545
targetEmails,
46-
Map.of("deadline", formatDeadline(recruit.getEndDate())));
47-
}
48-
49-
private String formatDeadline(LocalDateTime recruitEndDate) {
50-
String[] dayOfWeekNames = {"월", "화", "수", "목", "금", "토", "일"};
51-
String dayOfWeekName = dayOfWeekNames[recruitEndDate.getDayOfWeek().getValue() - 1];
52-
String datePart = recruitEndDate.format(DateTimeFormatter.ofPattern("yyyy년 M월 d일"));
53-
String timePart = recruitEndDate.format(DateTimeFormatter.ofPattern("HH:mm"));
54-
return String.format("%s(%s) %s", datePart, dayOfWeekName, timePart);
46+
Map.of("deadline", DateTimeUtil.formatWithDayOfWeek(recruit.getEndDate())));
5547
}
5648
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package org.ject.support.domain.admin.service;
2+
3+
import org.ject.support.base.UnitTestSupport;
4+
import org.ject.support.common.util.String2MapSerializer;
5+
import org.ject.support.domain.apply.domain.ApplicationForm;
6+
import org.ject.support.domain.apply.domain.Apply;
7+
import org.ject.support.domain.apply.dto.TempApplyDetailResponse;
8+
import org.ject.support.domain.apply.exception.ApplyException;
9+
import org.ject.support.domain.apply.repository.ApplyRepository;
10+
import org.ject.support.domain.member.entity.Member;
11+
import org.ject.support.domain.recruit.domain.Recruit;
12+
import org.ject.support.domain.recruit.domain.Semester;
13+
import org.junit.jupiter.api.Test;
14+
import org.mockito.InjectMocks;
15+
import org.mockito.Mock;
16+
17+
import java.util.Optional;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
21+
import static org.mockito.BDDMockito.given;
22+
import static org.mockito.Mockito.verify;
23+
24+
class AdminTempApplyServiceTest extends UnitTestSupport {
25+
26+
@InjectMocks
27+
private AdminTempApplyService adminTempApplyService;
28+
29+
@Mock
30+
private ApplyRepository applyRepository;
31+
32+
@Mock
33+
private String2MapSerializer string2MapSerializer;
34+
35+
@Test
36+
void 존재하지_않는_임시_저장된_지원서를_조회하면_예외_발생() {
37+
// given
38+
Long tempApplyId = 1L;
39+
given(applyRepository.findByIdAndStatusWithMember(tempApplyId, Apply.Status.TEMP_SAVED))
40+
.willReturn(Optional.empty());
41+
42+
// when, then
43+
assertThatThrownBy(() -> adminTempApplyService.getTempApplyDetail(tempApplyId))
44+
.isInstanceOf(ApplyException.class);
45+
}
46+
47+
@Test
48+
void 임시_저장된_지원서를_상세_조회한다() {
49+
// given
50+
Long tempApplyId = 1L;
51+
Member member = Member.builder()
52+
.name("김젝트")
53+
.build();
54+
Semester semester = Semester.builder()
55+
.name("1")
56+
.build();
57+
Recruit recruit = Recruit.builder()
58+
.semester(semester)
59+
.build();
60+
Apply submittedApply = Apply.builder()
61+
.id(tempApplyId)
62+
.member(member)
63+
.recruit(recruit)
64+
.status(Apply.Status.TEMP_SAVED)
65+
.applicationForm(ApplicationForm.builder().build())
66+
.build();
67+
given(applyRepository.findByIdAndStatusWithMember(tempApplyId, Apply.Status.TEMP_SAVED))
68+
.willReturn(Optional.of(submittedApply));
69+
70+
// when
71+
TempApplyDetailResponse result = adminTempApplyService.getTempApplyDetail(tempApplyId);
72+
73+
// then
74+
verify(applyRepository).findByIdAndStatusWithMember(tempApplyId, Apply.Status.TEMP_SAVED);
75+
assertThat(result.applyId()).isEqualTo(tempApplyId);
76+
}
77+
}

src/test/java/org/ject/support/domain/apply/repository/ApplyRepositoryTest.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,24 +106,27 @@ void setUp() {
106106
}
107107

108108
@Test
109-
void 해당_상태의_지원서가_없을_때_0_반환() {
109+
void 지원ID와_지원서의_상태로_지원서를_상세_조회한다() {
110110
// given
111111
Member feApplicant = createMember("emailFE@test.com", Role.APPLY);
112-
Member beApplicant = createMember("emailBE@test.com", Role.APPLY);
113-
memberRepository.saveAll(List.of(feApplicant, beApplicant));
112+
Member be1Applicant = createMember("emailBE1@test.com", Role.APPLY);
113+
memberRepository.saveAll(List.of(feApplicant, be1Applicant));
114114

115115
Apply feTempApply = getApply(feApplicant, feRecruit, TEMP_SAVED);
116-
Apply beSubmitApply = getApply(beApplicant, beRecruit, TEMP_SAVED);
117-
applyRepository.saveAll(List.of(feTempApply, beSubmitApply));
116+
Apply be1SubmitApply = getApply(be1Applicant, beRecruit, TEMP_SAVED);
117+
applyRepository.saveAll(List.of(feTempApply, be1SubmitApply));
118118

119119
// when
120-
Long count = applyRepository.countByStatus(SUBMITTED);
120+
Apply result = applyRepository.findByIdAndStatusWithMember(feTempApply.getId(), TEMP_SAVED)
121+
.orElseThrow();
121122

122123
// then
123-
assertThat(count).isZero();
124+
assertThat(result).isEqualTo(feTempApply);
125+
assertThat(result.getMember()).isEqualTo(feApplicant);
126+
assertThat(result.getRecruit()).isEqualTo(feRecruit);
127+
assertThat(result.getStatus()).isEqualTo(TEMP_SAVED);
124128
}
125129

126-
127130
private Recruit getRecruit(JobFamily jobFamily) {
128131
return Recruit.builder()
129132
.semester(semester)

0 commit comments

Comments
 (0)