Skip to content
Merged

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.codeNbug.mainserver.domain.admin.dto.response;

import java.time.LocalDateTime;

import org.codeNbug.mainserver.domain.event.entity.Event;
import org.codeNbug.mainserver.domain.event.entity.EventCategoryEnum;
import org.codeNbug.mainserver.domain.event.entity.EventInformation;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class EventAdminDto {
private Long eventId;
private String title;
private String category;
private LocalDateTime eventStart;
private LocalDateTime eventEnd;
private Integer seatCount; // 총 티켓 수
private Integer soldTickets; // 판매된 티켓 수
private String status;

public static EventAdminDto fromEntity(Event event, int soldTickets) {
EventInformation information = event.getInformation();

return EventAdminDto.builder()
.eventId(event.getEventId())
.title(information.getTitle())
.category(event.getCategory().name())
.eventStart(information.getEventStart())
.eventEnd(information.getEventEnd())
.seatCount(information.getSeatCount())
.soldTickets(soldTickets)
.status(event.getStatus().name())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.codeNbug.mainserver.domain.admin.dto.response;

import java.time.LocalDateTime;

import org.codeNbug.mainserver.domain.purchase.entity.PaymentStatusEnum;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TicketAdminDto {
private Long ticketId;
private Long eventId;
private String eventTitle;
private String userName;
private String userEmail;
private String phoneNumber;
private String seatInfo;
private PaymentStatusEnum paymentStatus;
private Integer amount;
private LocalDateTime purchaseDate;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@
import org.codeNbug.mainserver.domain.admin.dto.response.AdminLoginResponse;
import org.codeNbug.mainserver.domain.admin.dto.response.AdminSignupResponse;
import org.codeNbug.mainserver.domain.admin.dto.response.DashboardStatsResponse;
import org.codeNbug.mainserver.domain.admin.dto.response.EventAdminDto;
import org.codeNbug.mainserver.domain.admin.dto.response.ModifyRoleResponse;
import org.codeNbug.mainserver.domain.admin.dto.response.TicketAdminDto;
import org.codeNbug.mainserver.domain.event.entity.Event;
import org.codeNbug.mainserver.domain.event.entity.EventStatusEnum;
import org.codeNbug.mainserver.domain.manager.repository.EventRepository;
import org.codeNbug.mainserver.domain.notification.entity.NotificationEnum;
import org.codeNbug.mainserver.domain.notification.service.NotificationService;
import org.codeNbug.mainserver.domain.purchase.entity.Purchase;
import org.codeNbug.mainserver.domain.purchase.repository.PurchaseRepository;
import org.codeNbug.mainserver.domain.ticket.repository.TicketRepository;
import org.codeNbug.mainserver.global.exception.globalException.BadRequestException;
import org.codeNbug.mainserver.global.exception.globalException.DuplicateEmailException;
import org.codenbug.user.domain.user.entity.User;
import org.codenbug.user.domain.user.repository.UserRepository;
Expand All @@ -23,9 +31,11 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
* 관리자 관련 서비스
Expand All @@ -41,6 +51,8 @@ public class AdminService {
private final TicketRepository ticketRepository;
private final PasswordEncoder passwordEncoder;
private final TokenService tokenService;
private final NotificationService notificationService;
private final PurchaseRepository purchaseRepository;

/**
* 관리자 회원가입 서비스
Expand Down Expand Up @@ -314,4 +326,261 @@ else if ("sns".equals(userType)) {
log.error(">> 유효하지 않은 사용자 타입: {}", userType);
throw new IllegalArgumentException("유효하지 않은 사용자 타입입니다: " + userType);
}

/**
* 모든 이벤트 목록을 조회하고 각 이벤트의 티켓 정보를 포함하여 반환합니다.
*
* @return 이벤트 관리자 DTO 목록
*/
@Transactional(readOnly = true)
public List<EventAdminDto> getAllEvents() {
log.info(">> 모든 이벤트 목록 조회");

try {
// 삭제되지 않은 이벤트만 조회
List<Event> events = eventRepository.findAllByIsDeletedFalse();
log.debug(">> 이벤트 목록 조회 완료: {} 개", events.size());

// 각 이벤트에 대한 정보와 티켓 정보를 포함한 DTO 생성
List<EventAdminDto> eventDtos = events.stream()
.map(event -> {
// 해당 이벤트의 판매된 티켓 수 조회
int soldTickets = ticketRepository.countPaidTicketsByEventId(event.getEventId());
log.debug(">> 이벤트 ID={}, 판매된 티켓 수={}", event.getEventId(), soldTickets);

// DTO 생성
return EventAdminDto.fromEntity(event, soldTickets);
})
.collect(Collectors.toList());

log.info(">> 이벤트 목록 조회 완료: {} 개의 이벤트", eventDtos.size());

return eventDtos;
} catch (Exception e) {
log.error(">> 이벤트 목록 조회 중 오류: {}", e.getMessage(), e);
throw new RuntimeException("이벤트 목록 조회 중 오류가 발생했습니다: " + e.getMessage());
}
}

/**
* 모든 티켓 목록을 조회합니다.
*
* @return 티켓 관리자 DTO 목록
*/
@Transactional(readOnly = true)
public List<TicketAdminDto> getAllTickets() {
log.info(">> 모든 티켓 목록 조회");

try {
// 모든 티켓 조회
List<TicketAdminDto> tickets = ticketRepository.findAllTicketsForAdmin();
log.debug(">> 티켓 목록 조회 완료: {} 개", tickets.size());

log.info(">> 티켓 목록 조회 완료: {} 개의 티켓", tickets.size());

return tickets;
} catch (Exception e) {
log.error(">> 티켓 목록 조회 중 오류: {}", e.getMessage(), e);
throw new RuntimeException("티켓 목록 조회 중 오류가 발생했습니다: " + e.getMessage());
}
}

/**
* 특정 이벤트 정보를 조회합니다.
*
* @param eventId 조회할 이벤트 ID
* @return 이벤트 관리자 DTO
* @throws RuntimeException 이벤트를 찾을 수 없거나 오류 발생 시
*/
@Transactional(readOnly = true)
public EventAdminDto getEvent(Long eventId) {
log.info(">> 이벤트 상세 정보 조회: id={}", eventId);

try {
// 이벤트 조회
Event event = eventRepository.findById(eventId)
.orElseThrow(() -> new RuntimeException("해당 이벤트를 찾을 수 없습니다: " + eventId));

// 해당 이벤트의 판매된 티켓 수 조회
int soldTickets = ticketRepository.countPaidTicketsByEventId(eventId);
log.debug(">> 이벤트 ID={}, 판매된 티켓 수={}", event.getEventId(), soldTickets);

// DTO 생성
EventAdminDto eventDto = EventAdminDto.fromEntity(event, soldTickets);

log.info(">> 이벤트 상세 정보 조회 완료: id={}, 제목={}", eventId, eventDto.getTitle());

return eventDto;
} catch (Exception e) {
log.error(">> 이벤트 상세 정보 조회 중 오류: {}", e.getMessage(), e);
throw new RuntimeException("이벤트 상세 정보 조회 중 오류가 발생했습니다: " + e.getMessage());
}
}

/**
* 삭제 대기 중인 이벤트 목록을 조회합니다.
*
* @return 삭제 대기 중인 이벤트 목록
*/
@Transactional(readOnly = true)
public List<EventAdminDto> getDeletedEvents() {
log.info(">> 삭제 대기 중인 이벤트 목록 조회");

try {
// 삭제된 이벤트 조회
List<Event> events = eventRepository.findAllByIsDeletedTrue();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

삭제 대기 중인 이벤트 목록 조회 -> isDeleted 값이 true 인 이벤트의 목록을 조회하는게 맞을까요?
삭제 대기 중 이라는 말이 조금 어색하게 보여지는 듯 합니다...!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 관리자 대시보의 이벤트 관리 페이지를 보면 이벤트 목록 조회 탭과 삭제된 이벤트 탭으로 나뉘어 있습니다. isDelete값이 true인 이벤트의 경우 삭제된 이벤트 탭으로 이동하게 되고, 여기서 복구 버튼을 누르면 다시 이벤트 목록 조회 탭으로 되돌아오게 됩니다. 해서, 위와 같이 구현하였습니다.
때문에 isDeleted가 true 값의 경우 삭제된 이벤트에서 조회되기 때문에 문제가 없을 것으로 보이는데 다른 의견 있으실까요?
image
image

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

삭제 되어 대기중이라는 의미라는 말씀이신거죠??
삭제 대기 중이라는 의미를 저는 "삭제 요청 -> 삭제 대기 -> 삭제 승인 -> 최종 삭제" 라는 플로우로 느껴서 어색했던 것 같습니다!
복구 가능하다 라는 의미에서의 "삭제 대기 중" 이라면 합당해 보입니다!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞습니다. 추가로, 삭제된 이벤트 탭에서도 완전히 지울 수 있도록하는 기능을 추가해야 겠네요.
재확인차 질문합니다만 DB에서는 관련 데이터는 어떤 경우에서도 지워지지 않도록 하는게 맞는건지 확인부탁드립니다.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞습니다. DB 에서는 한번 저장된 이벤트는 실제 데이터 삭제가 이루어 지지 않는 것이 맞습니다.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다 .
감사합니다.

log.debug(">> 삭제된 이벤트 목록 조회 완료: {} 개", events.size());

// 각 이벤트에 대한 정보와 티켓 정보를 포함한 DTO 생성
List<EventAdminDto> eventDtos = events.stream()
.map(event -> {
// 해당 이벤트의 판매된 티켓 수 조회
int soldTickets = ticketRepository.countPaidTicketsByEventId(event.getEventId());
log.debug(">> 이벤트 ID={}, 판매된 티켓 수={}", event.getEventId(), soldTickets);

// DTO 생성
return EventAdminDto.fromEntity(event, soldTickets);
})
.collect(Collectors.toList());

log.info(">> 삭제된 이벤트 목록 조회 완료: {} 개의 이벤트", eventDtos.size());

return eventDtos;
} catch (Exception e) {
log.error(">> 삭제된 이벤트 목록 조회 중 오류: {}", e.getMessage(), e);
throw new RuntimeException("삭제된 이벤트 목록 조회 중 오류가 발생했습니다: " + e.getMessage());
}
}

/**
* 삭제된 이벤트를 복구합니다.
*
* @param eventId 복구할 이벤트 ID
* @param status 복구 후 설정할 이벤트 상태
* @return 복구된 이벤트 정보
* @throws IllegalAccessException 이벤트가 삭제되지 않은 경우
*/
@Transactional
public EventAdminDto restoreEvent(Long eventId, EventStatusEnum status) throws IllegalAccessException {
log.info(">> 이벤트 복구 처리 시작: eventId={}, status={}", eventId, status);

Event event = eventRepository.findById(eventId)
.orElseThrow(() -> {
log.error(">> 이벤트 복구 실패: 존재하지 않는 이벤트 - eventId={}", eventId);
return new BadRequestException("존재하지 않는 이벤트입니다.");
});

// 이벤트가 삭제되지 않았다면 400에러 전송
if (!event.getIsDeleted()) {
log.warn(">> 이벤트 복구 실패: 삭제되지 않은 이벤트 - eventId={}", eventId);
throw new IllegalAccessException("삭제되지 않은 이벤트입니다.");
}

// 이벤트 상태 변경
event.setIsDeleted(false);
event.setStatus(status);
log.info(">> 이벤트 상태 변경 완료: eventId={}, status={}", eventId, status);

// 알림 처리는 메인 로직과 분리하여 예외 처리
try {
// 해당 이벤트 구매자들 조회
List<Purchase> purchases = purchaseRepository.findAllByEventId(eventId);
log.debug(">> 이벤트 구매자 조회 완료: eventId={}, 구매자 수={}", eventId, purchases.size());

// 모든 구매자에게 행사 복구 알림 전송
String notificationContent = String.format(
"[%s] 행사가 복구되었습니다. 예매 내역을 확인해주세요.",
event.getInformation().getTitle()
);

for (Purchase purchase : purchases) {
try {
Long userId = purchase.getUser().getUserId();
notificationService.createNotification(
userId,
NotificationEnum.EVENT,
notificationContent
);
log.debug(">> 알림 전송 완료: userId={}, eventId={}", userId, eventId);
} catch (Exception e) {
log.error(">> 행사 복구 알림 전송 실패. 사용자ID: {}, 구매ID: {}, 오류: {}",
purchase.getUser().getUserId(), purchase.getId(), e.getMessage(), e);
// 개별 사용자 알림 실패는 다른 사용자 알림이나 이벤트 복구에 영향을 주지 않음
}
}
} catch (Exception e) {
log.error(">> 행사 복구 알림 처리 실패. 이벤트ID: {}, 오류: {}", eventId, e.getMessage(), e);
// 알림 전체 실패는 이벤트 복구에 영향을 주지 않도록 예외를 무시함
}

// 복구된 이벤트 정보 반환
int soldTickets = ticketRepository.countPaidTicketsByEventId(eventId);
EventAdminDto eventDto = EventAdminDto.fromEntity(event, soldTickets);

log.info(">> 이벤트 복구 처리 완료: eventId={}", eventId);
return eventDto;
}

/**
* 이벤트를 삭제 처리하는 메서드입니다.
* 관리자 권한으로 이벤트를 삭제하고, 관련된 구매자들에게 알림을 전송합니다.
*
* @param eventId 삭제할 이벤트 ID
* @throws IllegalAccessException 이미 삭제된 이벤트인 경우
*/
@Transactional
public void deleteEvent(Long eventId) throws IllegalAccessException {
log.info(">> 이벤트 삭제 처리 시작: eventId={}", eventId);

Event event = eventRepository.findById(eventId)
.orElseThrow(() -> {
log.error(">> 이벤트 삭제 실패: 존재하지 않는 이벤트 - eventId={}", eventId);
return new BadRequestException("존재하지 않는 이벤트입니다.");
});

// 이벤트가 이미 삭제되었다면 400에러 전송
if (event.getIsDeleted()) {
log.warn(">> 이벤트 삭제 실패: 이미 삭제된 이벤트 - eventId={}", eventId);
throw new IllegalAccessException("이미 삭제된 이벤트입니다.");
}

// 이벤트 상태 변경
event.setIsDeleted(true);
event.setStatus(EventStatusEnum.CANCELLED);
log.info(">> 이벤트 상태 변경 완료: eventId={}, status=CANCELLED", eventId);

// 알림 처리는 메인 로직과 분리하여 예외 처리
try {
// 해당 이벤트 구매자들 조회
List<Purchase> purchases = purchaseRepository.findAllByEventId(eventId);
log.debug(">> 이벤트 구매자 조회 완료: eventId={}, 구매자 수={}", eventId, purchases.size());

// 모든 구매자에게 행사 취소 알림 전송
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

행사 취소 이전, 구매한 티켓이 존재한다면 해당 티켓들에 대한 취소가 우선적으로 이루어져야 할 듯 합니다.
취소와 티켓 환불을 함께 고려한 로직은 구현되지 않아, 해당 로직은 리펙토링 진행 시 반영하도록 하겠습니다.

Copy link
Copy Markdown
Collaborator Author

@joungGo joungGo May 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다. 해당 로직 구현 후 알림 주시면 관리자 대시보드에도 반영하겠습니다.
추가 첨언 없으시면 반응 달아주세요.

String notificationContent = String.format(
"[%s] 행사가 취소되었습니다. 예매 내역을 확인해주세요.",
event.getInformation().getTitle()
);

for (Purchase purchase : purchases) {
try {
Long userId = purchase.getUser().getUserId();
notificationService.createNotification(
userId,
NotificationEnum.EVENT,
notificationContent
);
log.debug(">> 알림 전송 완료: userId={}, eventId={}", userId, eventId);
} catch (Exception e) {
log.error(">> 행사 취소 알림 전송 실패. 사용자ID: {}, 구매ID: {}, 오류: {}",
purchase.getUser().getUserId(), purchase.getId(), e.getMessage(), e);
// 개별 사용자 알림 실패는 다른 사용자 알림이나 이벤트 취소에 영향을 주지 않음
}
}
} catch (Exception e) {
log.error(">> 행사 취소 알림 처리 실패. 이벤트ID: {}, 오류: {}", eventId, e.getMessage(), e);
// 알림 전체 실패는 이벤트 취소에 영향을 주지 않도록 예외를 무시함
}

log.info(">> 이벤트 삭제 처리 완료: eventId={}", eventId);
}
}
Loading