-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/ticket: 수험번호 및 수험표 발급 기능 구현 #381
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b12ea0d
9cf32a0
75f9183
53b3ec2
0168370
32cc885
10f9d2f
fa08f36
7b9a961
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| package life.mosu.mosuserver.application.admin; | ||
|
|
||
| import jakarta.transaction.Transactional; | ||
| import life.mosu.mosuserver.application.admin.dto.ApplicationCsvInfo; | ||
| import life.mosu.mosuserver.application.admin.dto.ImportResultDto; | ||
| import life.mosu.mosuserver.application.admin.processor.ApplyGuestStepProcessor; | ||
| import life.mosu.mosuserver.application.admin.processor.ChangeTestPaperCheckedStepProcessor; | ||
| import life.mosu.mosuserver.application.admin.processor.GetApplicationGuestRequestStepProcessor; | ||
| import life.mosu.mosuserver.application.admin.processor.RegisterVirtualAccountStepProcessor; | ||
| import life.mosu.mosuserver.application.admin.util.CsvReader; | ||
| import life.mosu.mosuserver.presentation.application.dto.ApplicationGuestRequest; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.web.multipart.MultipartFile; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Slf4j | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class AdminApplicationImportService { | ||
|
|
||
| private final GetApplicationGuestRequestStepProcessor getApplicationGuestRequestStepProcessor; | ||
| private final ApplyGuestStepProcessor applyGuestStepProcessor; | ||
| private final ChangeTestPaperCheckedStepProcessor changeTestPaperCheckedStepProcessor; | ||
| private final RegisterVirtualAccountStepProcessor registerVirtualAccountStepProcessor; | ||
| private final CsvReader csvReader; | ||
|
|
||
| @Transactional | ||
| public ImportResultDto importGuestApplications(MultipartFile file) { | ||
| List<ApplicationCsvInfo> rows = csvReader.read(file); | ||
|
|
||
| int total = rows.size(); | ||
| int success = 0; | ||
|
|
||
| int lineNo = 1; | ||
| for (ApplicationCsvInfo row : rows) { | ||
| lineNo++; | ||
| try { | ||
| processGuestRow(row); | ||
| success++; | ||
| } catch (Exception e) { | ||
| log.error("게스트 신청 CSV 행 {} 처리 실패: {}", lineNo, e.getMessage(), e); | ||
| throw new RuntimeException("CSV 일괄 처리 중 오류 발생"); | ||
| } | ||
| } | ||
|
|
||
| int fail = total - success; | ||
| log.info("CSV Import 완료 - 총 {}건, 성공 {}, 실패 {}", total, success, fail); | ||
| return new ImportResultDto(total, success, fail); | ||
| } | ||
|
Comment on lines
+30
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
또한, Spring 환경에서는 @Transactional
public ImportResultDto importGuestApplications(MultipartFile file) {
List<ApplicationCsvInfo> rows = csvReader.read(file);
int total = rows.size();
int lineNo = 1;
for (ApplicationCsvInfo row : rows) {
lineNo++;
try {
processGuestRow(row);
} catch (Exception e) {
log.error("게스트 신청 CSV 행 {} 처리 실패: {}", lineNo, e.getMessage(), e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
log.info("CSV Import 실패로 인해 롤백됩니다.");
return new ImportResultDto(total, 0, total);
}
}
log.info("CSV Import 완료 - 총 {}건, 성공 {}, 실패 {}", total, total, 0);
return new ImportResultDto(total, total, 0);
} |
||
|
|
||
|
|
||
| private void processGuestRow(ApplicationCsvInfo csvInfo) { | ||
| ApplicationGuestRequest applicationGuestRequest = getApplicationGuestRequestStepProcessor.process(csvInfo); | ||
|
|
||
| Long applicationId = applyGuestStepProcessor.process(applicationGuestRequest); | ||
|
|
||
| if (Boolean.TRUE.equals(csvInfo.isTestPaperChecked())) { | ||
| changeTestPaperCheckedStepProcessor.process(applicationId); | ||
| } | ||
|
|
||
| registerVirtualAccountStepProcessor.process(applicationId); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,70 @@ | ||||||||||||||||||||||||||||||||
| package life.mosu.mosuserver.application.admin.dto; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| import java.time.LocalDate; | ||||||||||||||||||||||||||||||||
| import java.time.format.DateTimeParseException; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| public record ApplicationCsvInfo( | ||||||||||||||||||||||||||||||||
| String createdAt, | ||||||||||||||||||||||||||||||||
| String name, | ||||||||||||||||||||||||||||||||
| String gender, | ||||||||||||||||||||||||||||||||
| LocalDate birth, | ||||||||||||||||||||||||||||||||
| String phoneNumber, | ||||||||||||||||||||||||||||||||
| LocalDate examDate, | ||||||||||||||||||||||||||||||||
| String examSchool, | ||||||||||||||||||||||||||||||||
| Boolean isLunchChecked, | ||||||||||||||||||||||||||||||||
| Boolean isTestPaperChecked, | ||||||||||||||||||||||||||||||||
| String subject1, | ||||||||||||||||||||||||||||||||
| String subject2 | ||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||
| public static ApplicationCsvInfo of( | ||||||||||||||||||||||||||||||||
| String createdAt, | ||||||||||||||||||||||||||||||||
| String name, | ||||||||||||||||||||||||||||||||
| String gender, | ||||||||||||||||||||||||||||||||
| LocalDate birth, | ||||||||||||||||||||||||||||||||
| String phoneNumber, | ||||||||||||||||||||||||||||||||
| LocalDate examDate, | ||||||||||||||||||||||||||||||||
| String examSchool, | ||||||||||||||||||||||||||||||||
| Boolean isLunchChecked, | ||||||||||||||||||||||||||||||||
| Boolean isTestPaperChecked, | ||||||||||||||||||||||||||||||||
| String subject1, | ||||||||||||||||||||||||||||||||
| String subject2 | ||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||
| return new ApplicationCsvInfo(createdAt, name, gender, birth, phoneNumber, examDate, examSchool, isLunchChecked, isTestPaperChecked, subject1, subject2); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| public static ApplicationCsvInfo of(String[] values) { | ||||||||||||||||||||||||||||||||
| if (values.length < 11) { | ||||||||||||||||||||||||||||||||
| throw new IllegalArgumentException("CSV 행이 필수 컬럼 수(11개)보다 부족합니다. 실제 컬럼 수: " + values.length); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+36
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||
| return new ApplicationCsvInfo( | ||||||||||||||||||||||||||||||||
| values[0], // createdAt | ||||||||||||||||||||||||||||||||
| values[1], // name | ||||||||||||||||||||||||||||||||
| values[2], // gender | ||||||||||||||||||||||||||||||||
| parseDate(values[3]), // birth | ||||||||||||||||||||||||||||||||
| values[4], // phoneNumber | ||||||||||||||||||||||||||||||||
| parseDate(values[5]), // examDate | ||||||||||||||||||||||||||||||||
| values[6], // examSchool | ||||||||||||||||||||||||||||||||
| parseBoolean(values[7]), // isLunchChecked | ||||||||||||||||||||||||||||||||
| parseBoolean(values[8]), // isTestPaperChecked | ||||||||||||||||||||||||||||||||
| values[9], // subject1 | ||||||||||||||||||||||||||||||||
| values[10] // subject2 | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| private static LocalDate parseDate(String dateStr) { | ||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||
| return LocalDate.parse(dateStr.trim()); | ||||||||||||||||||||||||||||||||
| } catch (DateTimeParseException e) { | ||||||||||||||||||||||||||||||||
| throw new IllegalArgumentException("날짜 형식이 올바르지 않습니다"); | ||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+54
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Improve error message to include the invalid value. The error message "날짜 형식이 올바르지 않습니다" (Date format is incorrect) doesn't indicate which value failed or what format is expected. This makes debugging difficult for users. Apply this diff to improve the error message: private static LocalDate parseDate(String dateStr) {
try {
return LocalDate.parse(dateStr.trim());
} catch (DateTimeParseException e) {
- throw new IllegalArgumentException("날짜 형식이 올바르지 않습니다");
+ throw new IllegalArgumentException(
+ "날짜 형식이 올바르지 않습니다. 입력값: '" + dateStr + "', 예상 형식: yyyy-MM-dd", e);
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| private static Boolean parseBoolean(String bool) { | ||||||||||||||||||||||||||||||||
| if (bool == null || bool.isBlank()) { | ||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| String trimmedValue = bool.trim(); | ||||||||||||||||||||||||||||||||
| return Boolean.parseBoolean(trimmedValue); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package life.mosu.mosuserver.application.admin.dto; | ||
|
|
||
| public record ImportResultDto( | ||
| int totalProcessed, | ||
| int totalSuccess, | ||
| int totalFailure | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,31 @@ | ||||||||||
| package life.mosu.mosuserver.application.admin.processor; | ||||||||||
|
|
||||||||||
| import life.mosu.mosuserver.application.application.ApplicationService; | ||||||||||
| import life.mosu.mosuserver.domain.application.entity.ApplicationJpaEntity; | ||||||||||
| import life.mosu.mosuserver.domain.application.entity.ApplicationStatus; | ||||||||||
| import life.mosu.mosuserver.domain.application.repository.ApplicationJpaRepository; | ||||||||||
| import life.mosu.mosuserver.global.processor.StepProcessor; | ||||||||||
| import life.mosu.mosuserver.presentation.application.dto.ApplicationGuestRequest; | ||||||||||
| import life.mosu.mosuserver.presentation.application.dto.CreateApplicationResponse; | ||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||
| import org.springframework.stereotype.Component; | ||||||||||
|
|
||||||||||
| @Component | ||||||||||
| @RequiredArgsConstructor | ||||||||||
| public class ApplyGuestStepProcessor implements StepProcessor<ApplicationGuestRequest, Long> { | ||||||||||
|
|
||||||||||
| private final ApplicationService applicationService; | ||||||||||
| private final ApplicationJpaRepository applicationJpaRepository; | ||||||||||
|
|
||||||||||
| @Override | ||||||||||
| public Long process(ApplicationGuestRequest request) { | ||||||||||
| CreateApplicationResponse response = applicationService.applyByGuest(request); | ||||||||||
|
|
||||||||||
| Long applicationId = response.applicationId(); | ||||||||||
| ApplicationJpaEntity application = applicationJpaRepository.findById(applicationId) | ||||||||||
| .orElseThrow(() -> new IllegalArgumentException("신청을 찾을 수 없습니다. id=" + applicationId)); | ||||||||||
|
Comment on lines
+22
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Inefficient fetch immediately after creation. The code fetches the application entity from the database immediately after creating it via Consider one of the following approaches:
For approach 1, you would refactor the service method to accept the status as a parameter or return the entity directly: ApplicationJpaEntity application = applicationService.applyByGuestAndReturn(request, ApplicationStatus.APPROVED);
return application.getId();This reduces database operations and improves performance for bulk imports. 🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| application.changeStatus(ApplicationStatus.APPROVED); | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Status change is not persisted to the database. Line 28 calls Apply this diff to persist the status change: application.changeStatus(ApplicationStatus.APPROVED);
+applicationJpaRepository.save(application);
return applicationId;Alternatively, if 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| return applicationId; | ||||||||||
| } | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package life.mosu.mosuserver.application.admin.processor; | ||
|
|
||
| import life.mosu.mosuserver.domain.examapplication.entity.ExamApplicationJpaEntity; | ||
| import life.mosu.mosuserver.domain.examapplication.repository.ExamApplicationJpaRepository; | ||
| import life.mosu.mosuserver.global.exception.CustomRuntimeException; | ||
| import life.mosu.mosuserver.global.exception.ErrorCode; | ||
| import life.mosu.mosuserver.global.processor.StepProcessor; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class ChangeTestPaperCheckedStepProcessor implements StepProcessor<Long, List<Long>> { | ||
|
|
||
| private final ExamApplicationJpaRepository examApplicationJpaRepository; | ||
|
|
||
| @Override | ||
| public List<Long> process(Long applicationId) { | ||
|
|
||
| List<ExamApplicationJpaEntity> examApplications = examApplicationJpaRepository.findByApplicationId(applicationId); | ||
| if (examApplications.isEmpty()) { | ||
| throw new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_NOT_FOUND); | ||
| } | ||
|
|
||
| examApplications.forEach(ExamApplicationJpaEntity::setTestPaperChecked); | ||
|
|
||
| return examApplications.stream() | ||
| .map(ExamApplicationJpaEntity::getId) | ||
| .collect(Collectors.toList()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,49 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| package life.mosu.mosuserver.application.admin.processor; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| import life.mosu.mosuserver.application.admin.dto.ApplicationCsvInfo; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import life.mosu.mosuserver.domain.exam.entity.ExamJpaEntity; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import life.mosu.mosuserver.domain.exam.entity.ExamJpaRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import life.mosu.mosuserver.global.processor.StepProcessor; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import life.mosu.mosuserver.presentation.application.dto.ApplicationGuestRequest; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import life.mosu.mosuserver.presentation.application.dto.ExamApplicationRequest; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import life.mosu.mosuserver.presentation.common.FileRequest; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.LocalDate; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Set; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||
| public class GetApplicationGuestRequestStepProcessor implements StepProcessor<ApplicationCsvInfo, ApplicationGuestRequest> { | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| private final ExamJpaRepository examJpaRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||
| private static final String ORG_NAME = "모수사전예약"; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||
| public ApplicationGuestRequest process(ApplicationCsvInfo csvInfo) { | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| LocalDate examDate = csvInfo.examDate(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| String schoolName = csvInfo.examSchool(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| ExamJpaEntity exam = examJpaRepository.findByExamDateAndSchoolName(examDate, schoolName); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
ExamJpaEntity exam = examJpaRepository.findByExamDateAndSchoolName(examDate, schoolName);
if (exam == null) {
throw new IllegalArgumentException("해당 시험 정보가 존재하지 않습니다. examDate=" + examDate + ", schoolName=" + schoolName);
} |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| ExamApplicationRequest examApplicationRequest = new ExamApplicationRequest( | ||||||||||||||||||||||||||||||||||||||||||||||||
| exam.getId(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| csvInfo.isLunchChecked() | ||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+28
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard against missing exam lookups.
+import life.mosu.mosuserver.global.exception.CustomRuntimeException;
+import life.mosu.mosuserver.global.exception.ErrorCode;
+import java.util.Optional;
@@
- ExamJpaEntity exam = examJpaRepository.findByExamDateAndSchoolName(examDate, schoolName);
+ ExamJpaEntity exam = Optional.ofNullable(
+ examJpaRepository.findByExamDateAndSchoolName(examDate, schoolName))
+ .orElseThrow(() -> new CustomRuntimeException(ErrorCode.EXAM_NOT_FOUND));📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
| FileRequest admissionTicket = new FileRequest("", ""); | ||||||||||||||||||||||||||||||||||||||||||||||||
| Set<String> subjects = Set.of(csvInfo.subject1(), csvInfo.subject2()); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return new ApplicationGuestRequest( | ||||||||||||||||||||||||||||||||||||||||||||||||
| ORG_NAME, | ||||||||||||||||||||||||||||||||||||||||||||||||
| csvInfo.gender(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| csvInfo.name(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| csvInfo.birth(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| csvInfo.phoneNumber(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| examApplicationRequest, | ||||||||||||||||||||||||||||||||||||||||||||||||
| subjects, | ||||||||||||||||||||||||||||||||||||||||||||||||
| admissionTicket | ||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package life.mosu.mosuserver.application.admin.processor; | ||
|
|
||
| import life.mosu.mosuserver.application.virtualaccount.VirtualAccountOrderIdGenerator; | ||
| import life.mosu.mosuserver.domain.virtualaccount.VirtualAccountLogJpaEntity; | ||
| import life.mosu.mosuserver.domain.virtualaccount.VirtualAccountLogJpaRepository; | ||
| import life.mosu.mosuserver.global.processor.StepProcessor; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class RegisterVirtualAccountStepProcessor implements StepProcessor<Long, Long> { | ||
|
|
||
| private final VirtualAccountLogJpaRepository virtualAccountLogJpaRepository; | ||
| private final VirtualAccountOrderIdGenerator orderIdGenerator; | ||
|
|
||
| @Override | ||
| public Long process(Long applicationId) { | ||
| String orderId = orderIdGenerator.generate(); | ||
| VirtualAccountLogJpaEntity virtualAccountLog = VirtualAccountLogJpaEntity.create(applicationId, orderId, null, null, null, null); | ||
| virtualAccountLog.setDepositSuccess(); | ||
| VirtualAccountLogJpaEntity saved = virtualAccountLogJpaRepository.save(virtualAccountLog); | ||
|
|
||
| return saved.getId(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,59 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package life.mosu.mosuserver.application.admin.util; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import life.mosu.mosuserver.application.admin.dto.ApplicationCsvInfo; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.web.multipart.MultipartFile; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.BufferedReader; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.InputStreamReader; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.nio.charset.StandardCharsets; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.ArrayList; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Slf4j | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class CsvReader { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static final String CSV_DELIMITER = ","; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public List<ApplicationCsvInfo> read(MultipartFile file) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<ApplicationCsvInfo> results = new ArrayList<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (file == null || file.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.warn("업로드된 파일이 비어있습니다."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return results; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try (BufferedReader reader = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String line; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| boolean isHeader = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int lineNumber = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while ((line = reader.readLine()) != null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lineNumber++; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (line.isBlank()) continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isHeader) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isHeader = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String[] values = line.split(CSV_DELIMITER, -1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| results.add(ApplicationCsvInfo.of(values)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.error("CSV 파싱 실패 (Line {}): {}", lineNumber, e.getMessage()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new RuntimeException("CSV 파싱 오류"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+46
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Chain the original exception as cause for better debugging. Lines 48 and 54 throw new Apply this diff: } catch (Exception e) {
log.error("CSV 파싱 실패 (Line {}): {}", lineNumber, e.getMessage());
- throw new RuntimeException("CSV 파싱 오류");
+ throw new RuntimeException("CSV 파싱 오류 at line " + lineNumber, e);
} } catch (Exception e) {
log.error("파일 읽기 실패: {}", e.getMessage(), e);
- throw new RuntimeException("CSV 읽기 오류");
+ throw new RuntimeException("CSV 읽기 오류", e);
}Also applies to: 52-55 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.error("파일 읽기 실패: {}", e.getMessage(), e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new RuntimeException("CSV 읽기 오류"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+43
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preserve exception information when rethrowing. The current code catches generic Apply this fix to preserve exception context: try {
String[] values = line.split(CSV_DELIMITER, -1);
results.add(ApplicationCsvInfo.of(values));
} catch (Exception e) {
log.error("CSV 파싱 실패 (Line {}): {}", lineNumber, e.getMessage());
- throw new RuntimeException("CSV 파싱 오류");
+ throw new RuntimeException("CSV 파싱 오류 (Line " + lineNumber + ")", e);
}
}
} catch (Exception e) {
log.error("파일 읽기 실패: {}", e.getMessage(), e);
- throw new RuntimeException("CSV 읽기 오류");
+ throw new RuntimeException("CSV 읽기 오류", e);
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return results; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+20
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Use a proper CSV parsing library instead of String.split(). The current implementation uses
Consider using a battle-tested CSV library like Apache Commons CSV or OpenCSV: Using Apache Commons CSV: Add dependency: <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.12.0</version>
</dependency>Refactor the import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
public List<ApplicationCsvInfo> read(MultipartFile file) {
List<ApplicationCsvInfo> results = new ArrayList<>();
if (file == null || file.isEmpty()) {
log.warn("업로드된 파일이 비어있습니다.");
return results;
}
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8));
CSVParser csvParser = new CSVParser(reader,
CSVFormat.DEFAULT.withFirstRecordAsHeader().withIgnoreEmptyLines())) {
for (CSVRecord record : csvParser) {
try {
// Convert CSVRecord to String[] if needed, or adapt ApplicationCsvInfo.of()
results.add(ApplicationCsvInfo.of(record));
} catch (Exception e) {
log.error("CSV 파싱 실패 (Line {}): {}", record.getRecordNumber(), e.getMessage());
throw new RuntimeException("CSV 파싱 오류 at line " + record.getRecordNumber(), e);
}
}
} catch (IOException e) {
log.error("파일 읽기 실패: {}", e.getMessage(), e);
throw new RuntimeException("CSV 읽기 오류", e);
}
return results;
}This provides proper CSV parsing with quoted field support and better error context. 🤖 Prompt for AI AgentsAdd file size and row count limits to prevent DoS. The current implementation has no limits on:
Apply this diff to add safety limits: +private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
+private static final int MAX_ROWS = 10000;
+
public List<ApplicationCsvInfo> read(MultipartFile file) {
List<ApplicationCsvInfo> results = new ArrayList<>();
if (file == null || file.isEmpty()) {
log.warn("업로드된 파일이 비어있습니다.");
return results;
}
+ if (file.getSize() > MAX_FILE_SIZE) {
+ throw new IllegalArgumentException("파일 크기가 너무 큽니다. 최대 10MB까지 가능합니다.");
+ }
+
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) {
String line;
boolean isHeader = true;
int lineNumber = 0;
while ((line = reader.readLine()) != null) {
lineNumber++;
+ if (lineNumber > MAX_ROWS + 1) { // +1 for header
+ throw new IllegalArgumentException("파일 행 수가 너무 많습니다. 최대 " + MAX_ROWS + "행까지 가능합니다.");
+ }
if (line.isBlank()) continue;Adjust the limits based on your production requirements. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,5 @@ | ||
| package life.mosu.mosuserver.application.application; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import java.util.Optional; | ||
| import java.util.Set; | ||
| import java.util.function.Function; | ||
| import java.util.stream.Collectors; | ||
| import life.mosu.mosuserver.domain.application.entity.ApplicationJpaEntity; | ||
| import life.mosu.mosuserver.domain.exam.entity.ExamJpaEntity; | ||
| import life.mosu.mosuserver.domain.examapplication.entity.ExamApplicationJpaEntity; | ||
|
|
@@ -17,6 +10,10 @@ | |
| import life.mosu.mosuserver.presentation.application.dto.ExamApplicationResponse; | ||
| import life.mosu.mosuserver.presentation.examapplication.dto.ExamApplicationWithStatus; | ||
|
|
||
| import java.util.*; | ||
| import java.util.function.Function; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| public record ApplicationContext( | ||
| List<ApplicationJpaEntity> applications, | ||
| List<ExamApplicationWithStatus> examApplications, | ||
|
|
@@ -130,6 +127,7 @@ private Map.Entry<Long, ExamApplicationResponse> createExamApplicationResponse( | |
| exam.getExamDate(), | ||
| subjects, | ||
| lunchName | ||
| // examApp.getIsTestPaperChecked() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ); | ||
|
|
||
| return Map.entry(examApp.getApplicationId(), response); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not abort the entire import on the first row error.
The catch block rethrows a
RuntimeExceptionon the first failing row, so the loop stops immediately, the transaction rolls back, and the summary logic (including the failure count) is never reached. As a result, a single bad row prevents all other valid applications from being registered—defeating the purpose of batch import and the success/fail statistics you’re computing.Handle the exception per row (log it, continue, and let the failure counter increase) instead of propagating it so that the rest of the CSV can still be processed. For example:
This keeps the loop running, allowing
fail = total - successto reflect actual results while leaving successful rows committed.📝 Committable suggestion
🤖 Prompt for AI Agents