Skip to content

Commit 7ec4457

Browse files
authored
Merge pull request #382 from mosu-dev/develop
prod: 수험번호 및 수험표
2 parents 21e5e0d + d7a85bd commit 7ec4457

File tree

53 files changed

+1563
-90
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1563
-90
lines changed

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ dependencies {
111111

112112
//타임리프
113113
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
114+
115+
//pdf
116+
implementation 'org.apache.pdfbox:pdfbox:3.0.5'
117+
implementation 'org.apache.pdfbox:fontbox:3.0.5'
114118
}
115119

116120
configurations.configureEach {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package life.mosu.mosuserver.application.admin;
2+
3+
import jakarta.transaction.Transactional;
4+
import life.mosu.mosuserver.application.admin.dto.ApplicationCsvInfo;
5+
import life.mosu.mosuserver.application.admin.dto.ImportResultDto;
6+
import life.mosu.mosuserver.application.admin.processor.ApplyGuestStepProcessor;
7+
import life.mosu.mosuserver.application.admin.processor.ChangeTestPaperCheckedStepProcessor;
8+
import life.mosu.mosuserver.application.admin.processor.GetApplicationGuestRequestStepProcessor;
9+
import life.mosu.mosuserver.application.admin.processor.RegisterVirtualAccountStepProcessor;
10+
import life.mosu.mosuserver.application.admin.util.CsvReader;
11+
import life.mosu.mosuserver.presentation.application.dto.ApplicationGuestRequest;
12+
import lombok.RequiredArgsConstructor;
13+
import lombok.extern.slf4j.Slf4j;
14+
import org.springframework.stereotype.Service;
15+
import org.springframework.web.multipart.MultipartFile;
16+
17+
import java.util.List;
18+
19+
@Slf4j
20+
@Service
21+
@RequiredArgsConstructor
22+
public class AdminApplicationImportService {
23+
24+
private final GetApplicationGuestRequestStepProcessor getApplicationGuestRequestStepProcessor;
25+
private final ApplyGuestStepProcessor applyGuestStepProcessor;
26+
private final ChangeTestPaperCheckedStepProcessor changeTestPaperCheckedStepProcessor;
27+
private final RegisterVirtualAccountStepProcessor registerVirtualAccountStepProcessor;
28+
private final CsvReader csvReader;
29+
30+
@Transactional
31+
public ImportResultDto importGuestApplications(MultipartFile file) {
32+
List<ApplicationCsvInfo> rows = csvReader.read(file);
33+
34+
int total = rows.size();
35+
int success = 0;
36+
37+
int lineNo = 1;
38+
for (ApplicationCsvInfo row : rows) {
39+
lineNo++;
40+
try {
41+
processGuestRow(row);
42+
success++;
43+
} catch (Exception e) {
44+
log.error("게스트 신청 CSV 행 {} 처리 실패: {}", lineNo, e.getMessage(), e);
45+
throw new RuntimeException("CSV 일괄 처리 중 오류 발생");
46+
}
47+
}
48+
49+
int fail = total - success;
50+
log.info("CSV Import 완료 - 총 {}건, 성공 {}, 실패 {}", total, success, fail);
51+
return new ImportResultDto(total, success, fail);
52+
}
53+
54+
55+
private void processGuestRow(ApplicationCsvInfo csvInfo) {
56+
ApplicationGuestRequest applicationGuestRequest = getApplicationGuestRequestStepProcessor.process(csvInfo);
57+
58+
Long applicationId = applyGuestStepProcessor.process(applicationGuestRequest);
59+
60+
if (Boolean.TRUE.equals(csvInfo.isTestPaperChecked())) {
61+
changeTestPaperCheckedStepProcessor.process(applicationId);
62+
}
63+
64+
registerVirtualAccountStepProcessor.process(applicationId);
65+
}
66+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package life.mosu.mosuserver.application.admin.dto;
2+
3+
import java.time.LocalDate;
4+
import java.time.format.DateTimeParseException;
5+
6+
public record ApplicationCsvInfo(
7+
String createdAt,
8+
String name,
9+
String gender,
10+
LocalDate birth,
11+
String phoneNumber,
12+
LocalDate examDate,
13+
String examSchool,
14+
Boolean isLunchChecked,
15+
Boolean isTestPaperChecked,
16+
String subject1,
17+
String subject2
18+
) {
19+
public static ApplicationCsvInfo of(
20+
String createdAt,
21+
String name,
22+
String gender,
23+
LocalDate birth,
24+
String phoneNumber,
25+
LocalDate examDate,
26+
String examSchool,
27+
Boolean isLunchChecked,
28+
Boolean isTestPaperChecked,
29+
String subject1,
30+
String subject2
31+
) {
32+
return new ApplicationCsvInfo(createdAt, name, gender, birth, phoneNumber, examDate, examSchool, isLunchChecked, isTestPaperChecked, subject1, subject2);
33+
}
34+
35+
public static ApplicationCsvInfo of(String[] values) {
36+
if (values.length < 11) {
37+
throw new IllegalArgumentException("CSV 행이 필수 컬럼 수(11개)보다 부족합니다. 실제 컬럼 수: " + values.length);
38+
}
39+
return new ApplicationCsvInfo(
40+
values[0], // createdAt
41+
values[1], // name
42+
values[2], // gender
43+
parseDate(values[3]), // birth
44+
values[4], // phoneNumber
45+
parseDate(values[5]), // examDate
46+
values[6], // examSchool
47+
parseBoolean(values[7]), // isLunchChecked
48+
parseBoolean(values[8]), // isTestPaperChecked
49+
values[9], // subject1
50+
values[10] // subject2
51+
);
52+
}
53+
54+
private static LocalDate parseDate(String dateStr) {
55+
try {
56+
return LocalDate.parse(dateStr.trim());
57+
} catch (DateTimeParseException e) {
58+
throw new IllegalArgumentException("날짜 형식이 올바르지 않습니다");
59+
}
60+
}
61+
62+
private static Boolean parseBoolean(String bool) {
63+
if (bool == null || bool.isBlank()) {
64+
return false;
65+
}
66+
String trimmedValue = bool.trim();
67+
return Boolean.parseBoolean(trimmedValue);
68+
}
69+
70+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package life.mosu.mosuserver.application.admin.dto;
2+
3+
public record ImportResultDto(
4+
int totalProcessed,
5+
int totalSuccess,
6+
int totalFailure
7+
) {
8+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package life.mosu.mosuserver.application.admin.processor;
2+
3+
import life.mosu.mosuserver.application.application.ApplicationService;
4+
import life.mosu.mosuserver.domain.application.entity.ApplicationJpaEntity;
5+
import life.mosu.mosuserver.domain.application.entity.ApplicationStatus;
6+
import life.mosu.mosuserver.domain.application.repository.ApplicationJpaRepository;
7+
import life.mosu.mosuserver.global.processor.StepProcessor;
8+
import life.mosu.mosuserver.presentation.application.dto.ApplicationGuestRequest;
9+
import life.mosu.mosuserver.presentation.application.dto.CreateApplicationResponse;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.stereotype.Component;
12+
13+
@Component
14+
@RequiredArgsConstructor
15+
public class ApplyGuestStepProcessor implements StepProcessor<ApplicationGuestRequest, Long> {
16+
17+
private final ApplicationService applicationService;
18+
private final ApplicationJpaRepository applicationJpaRepository;
19+
20+
@Override
21+
public Long process(ApplicationGuestRequest request) {
22+
CreateApplicationResponse response = applicationService.applyByGuest(request);
23+
24+
Long applicationId = response.applicationId();
25+
ApplicationJpaEntity application = applicationJpaRepository.findById(applicationId)
26+
.orElseThrow(() -> new IllegalArgumentException("신청을 찾을 수 없습니다. id=" + applicationId));
27+
28+
application.changeStatus(ApplicationStatus.APPROVED);
29+
return applicationId;
30+
}
31+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package life.mosu.mosuserver.application.admin.processor;
2+
3+
import life.mosu.mosuserver.domain.examapplication.entity.ExamApplicationJpaEntity;
4+
import life.mosu.mosuserver.domain.examapplication.repository.ExamApplicationJpaRepository;
5+
import life.mosu.mosuserver.global.exception.CustomRuntimeException;
6+
import life.mosu.mosuserver.global.exception.ErrorCode;
7+
import life.mosu.mosuserver.global.processor.StepProcessor;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.stereotype.Component;
10+
11+
import java.util.List;
12+
import java.util.stream.Collectors;
13+
14+
@Component
15+
@RequiredArgsConstructor
16+
public class ChangeTestPaperCheckedStepProcessor implements StepProcessor<Long, List<Long>> {
17+
18+
private final ExamApplicationJpaRepository examApplicationJpaRepository;
19+
20+
@Override
21+
public List<Long> process(Long applicationId) {
22+
23+
List<ExamApplicationJpaEntity> examApplications = examApplicationJpaRepository.findByApplicationId(applicationId);
24+
if (examApplications.isEmpty()) {
25+
throw new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_NOT_FOUND);
26+
}
27+
28+
examApplications.forEach(ExamApplicationJpaEntity::setTestPaperChecked);
29+
30+
return examApplications.stream()
31+
.map(ExamApplicationJpaEntity::getId)
32+
.collect(Collectors.toList());
33+
}
34+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package life.mosu.mosuserver.application.admin.processor;
2+
3+
import life.mosu.mosuserver.application.admin.dto.ApplicationCsvInfo;
4+
import life.mosu.mosuserver.domain.exam.entity.ExamJpaEntity;
5+
import life.mosu.mosuserver.domain.exam.entity.ExamJpaRepository;
6+
import life.mosu.mosuserver.global.processor.StepProcessor;
7+
import life.mosu.mosuserver.presentation.application.dto.ApplicationGuestRequest;
8+
import life.mosu.mosuserver.presentation.application.dto.ExamApplicationRequest;
9+
import life.mosu.mosuserver.presentation.common.FileRequest;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.stereotype.Component;
12+
13+
import java.time.LocalDate;
14+
import java.util.Set;
15+
16+
@Component
17+
@RequiredArgsConstructor
18+
public class GetApplicationGuestRequestStepProcessor implements StepProcessor<ApplicationCsvInfo, ApplicationGuestRequest> {
19+
20+
private final ExamJpaRepository examJpaRepository;
21+
private static final String ORG_NAME = "모수사전예약";
22+
23+
@Override
24+
public ApplicationGuestRequest process(ApplicationCsvInfo csvInfo) {
25+
26+
LocalDate examDate = csvInfo.examDate();
27+
String schoolName = csvInfo.examSchool();
28+
ExamJpaEntity exam = examJpaRepository.findByExamDateAndSchoolName(examDate, schoolName);
29+
30+
ExamApplicationRequest examApplicationRequest = new ExamApplicationRequest(
31+
exam.getId(),
32+
csvInfo.isLunchChecked()
33+
);
34+
FileRequest admissionTicket = new FileRequest("", "");
35+
Set<String> subjects = Set.of(csvInfo.subject1(), csvInfo.subject2());
36+
37+
return new ApplicationGuestRequest(
38+
ORG_NAME,
39+
csvInfo.gender(),
40+
csvInfo.name(),
41+
csvInfo.birth(),
42+
csvInfo.phoneNumber(),
43+
examApplicationRequest,
44+
subjects,
45+
admissionTicket
46+
);
47+
}
48+
49+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package life.mosu.mosuserver.application.admin.processor;
2+
3+
import life.mosu.mosuserver.application.virtualaccount.VirtualAccountOrderIdGenerator;
4+
import life.mosu.mosuserver.domain.virtualaccount.VirtualAccountLogJpaEntity;
5+
import life.mosu.mosuserver.domain.virtualaccount.VirtualAccountLogJpaRepository;
6+
import life.mosu.mosuserver.global.processor.StepProcessor;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.stereotype.Component;
9+
10+
@Component
11+
@RequiredArgsConstructor
12+
public class RegisterVirtualAccountStepProcessor implements StepProcessor<Long, Long> {
13+
14+
private final VirtualAccountLogJpaRepository virtualAccountLogJpaRepository;
15+
private final VirtualAccountOrderIdGenerator orderIdGenerator;
16+
17+
@Override
18+
public Long process(Long applicationId) {
19+
String orderId = orderIdGenerator.generate();
20+
VirtualAccountLogJpaEntity virtualAccountLog = VirtualAccountLogJpaEntity.create(applicationId, orderId, null, null, null, null);
21+
virtualAccountLog.setDepositSuccess();
22+
VirtualAccountLogJpaEntity saved = virtualAccountLogJpaRepository.save(virtualAccountLog);
23+
24+
return saved.getId();
25+
}
26+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package life.mosu.mosuserver.application.admin.util;
2+
3+
import life.mosu.mosuserver.application.admin.dto.ApplicationCsvInfo;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.stereotype.Component;
6+
import org.springframework.web.multipart.MultipartFile;
7+
8+
import java.io.BufferedReader;
9+
import java.io.InputStreamReader;
10+
import java.nio.charset.StandardCharsets;
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
14+
@Slf4j
15+
@Component
16+
public class CsvReader {
17+
18+
private static final String CSV_DELIMITER = ",";
19+
20+
public List<ApplicationCsvInfo> read(MultipartFile file) {
21+
List<ApplicationCsvInfo> results = new ArrayList<>();
22+
23+
if (file == null || file.isEmpty()) {
24+
log.warn("업로드된 파일이 비어있습니다.");
25+
return results;
26+
}
27+
28+
try (BufferedReader reader =
29+
new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) {
30+
31+
String line;
32+
boolean isHeader = true;
33+
int lineNumber = 0;
34+
35+
while ((line = reader.readLine()) != null) {
36+
lineNumber++;
37+
if (line.isBlank()) continue;
38+
if (isHeader) {
39+
isHeader = false;
40+
continue;
41+
}
42+
43+
try {
44+
String[] values = line.split(CSV_DELIMITER, -1);
45+
results.add(ApplicationCsvInfo.of(values));
46+
} catch (Exception e) {
47+
log.error("CSV 파싱 실패 (Line {}): {}", lineNumber, e.getMessage());
48+
throw new RuntimeException("CSV 파싱 오류");
49+
50+
}
51+
}
52+
} catch (Exception e) {
53+
log.error("파일 읽기 실패: {}", e.getMessage(), e);
54+
throw new RuntimeException("CSV 읽기 오류");
55+
}
56+
57+
return results;
58+
}
59+
}

src/main/java/life/mosu/mosuserver/application/application/ApplicationContext.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
package life.mosu.mosuserver.application.application;
22

3-
import java.util.List;
4-
import java.util.Map;
5-
import java.util.Objects;
6-
import java.util.Optional;
7-
import java.util.Set;
8-
import java.util.function.Function;
9-
import java.util.stream.Collectors;
103
import life.mosu.mosuserver.domain.application.entity.ApplicationJpaEntity;
114
import life.mosu.mosuserver.domain.exam.entity.ExamJpaEntity;
125
import life.mosu.mosuserver.domain.examapplication.entity.ExamApplicationJpaEntity;
@@ -17,6 +10,10 @@
1710
import life.mosu.mosuserver.presentation.application.dto.ExamApplicationResponse;
1811
import life.mosu.mosuserver.presentation.examapplication.dto.ExamApplicationWithStatus;
1912

13+
import java.util.*;
14+
import java.util.function.Function;
15+
import java.util.stream.Collectors;
16+
2017
public record ApplicationContext(
2118
List<ApplicationJpaEntity> applications,
2219
List<ExamApplicationWithStatus> examApplications,
@@ -130,6 +127,7 @@ private Map.Entry<Long, ExamApplicationResponse> createExamApplicationResponse(
130127
exam.getExamDate(),
131128
subjects,
132129
lunchName
130+
// examApp.getIsTestPaperChecked()
133131
);
134132

135133
return Map.entry(examApp.getApplicationId(), response);

0 commit comments

Comments
 (0)