From e40b09a8b74f1f28d5facd8422233bb252a04bec Mon Sep 17 00:00:00 2001 From: dongkyeomjang Date: Thu, 21 Nov 2024 02:56:03 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Feature/#46=20-=20=EA=B0=81=20?= =?UTF-8?q?=EA=B0=80=EA=B2=8C=EB=B3=84=20Event=20=EC=8A=A4=EC=BC=80?= =?UTF-8?q?=EC=A4=84=EB=A7=81=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 + http/onjung/OnjungControllerHttpRequest.http | 1 + .../response/ReadStoreDetailResponseDto.java | 10 +- .../service/ReadStoreDetailService.java | 2 +- .../repository/mysql/UserRepository.java | 7 + .../onjung/core/config/SchedulerConfig.java | 39 +++++ .../core/exception/error/ErrorCode.java | 1 + .../core/listener/AppEventListener.java | 44 +++++ .../application/controller/command}/.keep | 0 .../EventSchedulerConsumerV1Controller.java | 20 +++ .../application/controller/{ => query}/.keep | 0 .../onjung/event/application/service/.keep | 0 .../service/ProcessCompletedEventService.java | 165 ++++++++++++++++++ .../onjung/event/application/usecase/.keep | 0 .../usecase/ProcessCompletedEventUseCase.java | 8 + .../com/daon/onjung/event/domain/Event.java | 14 +- .../com/daon/onjung/event/domain/Ticket.java | 2 +- .../event/domain/event/EventScheduled.java | 16 ++ .../event/domain/service/EventService.java | 16 ++ .../event/domain/service/TicketService.java | 31 ++++ .../repository/mysql/TicketRepository.java | 3 +- .../dto/request/CreateDonationRequestDto.java | 3 + .../service/CreateDonationService.java | 23 +-- .../daon/onjung/onjung/domain/Donation.java | 8 +- .../domain/service/DonationService.java | 4 +- .../repository/mysql/DonationRepository.java | 4 + .../service/SignUpOwnerByDefaultService.java | 52 ++++++ 27 files changed, 444 insertions(+), 32 deletions(-) create mode 100644 src/main/java/com/daon/onjung/core/config/SchedulerConfig.java create mode 100644 src/main/java/com/daon/onjung/core/listener/AppEventListener.java rename src/main/java/com/daon/onjung/{account/application/service => event/application/controller/command}/.keep (100%) create mode 100644 src/main/java/com/daon/onjung/event/application/controller/consumer/EventSchedulerConsumerV1Controller.java rename src/main/java/com/daon/onjung/event/application/controller/{ => query}/.keep (100%) delete mode 100644 src/main/java/com/daon/onjung/event/application/service/.keep create mode 100644 src/main/java/com/daon/onjung/event/application/service/ProcessCompletedEventService.java delete mode 100644 src/main/java/com/daon/onjung/event/application/usecase/.keep create mode 100644 src/main/java/com/daon/onjung/event/application/usecase/ProcessCompletedEventUseCase.java create mode 100644 src/main/java/com/daon/onjung/event/domain/event/EventScheduled.java create mode 100644 src/main/java/com/daon/onjung/event/domain/service/TicketService.java diff --git a/build.gradle b/build.gradle index be81673..3db7aaa 100644 --- a/build.gradle +++ b/build.gradle @@ -68,6 +68,9 @@ dependencies { implementation 'org.springframework.cloud:spring-cloud-aws-context:2.2.6.RELEASE' implementation 'org.springframework.cloud:spring-cloud-aws-autoconfigure:2.2.6.RELEASE' + // Secheduler + implementation 'org.springframework.boot:spring-boot-starter-quartz' + // Testing Dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' diff --git a/http/onjung/OnjungControllerHttpRequest.http b/http/onjung/OnjungControllerHttpRequest.http index 236cf28..4f76230 100644 --- a/http/onjung/OnjungControllerHttpRequest.http +++ b/http/onjung/OnjungControllerHttpRequest.http @@ -50,5 +50,6 @@ Authorization: Bearer {{access_token}} Content-Type: application/json { + "event_id": {{onjung.API_4_7.event_id}}, "donation_amount": {{onjung.API_4_7.donation_amount}} } diff --git a/src/main/java/com/daon/onjung/account/application/dto/response/ReadStoreDetailResponseDto.java b/src/main/java/com/daon/onjung/account/application/dto/response/ReadStoreDetailResponseDto.java index d68be60..1630625 100644 --- a/src/main/java/com/daon/onjung/account/application/dto/response/ReadStoreDetailResponseDto.java +++ b/src/main/java/com/daon/onjung/account/application/dto/response/ReadStoreDetailResponseDto.java @@ -131,6 +131,10 @@ public static StoreInfoDto fromEntity(Store store) { @Getter public static class EventInfoDto { + @NotNull(message = "id는 null일 수 없습니다.") + @JsonProperty("id") + private final Long id; + @NotNull(message = "total_amount는 null일 수 없습니다.") @JsonProperty("total_amount") private final Integer totalAmount; @@ -140,13 +144,15 @@ public static class EventInfoDto { private final Integer restOfDate; @Builder - public EventInfoDto(Integer totalAmount, Integer restOfDate) { + public EventInfoDto(Integer totalAmount, Integer restOfDate, Long id) { + this.id = id; this.totalAmount = totalAmount; this.restOfDate = restOfDate; } - public static EventInfoDto fromEntity(Integer totalAmount, Integer restOfDate) { + public static EventInfoDto of(Long id, Integer totalAmount, Integer restOfDate) { return EventInfoDto.builder() + .id(id) .totalAmount(totalAmount) .restOfDate(restOfDate) .build(); diff --git a/src/main/java/com/daon/onjung/account/application/service/ReadStoreDetailService.java b/src/main/java/com/daon/onjung/account/application/service/ReadStoreDetailService.java index a92e190..37cf5a4 100644 --- a/src/main/java/com/daon/onjung/account/application/service/ReadStoreDetailService.java +++ b/src/main/java/com/daon/onjung/account/application/service/ReadStoreDetailService.java @@ -50,7 +50,7 @@ public ReadStoreDetailResponseDto execute(Long id) { // event 정보 Integer totalAmount = getTotalAmount(id); - ReadStoreDetailResponseDto.EventInfoDto eventInfoDto = ReadStoreDetailResponseDto.EventInfoDto.fromEntity(totalAmount, eventService.getRestOfDate(event)); + ReadStoreDetailResponseDto.EventInfoDto eventInfoDto = ReadStoreDetailResponseDto.EventInfoDto.of(event.getId(), totalAmount, eventService.getRestOfDate(event)); // onjung 정보 (null 값 대신 0으로 Integer totalOnjungCount = Optional.ofNullable(storeRepository.countUsersByStoreId(id)).orElse(0); diff --git a/src/main/java/com/daon/onjung/account/repository/mysql/UserRepository.java b/src/main/java/com/daon/onjung/account/repository/mysql/UserRepository.java index 5e35088..6bba8b3 100644 --- a/src/main/java/com/daon/onjung/account/repository/mysql/UserRepository.java +++ b/src/main/java/com/daon/onjung/account/repository/mysql/UserRepository.java @@ -2,11 +2,18 @@ import com.daon.onjung.account.domain.User; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; import java.util.UUID; + @Repository public interface UserRepository extends JpaRepository { Optional findBySerialId(String serialId); + + @Query("SELECT u.id FROM User u") + List findAllUserIds(); + } diff --git a/src/main/java/com/daon/onjung/core/config/SchedulerConfig.java b/src/main/java/com/daon/onjung/core/config/SchedulerConfig.java new file mode 100644 index 0000000..27b5055 --- /dev/null +++ b/src/main/java/com/daon/onjung/core/config/SchedulerConfig.java @@ -0,0 +1,39 @@ +package com.daon.onjung.core.config; + +import lombok.Setter; +import org.quartz.spi.TriggerFiredBundle; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.quartz.SchedulerFactoryBean; +import org.springframework.scheduling.quartz.SpringBeanJobFactory; + +@Configuration +public class SchedulerConfig { + + @Bean + public SchedulerFactoryBean schedulerFactoryBean(AutowiringSpringBeanJobFactory jobFactory) { + SchedulerFactoryBean factoryBean = new SchedulerFactoryBean(); + factoryBean.setJobFactory(jobFactory); // Spring 빈으로 관리되는 JobFactory 주입 + return factoryBean; + } + + @Bean + public AutowiringSpringBeanJobFactory jobFactory(AutowireCapableBeanFactory beanFactory) { + AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(); + jobFactory.setBeanFactory(beanFactory); // Spring의 BeanFactory 주입 + return jobFactory; + } + + @Setter + public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory { + private AutowireCapableBeanFactory beanFactory; + + @Override + protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { + Object job = super.createJobInstance(bundle); + beanFactory.autowireBean(job); // Spring 의존성 주입 + return job; + } + } +} diff --git a/src/main/java/com/daon/onjung/core/exception/error/ErrorCode.java b/src/main/java/com/daon/onjung/core/exception/error/ErrorCode.java index 280220f..bb770b4 100644 --- a/src/main/java/com/daon/onjung/core/exception/error/ErrorCode.java +++ b/src/main/java/com/daon/onjung/core/exception/error/ErrorCode.java @@ -47,6 +47,7 @@ public enum ErrorCode { INTERNAL_SERVER_ERROR(50000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 에러입니다."), INTERNAL_DATA_ERROR(50001, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 데이터 에러입니다."), UPLOAD_FILE_ERROR(50002, HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드에 실패하였습니다."), + SCHEDULER_ERROR(50003, HttpStatus.INTERNAL_SERVER_ERROR, "스케줄러 등록에 실패하였습니다."), // External Server Error EXTERNAL_SERVER_ERROR(50200, HttpStatus.BAD_GATEWAY, "서버 외부 에러입니다."), diff --git a/src/main/java/com/daon/onjung/core/listener/AppEventListener.java b/src/main/java/com/daon/onjung/core/listener/AppEventListener.java new file mode 100644 index 0000000..0c5c4b3 --- /dev/null +++ b/src/main/java/com/daon/onjung/core/listener/AppEventListener.java @@ -0,0 +1,44 @@ +package com.daon.onjung.core.listener; + +import com.daon.onjung.core.exception.error.ErrorCode; +import com.daon.onjung.core.exception.type.CommonException; +import com.daon.onjung.event.application.controller.consumer.EventSchedulerConsumerV1Controller; +import com.daon.onjung.event.domain.event.EventScheduled; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.quartz.*; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import java.sql.Timestamp; + +@Component +@RequiredArgsConstructor +@Slf4j +public class AppEventListener { + + private final Scheduler scheduler; + + @EventListener + public void handleEventScheduled(EventScheduled eventScheduled) { + JobDetail jobDetail = JobBuilder.newJob(EventSchedulerConsumerV1Controller.class) + .withIdentity("eventJob-" + eventScheduled.eventId(), "eventGroup") + .usingJobData("eventId", eventScheduled.eventId()) + .build(); + log.info("Job 등록 완료. JobKey: {}", jobDetail.getKey()); + + Trigger trigger = TriggerBuilder.newTrigger() + .withIdentity("eventTrigger-" + eventScheduled.eventId(), "eventGroup") + .startAt(Timestamp.valueOf(eventScheduled.scheduledTime())) + .build(); + log.info("Trigger 등록 완료. TriggerKey: {}", trigger.getKey()); + log.info("Trigger 시작 시간: {}", trigger.getStartTime()); + + try { + scheduler.scheduleJob(jobDetail, trigger); + } catch (SchedulerException e) { + throw new CommonException(ErrorCode.SCHEDULER_ERROR); + } + } + +} diff --git a/src/main/java/com/daon/onjung/account/application/service/.keep b/src/main/java/com/daon/onjung/event/application/controller/command/.keep similarity index 100% rename from src/main/java/com/daon/onjung/account/application/service/.keep rename to src/main/java/com/daon/onjung/event/application/controller/command/.keep diff --git a/src/main/java/com/daon/onjung/event/application/controller/consumer/EventSchedulerConsumerV1Controller.java b/src/main/java/com/daon/onjung/event/application/controller/consumer/EventSchedulerConsumerV1Controller.java new file mode 100644 index 0000000..0430c2a --- /dev/null +++ b/src/main/java/com/daon/onjung/event/application/controller/consumer/EventSchedulerConsumerV1Controller.java @@ -0,0 +1,20 @@ +package com.daon.onjung.event.application.controller.consumer; + +import com.daon.onjung.event.application.usecase.ProcessCompletedEventUseCase; +import lombok.RequiredArgsConstructor; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +@RequiredArgsConstructor +public class EventSchedulerConsumerV1Controller implements Job { + + private final ProcessCompletedEventUseCase processCompletedEventUseCase; + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + Long eventId = context.getJobDetail().getJobDataMap().getLong("eventId"); + + processCompletedEventUseCase.execute(eventId); + } +} diff --git a/src/main/java/com/daon/onjung/event/application/controller/.keep b/src/main/java/com/daon/onjung/event/application/controller/query/.keep similarity index 100% rename from src/main/java/com/daon/onjung/event/application/controller/.keep rename to src/main/java/com/daon/onjung/event/application/controller/query/.keep diff --git a/src/main/java/com/daon/onjung/event/application/service/.keep b/src/main/java/com/daon/onjung/event/application/service/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/daon/onjung/event/application/service/ProcessCompletedEventService.java b/src/main/java/com/daon/onjung/event/application/service/ProcessCompletedEventService.java new file mode 100644 index 0000000..0e76926 --- /dev/null +++ b/src/main/java/com/daon/onjung/event/application/service/ProcessCompletedEventService.java @@ -0,0 +1,165 @@ +package com.daon.onjung.event.application.service; + +import com.daon.onjung.account.domain.Store; +import com.daon.onjung.account.domain.type.EBankName; +import com.daon.onjung.account.repository.mysql.UserRepository; +import com.daon.onjung.core.dto.CreateVirtualAccountResponseDto; +import com.daon.onjung.core.exception.error.ErrorCode; +import com.daon.onjung.core.exception.type.CommonException; +import com.daon.onjung.core.utility.BankUtil; +import com.daon.onjung.core.utility.RestClientUtil; +import com.daon.onjung.event.application.usecase.ProcessCompletedEventUseCase; +import com.daon.onjung.event.domain.Event; +import com.daon.onjung.event.domain.Ticket; +import com.daon.onjung.event.domain.event.EventScheduled; +import com.daon.onjung.event.domain.service.EventService; +import com.daon.onjung.event.domain.service.TicketService; +import com.daon.onjung.event.domain.type.EStatus; +import com.daon.onjung.event.repository.mysql.EventRepository; +import com.daon.onjung.event.repository.mysql.TicketRepository; +import com.daon.onjung.onjung.repository.mysql.DonationRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ProcessCompletedEventService implements ProcessCompletedEventUseCase { + + private final EventRepository eventRepository; + private final UserRepository userRepository; + private final TicketRepository ticketRepository; + private final DonationRepository donationRepository; + + private final EventService eventService; + private final TicketService ticketService; + + private final RestClientUtil restClientUtil; + private final BankUtil bankUtil; + + private final ApplicationEventPublisher applicationEventPublisher; + + @Override + @Transactional + public void execute(Long eventId) { + + // 이벤트 조회 + Event currentEvent = eventRepository.findById(eventId) + .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_RESOURCE)); + + // 가게 조회 + Store store = currentEvent.getStore(); + + // 현재 진행중인 이벤트의 상태를 모금완료로 변경 + currentEvent = eventService.completeEvent(currentEvent); + eventRepository.save(currentEvent); + + // 새로운 이벤트 생성 + Event newEvent = eventService.createEvent( + LocalDate.now(), + LocalDate.now().plusDays(13), + store + ); + newEvent = eventRepository.save(newEvent); + + // 가상 계좌 생성 + String url = bankUtil.createCreateVirtualAccountRequestUrl(); + HttpHeaders headers = bankUtil.createVirtualAccountRequestHeaders(); + String body = bankUtil.createCreateVirtualAccountRequestBody(newEvent.getId(), EBankName.KAKAO.toString()); + + CreateVirtualAccountResponseDto createVirtualAccountResponseDto = + bankUtil.mapToCreateVirtualAccountResponseDto(restClientUtil.sendPostMethod(url, headers, body)); + + // 이벤트에 은행 정보 업데이트 + newEvent.updateBankInfo( + EBankName.fromString(createVirtualAccountResponseDto.data().bankName()), + createVirtualAccountResponseDto.data().bankId() + ); + eventRepository.save(newEvent); + + // 새롭게 생성된 이벤트에 대한 종료일자에 맞춘 이벤트 발행. 발행한 이벤트는 이벤트 리스너에 의해 스케줄러에 등록됨 + applicationEventPublisher.publishEvent( + EventScheduled.builder() + .eventId(newEvent.getId()) + .scheduledTime(newEvent.getEndDate().plusDays(1).atStartOfDay()) +// .scheduledTime(LocalDateTime.now().plusMinutes(1)) // 테스트용 1분 뒤 + .build() + ); + + // 종료된 이벤트와 연결된 가상계좌에 모급된 금액을 조회 + headers = bankUtil.createVirtualAccountRequestHeaders(); + url = bankUtil.createReadVirtualAccountRequestUrl(currentEvent.getBankId()); + Integer totalBalance = bankUtil.mapToReadVirtualAccountResponseDto(restClientUtil.sendGetMethod(url, headers)).data().balance(); + + // 종료된 이벤트와 연결된 가상계좌에 모금된 금액을 고용주 계좌에 이체 + headers = bankUtil.createVirtualAccountRequestHeaders(); + url = bankUtil.createTransferVirtualAccountRequestUrl(currentEvent.getBankId()); + String requestBody = bankUtil.createTransferVirtualAccountRequestBody(totalBalance, store.getOwner().getBankAccountNumber()); + bankUtil.mapToDepositOrTransferVirtualAccountResponseDto(restClientUtil.sendPostMethod(url, headers, requestBody)); + + // 발행 가능한 식권 + int ticketNumber = totalBalance / 10000; + + // 발행 가능한 식권만큼 랜덤한 유저 선택 + List userIds = userRepository.findAllUserIds(); + Random random = new Random(); + + // 이미 티켓을 발급받은 유저를 저장 + Set issuedUserIds = new HashSet<>(); + int issuedTickets = 0; // 발급된 티켓 개수 + + // 발급 가능한 티켓 수만큼 루프 + while (issuedTickets < ticketNumber) { + if (issuedUserIds.size() == userIds.size()) { + // 모든 유저가 티켓을 발급받은 경우 루프 종료 + break; + } + + // 랜덤하게 유저 선택 + UUID userId = userIds.get(random.nextInt(userIds.size())); + int userSize = userIds.size(); + int cursor = 0; + // 해당 이벤트에 대해 동참하기를 한 유저인지 확인. 동참하기를 한 유저라면 티켓 발급 안하고 다음 유저를 뽑음 + while(cursor < userSize) { + if (donationRepository.findByUserIdAndEventId(userId, eventId).isEmpty()) + break; + userId = userIds.get(random.nextInt(userIds.size())); + cursor++; + } + + if (cursor == userSize) { + // 더 이상 동참하기 안한 유저를 찾을 수 없는 경우 티켓 발급 종료 + log.info("-----------------더 이상 동참하기 안한 유저를 찾을 수 없음. 티켓 발급 종료--------------------"); + break; + } + + // 이미 티켓을 발급받은 유저인지 확인 + if (!issuedUserIds.contains(userId)) { + // 티켓 발급 + Ticket ticket = ticketService.createTicket( + LocalDate.now().plusDays(30), + 10000, + true, + store, + userRepository.findById(userId).orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_RESOURCE)), + currentEvent + ); + ticketRepository.save(ticket); + + // 발급된 유저 기록 및 발급된 티켓 수 증가 + issuedUserIds.add(userId); + issuedTickets++; + log.info("유저 {}에게 티켓 발급 완료", userId); + + } + } + } +} diff --git a/src/main/java/com/daon/onjung/event/application/usecase/.keep b/src/main/java/com/daon/onjung/event/application/usecase/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/daon/onjung/event/application/usecase/ProcessCompletedEventUseCase.java b/src/main/java/com/daon/onjung/event/application/usecase/ProcessCompletedEventUseCase.java new file mode 100644 index 0000000..fef0005 --- /dev/null +++ b/src/main/java/com/daon/onjung/event/application/usecase/ProcessCompletedEventUseCase.java @@ -0,0 +1,8 @@ +package com.daon.onjung.event.application.usecase; + +import com.daon.onjung.core.annotation.bean.UseCase; + +@UseCase +public interface ProcessCompletedEventUseCase { + void execute(Long eventId); +} diff --git a/src/main/java/com/daon/onjung/event/domain/Event.java b/src/main/java/com/daon/onjung/event/domain/Event.java index 6353a5f..b0ace26 100644 --- a/src/main/java/com/daon/onjung/event/domain/Event.java +++ b/src/main/java/com/daon/onjung/event/domain/Event.java @@ -63,14 +63,10 @@ public class Event { /* Methods ------------------------------------ */ /* -------------------------------------------- */ @Builder - public Event(EStatus status, LocalDate startDate, LocalDate endDate, LocalDate storeDeliveryDate, LocalDate ticketIssueDate, LocalDate reportDate, EBankName bankName, Store store) { - this.status = status; + public Event(LocalDate startDate, LocalDate endDate, Store store) { + this.status = EStatus.IN_PROGRESS; this.startDate = startDate; this.endDate = endDate; - this.storeDeliveryDate = storeDeliveryDate; - this.ticketIssueDate = ticketIssueDate; - this.reportDate = reportDate; - this.bankName = bankName; this.store = store; } @@ -78,4 +74,10 @@ public void updateBankInfo(EBankName bankName, Long bankId) { this.bankName = bankName; this.bankId = bankId; } + + public void completeEvent() { + this.status = EStatus.TICKET_ISSUE; + this.storeDeliveryDate = LocalDate.now(); + this.ticketIssueDate = LocalDate.now(); + } } diff --git a/src/main/java/com/daon/onjung/event/domain/Ticket.java b/src/main/java/com/daon/onjung/event/domain/Ticket.java index 375bc10..aeab15a 100644 --- a/src/main/java/com/daon/onjung/event/domain/Ticket.java +++ b/src/main/java/com/daon/onjung/event/domain/Ticket.java @@ -60,7 +60,7 @@ public class Ticket { /* Methods ------------------------------------ */ /* -------------------------------------------- */ @Builder - public Ticket(LocalDate expirationDate, int ticketPrice, boolean isValidate, Store store, User user, Event event) { + public Ticket(LocalDate expirationDate, Integer ticketPrice, Boolean isValidate, Store store, User user, Event event) { this.expirationDate = expirationDate; this.ticketPrice = ticketPrice; this.isValidate = isValidate; diff --git a/src/main/java/com/daon/onjung/event/domain/event/EventScheduled.java b/src/main/java/com/daon/onjung/event/domain/event/EventScheduled.java new file mode 100644 index 0000000..0145ead --- /dev/null +++ b/src/main/java/com/daon/onjung/event/domain/event/EventScheduled.java @@ -0,0 +1,16 @@ +package com.daon.onjung.event.domain.event; + +import lombok.Builder; + +import java.time.LocalDateTime; + +public record EventScheduled( + Long eventId, + LocalDateTime scheduledTime +) { + @Builder + public EventScheduled(Long eventId, LocalDateTime scheduledTime) { + this.eventId = eventId; + this.scheduledTime = scheduledTime; + } +} diff --git a/src/main/java/com/daon/onjung/event/domain/service/EventService.java b/src/main/java/com/daon/onjung/event/domain/service/EventService.java index 6b6b1dc..d551290 100644 --- a/src/main/java/com/daon/onjung/event/domain/service/EventService.java +++ b/src/main/java/com/daon/onjung/event/domain/service/EventService.java @@ -1,7 +1,9 @@ package com.daon.onjung.event.domain.service; +import com.daon.onjung.account.domain.Store; import com.daon.onjung.account.domain.type.EBankName; import com.daon.onjung.event.domain.Event; +import com.daon.onjung.event.domain.type.EStatus; import org.springframework.stereotype.Service; import java.time.LocalDate; @@ -17,4 +19,18 @@ public Event updateBankInfo(Event event, EBankName bankName, Long bankId) { public Integer getRestOfDate(Event event) { return (int) (event.getEndDate().toEpochDay() - LocalDate.now().toEpochDay()); } + + public Event createEvent(LocalDate startDate, LocalDate endDate, Store store) { + + return Event.builder() + .startDate(startDate) + .endDate(endDate) + .store(store) + .build(); + } + + public Event completeEvent(Event event) { + event.completeEvent(); + return event; + } } diff --git a/src/main/java/com/daon/onjung/event/domain/service/TicketService.java b/src/main/java/com/daon/onjung/event/domain/service/TicketService.java new file mode 100644 index 0000000..76046a0 --- /dev/null +++ b/src/main/java/com/daon/onjung/event/domain/service/TicketService.java @@ -0,0 +1,31 @@ +package com.daon.onjung.event.domain.service; + +import com.daon.onjung.account.domain.Store; +import com.daon.onjung.account.domain.User; +import com.daon.onjung.event.domain.Event; +import com.daon.onjung.event.domain.Ticket; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; + +@Service +public class TicketService { + + public Ticket createTicket( + LocalDate expirationDate, + Integer ticketPrice, + Boolean isValidate, + Store store, + User user, + Event event + ) { + return Ticket.builder() + .expirationDate(expirationDate) + .ticketPrice(ticketPrice) + .isValidate(isValidate) + .store(store) + .user(user) + .event(event) + .build(); + } +} diff --git a/src/main/java/com/daon/onjung/event/repository/mysql/TicketRepository.java b/src/main/java/com/daon/onjung/event/repository/mysql/TicketRepository.java index 106a03b..fc8a27d 100644 --- a/src/main/java/com/daon/onjung/event/repository/mysql/TicketRepository.java +++ b/src/main/java/com/daon/onjung/event/repository/mysql/TicketRepository.java @@ -8,8 +8,7 @@ public interface TicketRepository extends JpaRepository { - // user로 Ticket 조회, expidationDate를 ASC로 정렬 + // user로 Ticket 조회, expirationDate를 ASC로 정렬 List findByUserOrderByExpirationDateAsc(User user); - } diff --git a/src/main/java/com/daon/onjung/onjung/application/dto/request/CreateDonationRequestDto.java b/src/main/java/com/daon/onjung/onjung/application/dto/request/CreateDonationRequestDto.java index aaa6c53..a03abf1 100644 --- a/src/main/java/com/daon/onjung/onjung/application/dto/request/CreateDonationRequestDto.java +++ b/src/main/java/com/daon/onjung/onjung/application/dto/request/CreateDonationRequestDto.java @@ -4,6 +4,9 @@ public record CreateDonationRequestDto( + @JsonProperty("event_id") + Long eventId, + @JsonProperty("donation_amount") Integer donationAmount ) { diff --git a/src/main/java/com/daon/onjung/onjung/application/service/CreateDonationService.java b/src/main/java/com/daon/onjung/onjung/application/service/CreateDonationService.java index 045a158..3eaff64 100644 --- a/src/main/java/com/daon/onjung/onjung/application/service/CreateDonationService.java +++ b/src/main/java/com/daon/onjung/onjung/application/service/CreateDonationService.java @@ -53,29 +53,17 @@ public CreateDonationResponseDto execute(UUID accountId, Long storeId, CreateDon .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_RESOURCE)); // 이벤트 조회 - Event event = eventRepository.findINPROGRESSEventByStoreId(storeId) + Event event = eventRepository.findById(requestDto.eventId()) .orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_RESOURCE)); // 동참 생성 - Donation donation = donationService.createDonation(user, store, requestDto.donationAmount()); + Donation donation = donationService.createDonation(user, store, event, requestDto.donationAmount()); donationRepository.save(donation); // 해당 이벤트에 생성된 가상 계좌가 있는지 확인 + // TODO: Domain Service 로직으로 이동 if (event.getBankId() == null) { - // 은행에 가상계좌 생성 요청 - String url = bankUtil.createCreateVirtualAccountRequestUrl(); - HttpHeaders headers = bankUtil.createVirtualAccountRequestHeaders(); - String body = bankUtil.createCreateVirtualAccountRequestBody(event.getId(), EBankName.KAKAO.toString()); - - CreateVirtualAccountResponseDto createVirtualAccountResponseDto = - bankUtil.mapToCreateVirtualAccountResponseDto(restClientUtil.sendPostMethod(url, headers, body)); - - // 이벤트에 은행 정보 업데이트 - event.updateBankInfo( - EBankName.fromString(createVirtualAccountResponseDto.data().bankName()), - createVirtualAccountResponseDto.data().bankId() - ); - eventRepository.save(event); + throw new CommonException(ErrorCode.NOT_FOUND_RESOURCE); } // 은행에 동참금액 입금 @@ -83,8 +71,7 @@ public CreateDonationResponseDto execute(UUID accountId, Long storeId, CreateDon HttpHeaders headers = bankUtil.createVirtualAccountRequestHeaders(); String body = bankUtil.createDepositVirtualAccountRequestBody(requestDto.donationAmount(), user.getNickName()); - DepositOrTransferVirtualAccountResponseDto depositOrTransferVirtualAccountResponseDto = - bankUtil.mapToDepositOrTransferVirtualAccountResponseDto(restClientUtil.sendPostMethod(url, headers, body)); + restClientUtil.sendPostMethod(url, headers, body); return CreateDonationResponseDto.of(donation, store); } diff --git a/src/main/java/com/daon/onjung/onjung/domain/Donation.java b/src/main/java/com/daon/onjung/onjung/domain/Donation.java index 459591f..acc3f43 100644 --- a/src/main/java/com/daon/onjung/onjung/domain/Donation.java +++ b/src/main/java/com/daon/onjung/onjung/domain/Donation.java @@ -2,6 +2,7 @@ import com.daon.onjung.account.domain.Store; import com.daon.onjung.account.domain.User; +import com.daon.onjung.event.domain.Event; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; @@ -45,14 +46,19 @@ public class Donation { @JoinColumn(name = "stores_id", nullable = false) private Store store; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "events_id", nullable = false) + private Event event; + /* -------------------------------------------- */ /* Methods ------------------------------------ */ /* -------------------------------------------- */ @Builder - public Donation(Integer donationAmount, User user, Store store) { + public Donation(Integer donationAmount, User user, Store store, Event event) { this.donationAmount = donationAmount; this.user = user; this.store = store; + this.event = event; this.createdAt = LocalDateTime.now(); } } diff --git a/src/main/java/com/daon/onjung/onjung/domain/service/DonationService.java b/src/main/java/com/daon/onjung/onjung/domain/service/DonationService.java index e59b45d..94b2ac6 100644 --- a/src/main/java/com/daon/onjung/onjung/domain/service/DonationService.java +++ b/src/main/java/com/daon/onjung/onjung/domain/service/DonationService.java @@ -2,16 +2,18 @@ import com.daon.onjung.account.domain.Store; import com.daon.onjung.account.domain.User; +import com.daon.onjung.event.domain.Event; import com.daon.onjung.onjung.domain.Donation; import org.springframework.stereotype.Service; @Service public class DonationService { - public Donation createDonation(User user, Store store, Integer donationAmount) { + public Donation createDonation(User user, Store store, Event event, Integer donationAmount) { return Donation.builder() .user(user) .store(store) + .event(event) .donationAmount(donationAmount) .build(); } diff --git a/src/main/java/com/daon/onjung/onjung/repository/mysql/DonationRepository.java b/src/main/java/com/daon/onjung/onjung/repository/mysql/DonationRepository.java index cebfd6b..8548579 100644 --- a/src/main/java/com/daon/onjung/onjung/repository/mysql/DonationRepository.java +++ b/src/main/java/com/daon/onjung/onjung/repository/mysql/DonationRepository.java @@ -5,8 +5,12 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; +import java.util.Optional; +import java.util.UUID; public interface DonationRepository extends JpaRepository { List findAllByUser(User user); + + List findByUserIdAndEventId(UUID userId, Long eventId); } diff --git a/src/main/java/com/daon/onjung/security/application/service/SignUpOwnerByDefaultService.java b/src/main/java/com/daon/onjung/security/application/service/SignUpOwnerByDefaultService.java index 36140d9..589072a 100644 --- a/src/main/java/com/daon/onjung/security/application/service/SignUpOwnerByDefaultService.java +++ b/src/main/java/com/daon/onjung/security/application/service/SignUpOwnerByDefaultService.java @@ -4,32 +4,52 @@ import com.daon.onjung.account.domain.Store; import com.daon.onjung.account.domain.service.OwnerService; import com.daon.onjung.account.domain.service.StoreService; +import com.daon.onjung.account.domain.type.EBankName; import com.daon.onjung.account.repository.mysql.OwnerRepository; import com.daon.onjung.account.repository.mysql.StoreRepository; +import com.daon.onjung.core.dto.CreateVirtualAccountResponseDto; +import com.daon.onjung.core.utility.BankUtil; +import com.daon.onjung.core.utility.RestClientUtil; import com.daon.onjung.core.utility.S3Util; +import com.daon.onjung.event.domain.Event; +import com.daon.onjung.event.domain.event.EventScheduled; +import com.daon.onjung.event.domain.service.EventService; +import com.daon.onjung.event.repository.mysql.EventRepository; import com.daon.onjung.security.application.dto.request.SignUpOwnerByDefaultRequestDto; import com.daon.onjung.security.application.usecase.SignUpOwnerByDefaultUseCase; import com.daon.onjung.security.domain.type.EImageType; import com.daon.onjung.security.domain.type.ESecurityProvider; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.HttpHeaders; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; + @Service @RequiredArgsConstructor public class SignUpOwnerByDefaultService implements SignUpOwnerByDefaultUseCase { private final OwnerRepository ownerRepository; private final StoreRepository storeRepository; + private final EventRepository eventRepository; private final OwnerService ownerService; private final StoreService storeService; + private final EventService eventService; private final S3Util s3Util; + private final BankUtil bankUtil; + private final RestClientUtil restClientUtil; private final BCryptPasswordEncoder bCryptPasswordEncoder; + private final ApplicationEventPublisher applicationEventPublisher; + @Override @Transactional public void execute(MultipartFile logo, @@ -71,5 +91,37 @@ public void execute(MultipartFile logo, ); storeRepository.save(store); + // 생성한 가게에 대한 이벤트 생성 + Event event = eventService.createEvent( + LocalDate.now(), + LocalDate.now().plusDays(13), + store + ); + event = eventRepository.save(event); + + // 가상계좌 생성 + String url = bankUtil.createCreateVirtualAccountRequestUrl(); + HttpHeaders headers = bankUtil.createVirtualAccountRequestHeaders(); + String body = bankUtil.createCreateVirtualAccountRequestBody(event.getId(), EBankName.KAKAO.toString()); + + CreateVirtualAccountResponseDto createVirtualAccountResponseDto = + bankUtil.mapToCreateVirtualAccountResponseDto(restClientUtil.sendPostMethod(url, headers, body)); + + // 이벤트에 은행 정보 업데이트 + event.updateBankInfo( + EBankName.fromString(createVirtualAccountResponseDto.data().bankName()), + createVirtualAccountResponseDto.data().bankId() + ); + eventRepository.save(event); + + // 생성한 이벤트에 대해 이벤트 발행 + applicationEventPublisher.publishEvent( + EventScheduled.builder() + .eventId(event.getId()) + .scheduledTime(event.getEndDate().plusDays(1).atStartOfDay()) +// .scheduledTime(LocalDateTime.now().plusMinutes(1)) // 테스트용 1분 뒤 + .build() + ); + } }