-
Notifications
You must be signed in to change notification settings - Fork 2
MOSU-147 refactor: 신청 response 및 로직 수정, 엔티티 필드 추가 #154
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
4e92e02
3474ce7
da03c98
98f0034
0fc075c
86683e3
00b2ccf
3afc30c
90dddbb
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 |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| package life.mosu.mosuserver.application.examapplication; | ||
|
|
||
| import java.util.HashSet; | ||
| import java.time.LocalDate; | ||
| import java.time.LocalDateTime; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| import java.util.stream.Collectors; | ||
|
|
@@ -15,6 +16,7 @@ | |
| import life.mosu.mosuserver.domain.examapplication.projection.ExamApplicationInfoProjection; | ||
| import life.mosu.mosuserver.domain.examapplication.projection.ExamTicketInfoProjection; | ||
| import life.mosu.mosuserver.domain.examapplication.service.ExamNumberGenerationService; | ||
| import life.mosu.mosuserver.domain.payment.PaymentJpaRepository; | ||
| import life.mosu.mosuserver.global.exception.CustomRuntimeException; | ||
| import life.mosu.mosuserver.global.exception.ErrorCode; | ||
| import life.mosu.mosuserver.infra.persistence.s3.S3Service; | ||
|
|
@@ -23,10 +25,12 @@ | |
| import life.mosu.mosuserver.presentation.examapplication.dto.ExamApplicationInfoResponse; | ||
| import life.mosu.mosuserver.presentation.examapplication.dto.UpdateSubjectRequest; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Propagation; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Slf4j | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class ExamApplicationService { | ||
|
|
@@ -35,6 +39,7 @@ public class ExamApplicationService { | |
| private final ApplicationJpaRepository applicationJpaRepository; | ||
| private final ExamSubjectJpaRepository examSubjectJpaRepository; | ||
| private final ExamNumberGenerationService examNumberGenerationService; | ||
| private final PaymentJpaRepository paymentJpaRepository; | ||
| private final S3Service s3Service; | ||
| private final FixedQuantityDiscountCalculator calculator; | ||
|
|
||
|
|
@@ -47,62 +52,44 @@ public List<ExamApplicationJpaEntity> register(RegisterExamApplicationEvent even | |
| } | ||
|
|
||
| @Transactional | ||
| public ExamApplicationInfoResponse updateSubjects(Long examApplicationId, | ||
| public void updateSubjects(Long userId, Long examApplicationId, | ||
| UpdateSubjectRequest request) { | ||
|
|
||
| examSubjectJpaRepository.deleteByExamApplicationId(examApplicationId); | ||
| validateUser(userId, examApplicationId); | ||
| examSubjectJpaRepository.deleteExamSubjectsWithDonePayment(examApplicationId); | ||
| List<ExamSubjectJpaEntity> examSubjects = request.toEntityList(examApplicationId); | ||
| examSubjectJpaRepository.saveAll(examSubjects); | ||
|
|
||
| ExamApplicationInfoProjection examApplicationInfo = examApplicationJpaRepository | ||
| .findExamApplicationInfoById(examApplicationId) | ||
| .orElseThrow( | ||
| () -> new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_NOT_FOUND)); | ||
|
|
||
| Integer totalAmount = examApplicationInfo.paymentAmount().getTotalAmount(); | ||
| Integer discountAmount = getAppliedDiscountAmount(totalAmount); | ||
| AddressResponse address = AddressResponse.from(examApplicationInfo.address()); | ||
| Set<String> subjectSet = new HashSet<>(request.subjects()); | ||
|
|
||
| return ExamApplicationInfoResponse.of( | ||
| examApplicationInfo.examApplicationId(), | ||
| examApplicationInfo.paymentKey(), | ||
| examApplicationInfo.examDate(), | ||
| examApplicationInfo.schoolName(), | ||
| address, | ||
| subjectSet, | ||
| examApplicationInfo.lunchName(), | ||
| totalAmount, | ||
| discountAmount, | ||
| examApplicationInfo.paymentMethod().getName() | ||
| ); | ||
| } | ||
|
|
||
| @Transactional(propagation = Propagation.REQUIRES_NEW) | ||
| public void deleteExamApplication(Long examApplicationId) { | ||
| public void deleteExamApplication(Long userId, Long examApplicationId) { | ||
| validateUser(userId, examApplicationId); | ||
|
|
||
| ExamApplicationJpaEntity examApplication = examApplicationJpaRepository.findById( | ||
| examApplicationId) | ||
| .orElseThrow( | ||
| () -> new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_NOT_FOUND)); | ||
| Long applicationId = examApplication.getApplicationId(); | ||
|
|
||
| examApplicationJpaRepository.deleteById(examApplicationId); | ||
| examApplicationJpaRepository.updateDeleteById(examApplicationId); | ||
|
|
||
| if (!examApplicationJpaRepository.existsByApplicationId(applicationId)) { | ||
| applicationJpaRepository.deleteById(applicationId); | ||
| applicationJpaRepository.deleteWithExamTicketById(applicationId); | ||
| } | ||
|
|
||
| examSubjectJpaRepository.deleteByExamApplicationId(examApplicationId); | ||
|
|
||
| } | ||
|
|
||
|
|
||
| @Transactional | ||
| public ExamTicketResponse getExamTicket(Long examApplicationId) { | ||
| public ExamTicketResponse getExamTicket(Long userId, Long examApplicationId) { | ||
| ExamTicketInfoProjection examTicketInfo = examApplicationJpaRepository.findExamTicketInfoProjectionById( | ||
| examApplicationId) | ||
| userId, examApplicationId) | ||
| .orElseThrow( | ||
| () -> new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_NOT_FOUND)); | ||
| () -> new CustomRuntimeException(ErrorCode.EXAM_RESOURCE_ACCESS_DENIED)); | ||
|
|
||
| validateExamTicketOpenDate(examTicketInfo.examDate(), examTicketInfo.examNumber()); | ||
|
|
||
| List<ExamSubjectJpaEntity> examSubjects = examSubjectJpaRepository.findByExamApplicationId( | ||
| examApplicationId); | ||
|
|
@@ -112,31 +99,51 @@ public ExamTicketResponse getExamTicket(Long examApplicationId) { | |
| .map(Subject::getSubjectName) | ||
| .toList(); | ||
|
|
||
| String examTicketImgUrl = s3Service.getPreSignedUrl(examTicketInfo.s3Key()); | ||
| String s3Key = examTicketInfo.s3Key(); | ||
| String examTicketImgUrl = null; | ||
|
|
||
| if (s3Key != null) { | ||
| examTicketImgUrl = s3Service.getPreSignedUrl(s3Key); | ||
| } | ||
|
|
||
| return ExamTicketResponse.of(examTicketImgUrl, examTicketInfo.userName(), | ||
| examTicketInfo.birth(), | ||
| examTicketInfo.examNumber(), subjects, examTicketInfo.schoolName()); | ||
|
|
||
| } | ||
|
|
||
| //TODO: 테스트 필요 | ||
| public ExamApplicationInfoResponse getApplication(Long examApplicationId) { | ||
|
|
||
| public ExamApplicationInfoResponse getApplication(Long userId, Long examApplicationId, | ||
| Long applicationId) { | ||
| validateUser(userId, examApplicationId); | ||
|
|
||
| //상세 조회는 done 만 가능 | ||
| // Integer examApplicationCount = paymentJpaRepository.countByExamApplicationId( | ||
| // examApplicationId); | ||
| List<ExamApplicationJpaEntity> examApplicationEntities = examApplicationJpaRepository.findByApplicationId( | ||
| applicationId); | ||
| Integer lunchCount = (int) examApplicationEntities.stream() | ||
| .filter(ExamApplicationJpaEntity::getIsLunchChecked) | ||
| .count(); | ||
|
|
||
| ExamApplicationInfoProjection examApplicationInfo = examApplicationJpaRepository | ||
| .findExamApplicationInfoById(examApplicationId) | ||
| .findExamApplicationInfoById(userId, examApplicationId) | ||
| .orElseThrow( | ||
| () -> new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_NOT_FOUND)); | ||
|
|
||
| List<ExamSubjectJpaEntity> examSubjects = | ||
| examSubjectJpaRepository.findByExamApplicationId(examApplicationId); | ||
|
|
||
| Set<String> subjects = examSubjects.stream() | ||
| .map(examSubject -> examSubject.getSubject() | ||
| .getSubjectName()) | ||
| .map(ExamSubjectJpaEntity::getSubjectName) | ||
| .collect(Collectors.toSet()); | ||
| //totalAmount 는 Lunch 가격이 포함되었을 수도 있음 | ||
| //totalAmount - Lunch 가격으로 getAppliedDiscountAmount() 메소드에 넣어야함. | ||
|
|
||
| Integer totalAmount = examApplicationInfo.paymentAmount().getTotalAmount(); | ||
| Integer discountAmount = getAppliedDiscountAmount(totalAmount); | ||
| Integer discountAmount = getAppliedDiscountAmount( | ||
| lunchCount > 0 ? totalAmount - (9000 * lunchCount) | ||
| : totalAmount); | ||
|
|
||
| Integer paymentAmount = | ||
| examApplicationInfo.paymentAmount().getTotalAmount() + discountAmount; | ||
|
|
@@ -155,7 +162,28 @@ public ExamApplicationInfoResponse getApplication(Long examApplicationId) { | |
| ); | ||
| } | ||
|
|
||
| private void validateUser(Long userId, Long examApplicationId) { | ||
| boolean check = examApplicationJpaRepository.existByUserIdAndExamApplicationId( | ||
| userId, | ||
| examApplicationId); | ||
|
|
||
| if (!check) { | ||
| throw new CustomRuntimeException(ErrorCode.USER_NOT_ACCESS_FORBIDDEN); | ||
| } | ||
| } | ||
|
|
||
| private int getAppliedDiscountAmount(Integer totalAmount) { | ||
| log.info("total amount: {}", totalAmount); | ||
| return calculator.getAppliedDiscountAmount(totalAmount); | ||
| } | ||
|
|
||
| private void validateExamTicketOpenDate(LocalDate examDate, String examNumber) { | ||
|
|
||
| LocalDateTime openDateTime = examDate.minusDays(3).atTime(8, 0); | ||
|
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. The values LocalDateTime openDateTime = examDate.minusDays(EXAM_TICKET_OPEN_DAYS_BEFORE).atTime(EXAM_TICKET_OPEN_HOUR, 0); |
||
| LocalDateTime now = LocalDateTime.now(); | ||
|
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. Using LocalDateTime now = LocalDateTime.now(clock); // Assuming a Clock instance is injected |
||
|
|
||
| if (examNumber == null || now.isBefore(openDateTime)) { | ||
| throw new CustomRuntimeException(ErrorCode.EXAM_TICKET_NOT_OPEN); | ||
| } | ||
| } | ||
| } | ||
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.
Using the magic number
9000for the lunch price makes the code harder to maintain and prone to inconsistencies. This value should be externalized to a constant, perhaps in a configuration file or a shared constants class, to be used across the application. TheDatabaseInitializeralready defines aLUNCH_PRICEconstant, and a similar approach should be adopted here.