diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java index c87fe1be..730d9ab9 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java @@ -1,9 +1,7 @@ package com.nowait.applicationadmin.exception; import static com.nowait.common.exception.ErrorMessage.*; -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.FORBIDDEN; -import static org.springframework.http.HttpStatus.NOT_FOUND; +import static org.springframework.http.HttpStatus.*; import static org.springframework.http.HttpStatus.UNAUTHORIZED; import java.util.Map; @@ -25,24 +23,55 @@ import com.nowait.common.exception.ErrorMessage; import com.nowait.common.exception.ErrorResponse; import com.nowait.discord.service.DiscordAlarmService; +import com.nowait.domaincorerdb.menu.exception.MenuAlreadyDeletedException; import com.nowait.domaincorerdb.menu.exception.MenuCreationUnauthorizedException; +import com.nowait.domaincorerdb.menu.exception.MenuCrossStoreConflictException; import com.nowait.domaincorerdb.menu.exception.MenuDeleteUnauthorizedException; +import com.nowait.domaincorerdb.menu.exception.MenuDuplicateIdException; +import com.nowait.domaincorerdb.menu.exception.MenuImageEmptyException; +import com.nowait.domaincorerdb.menu.exception.MenuInvalidSortOrderException; +import com.nowait.domaincorerdb.menu.exception.MenuNotFoundException; +import com.nowait.domaincorerdb.menu.exception.MenuParamEmptyException; +import com.nowait.domaincorerdb.menu.exception.MenuToggleUnauthorizedException; import com.nowait.domaincorerdb.menu.exception.MenuUpdateUnauthorizedException; import com.nowait.domaincorerdb.menu.exception.MenuViewUnauthorizedException; +import com.nowait.domaincorerdb.order.exception.DepositorNameTooLongException; import com.nowait.domaincorerdb.order.exception.DuplicateOrderException; +import com.nowait.domaincorerdb.order.exception.OrderAlreadyCancelledException; import com.nowait.domaincorerdb.order.exception.OrderItemsEmptyException; import com.nowait.domaincorerdb.order.exception.OrderNotFoundException; import com.nowait.domaincorerdb.order.exception.OrderParameterEmptyException; import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException; +import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; +import com.nowait.domaincorerdb.reservation.exception.InvalidReservationParameterException; +import com.nowait.domaincorerdb.reservation.exception.InvalidReservationStatusTransitionException; +import com.nowait.domaincorerdb.reservation.exception.ReservationAddUnauthorizedException; +import com.nowait.domaincorerdb.reservation.exception.ReservationAlreadyCancelledException; +import com.nowait.domaincorerdb.reservation.exception.ReservationAlreadyConfirmedException; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; +import com.nowait.domaincorerdb.reservation.exception.ReservationNumberIssueFailException; import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationViewUnauthorizedException; +import com.nowait.domaincorerdb.reservation.exception.UnsupportedReservationStatusException; +import com.nowait.domaincorerdb.reservation.exception.UserWaitingLimitExceededException; import com.nowait.domaincorerdb.store.exception.StoreDeleteUnauthorizedException; +import com.nowait.domaincorerdb.store.exception.StoreImageEmptyException; +import com.nowait.domaincorerdb.store.exception.StoreImageNotFoundException; +import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; +import com.nowait.domaincorerdb.store.exception.StoreParamEmptyException; import com.nowait.domaincorerdb.store.exception.StoreUpdateUnauthorizedException; import com.nowait.domaincorerdb.store.exception.StoreViewUnauthorizedException; +import com.nowait.domaincorerdb.store.exception.StoreWaitingDisabledException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentAlreadyExistsException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentCreationUnauthorizedException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentNotFoundException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentParamEmptyException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentUpdateUnauthorizedException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentViewUnauthorizedException; import com.nowait.domaincorerdb.token.exception.BusinessException; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; +import com.nowait.domaincoreredis.rank.exception.MenuCounterUpdateException; import io.swagger.v3.oas.annotations.Hidden; import lombok.RequiredArgsConstructor; @@ -131,23 +160,20 @@ public ErrorResponse handleMultipartException(MultipartException e, WebRequest r public ErrorResponse userNotFoundException(UserNotFoundException e, WebRequest request) { alarm(e, request); log.error("userNotFoundException", e); - return new ErrorResponse(e.getMessage(), NOTFOUND_USER.getCode()); + return new ErrorResponse(e.getMessage(), NOT_FOUND_USER.getCode()); } - @ResponseStatus(BAD_REQUEST) - @ExceptionHandler(OrderParameterEmptyException.class) - public ErrorResponse orderParameterEmptyException(OrderParameterEmptyException e, WebRequest request) { - alarm(e, request); - log.error("orderParameterEmptyException", e); - return new ErrorResponse(e.getMessage(), ORDER_PARAMETER_EMPTY.getCode()); - } + + /** + * 주문 관련 예외 처리 + */ @ResponseStatus(BAD_REQUEST) - @ExceptionHandler(OrderItemsEmptyException.class) - public ErrorResponse orderItemsEmptyException(OrderItemsEmptyException e, WebRequest request) { + @ExceptionHandler(DepositorNameTooLongException.class) + public ErrorResponse depositorNameTooLongException(DepositorNameTooLongException e, WebRequest request) { alarm(e, request); - log.error("orderItemsEmptyException", e); - return new ErrorResponse(e.getMessage(), ORDER_ITEMS_EMPTY.getCode()); + log.error("depositorNameTooLongException", e); + return new ErrorResponse(e.getMessage(), DEPOSITOR_NAME_TOO_LONG.getCode()); } @ResponseStatus(BAD_REQUEST) @@ -158,12 +184,20 @@ public ErrorResponse duplicateOrderException(DuplicateOrderException e, WebReque return new ErrorResponse(e.getMessage(), ErrorMessage.DUPLICATE_ORDER.getCode()); } - @ResponseStatus(FORBIDDEN) - @ExceptionHandler(OrderViewUnauthorizedException.class) - public ErrorResponse orderViewUnauthorizedException(OrderViewUnauthorizedException e, WebRequest request) { + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(OrderAlreadyCancelledException.class) + public ErrorResponse orderAlreadyCancelledException(OrderAlreadyCancelledException e, WebRequest request) { alarm(e, request); - log.error("orderViewUnauthorizedException", e); - return new ErrorResponse(e.getMessage(), ORDER_VIEW_UNAUTHORIZED.getCode()); + log.error("orderAlreadyCancelledException", e); + return new ErrorResponse(e.getMessage(), ORDER_ALREADY_CANCELLED.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(OrderItemsEmptyException.class) + public ErrorResponse orderItemsEmptyException(OrderItemsEmptyException e, WebRequest request) { + alarm(e, request); + log.error("orderItemsEmptyException", e); + return new ErrorResponse(e.getMessage(), ORDER_ITEMS_EMPTY.getCode()); } @ResponseStatus(NOT_FOUND) @@ -174,6 +208,14 @@ public ErrorResponse orderNotFoundException(OrderNotFoundException e, WebRequest return new ErrorResponse(e.getMessage(), ORDER_NOT_FOUND.getCode()); } + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(OrderParameterEmptyException.class) + public ErrorResponse orderParameterEmptyException(OrderParameterEmptyException e, WebRequest request) { + alarm(e, request); + log.error("orderParameterEmptyException", e); + return new ErrorResponse(e.getMessage(), ORDER_PARAMETER_EMPTY.getCode()); + } + @ResponseStatus(FORBIDDEN) @ExceptionHandler(OrderUpdateUnauthorizedException.class) public ErrorResponse orderUpdateUnauthorizedException(OrderUpdateUnauthorizedException e, WebRequest request) { @@ -182,6 +224,66 @@ public ErrorResponse orderUpdateUnauthorizedException(OrderUpdateUnauthorizedExc return new ErrorResponse(e.getMessage(), ORDER_UPDATE_UNAUTHORIZED.getCode()); } + @ResponseStatus(FORBIDDEN) + @ExceptionHandler(OrderViewUnauthorizedException.class) + public ErrorResponse orderViewUnauthorizedException(OrderViewUnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("orderViewUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), ORDER_VIEW_UNAUTHORIZED.getCode()); + } + + /** + * 예약 관련 예외 처리 + */ + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(DuplicateReservationException.class) + public ErrorResponse duplicateReservationException(DuplicateReservationException e, WebRequest request) { + alarm(e, request); + log.error("duplicateReservationException", e); + return new ErrorResponse(e.getMessage(), DUPLICATE_RESERVATION.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(InvalidReservationParameterException.class) + public ErrorResponse invalidReservationParameterException(InvalidReservationParameterException e, WebRequest request) { + alarm(e, request); + log.error("invalidReservationParameterException", e); + return new ErrorResponse(e.getMessage(), INVALID_RESERVATION_PARAMETER.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(InvalidReservationStatusTransitionException.class) + public ErrorResponse invalidReservationStatusTransitionException(InvalidReservationStatusTransitionException e, WebRequest request) { + alarm(e, request); + log.error("invalidReservationStatusTransitionException", e); + return new ErrorResponse(e.getMessage(), INVALID_RESERVATION_STATUS_TRANSITION.getCode()); + } + + @ResponseStatus(FORBIDDEN) + @ExceptionHandler(ReservationAddUnauthorizedException.class) + public ErrorResponse reservationAddUnauthorizedException(ReservationAddUnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("reservationAddUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), RESERVATION_ADD_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(CONFLICT) + @ExceptionHandler(ReservationAlreadyCancelledException.class) + public ErrorResponse reservationAlreadyCancelledException(ReservationAlreadyCancelledException e, WebRequest request) { + alarm(e, request); + log.error("reservationAlreadyCancelledException", e); + return new ErrorResponse(e.getMessage(), RESERVATION_ALREADY_CANCELLED.getCode()); + } + + @ResponseStatus(CONFLICT) + @ExceptionHandler(ReservationAlreadyConfirmedException.class) + public ErrorResponse reservationAlreadyConfirmedException(ReservationAlreadyConfirmedException e, WebRequest request) { + alarm(e, request); + log.error("reservationAlreadyConfirmedException", e); + return new ErrorResponse(e.getMessage(), RESERVATION_ALREADY_CONFIRMED.getCode()); + } + @ResponseStatus(NOT_FOUND) @ExceptionHandler(ReservationNotFoundException.class) public ErrorResponse reservationNotFoundException(ReservationNotFoundException e, WebRequest request) { @@ -190,12 +292,12 @@ public ErrorResponse reservationNotFoundException(ReservationNotFoundException e return new ErrorResponse(e.getMessage(), NOTFOUND_RESERVATION.getCode()); } - @ResponseStatus(FORBIDDEN) - @ExceptionHandler(ReservationViewUnauthorizedException.class) - public ErrorResponse reservationViewUnauthorizedException(ReservationViewUnauthorizedException e, WebRequest request) { + @ResponseStatus(INTERNAL_SERVER_ERROR) + @ExceptionHandler(ReservationNumberIssueFailException.class) + public ErrorResponse reservationNumberIssueFailException(ReservationNumberIssueFailException e, WebRequest request) { alarm(e, request); - log.error("reservation_viewUnauthorizedException", e); - return new ErrorResponse(e.getMessage(), RESERVATION_VIEW_UNAUTHORIZED.getCode()); + log.error("reservationNumberIssueFailException", e); + return new ErrorResponse(e.getMessage(), RESERVATION_NUMBER_ISSUE_FAIL.getCode()); } @ResponseStatus(FORBIDDEN) @@ -206,6 +308,42 @@ public ErrorResponse reservationUpdateUnauthorizedException(ReservationUpdateUna return new ErrorResponse(e.getMessage(), RESERVATION_UPDATE_UNAUTHORIZED.getCode()); } + @ResponseStatus(FORBIDDEN) + @ExceptionHandler(ReservationViewUnauthorizedException.class) + public ErrorResponse reservationViewUnauthorizedException(ReservationViewUnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("reservation_viewUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), RESERVATION_VIEW_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(UnsupportedReservationStatusException.class) + public ErrorResponse unsupportedReservationStatusException(UnsupportedReservationStatusException e, WebRequest request) { + alarm(e, request); + log.error("unsupportedReservationStatusException", e); + return new ErrorResponse(e.getMessage(), UNSUPPORTED_RESERVATION_STATUS.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(UserWaitingLimitExceededException.class) + public ErrorResponse userWaitingLimitExceededException(UserWaitingLimitExceededException e, WebRequest request) { + alarm(e, request); + log.error("userWaitingLimitExceededException", e); + return new ErrorResponse(e.getMessage(), USER_WAITING_LIMIT_EXCEEDED.getCode()); + } + + + /** + * 메뉴 관련 예외 처리 + */ + @ResponseStatus(CONFLICT) + @ExceptionHandler(MenuAlreadyDeletedException.class) + public ErrorResponse menuAlreadyDeletedException(MenuAlreadyDeletedException e, WebRequest request) { + alarm(e, request); + log.error("menuAlreadyDeletedException", e); + return new ErrorResponse(e.getMessage(), MENU_ALREADY_DELETED.getCode()); + } + @ResponseStatus(FORBIDDEN) @ExceptionHandler(MenuCreationUnauthorizedException.class) public ErrorResponse menuCreationUnauthorizedException(MenuCreationUnauthorizedException e, WebRequest request) { @@ -214,12 +352,60 @@ public ErrorResponse menuCreationUnauthorizedException(MenuCreationUnauthorizedE return new ErrorResponse(e.getMessage(), MENU_CREATION_UNAUTHORIZED.getCode()); } + @ResponseStatus(CONFLICT) + @ExceptionHandler(MenuCrossStoreConflictException.class) + public ErrorResponse menuCrossStoreConflictException(MenuCrossStoreConflictException e, WebRequest request) { + alarm(e, request); + log.error("menuCrossStoreConflictException", e); + return new ErrorResponse(e.getMessage(), MENU_CROSS_STORE_CONFLICT.getCode()); + } + @ResponseStatus(FORBIDDEN) - @ExceptionHandler(MenuViewUnauthorizedException.class) - public ErrorResponse menuViewUnauthorizedException(MenuViewUnauthorizedException e, WebRequest request) { + @ExceptionHandler(MenuDeleteUnauthorizedException.class) + public ErrorResponse menuDeleteUnauthorizedException(MenuDeleteUnauthorizedException e, WebRequest request) { alarm(e, request); - log.error("menuViewUnauthorizedException", e); - return new ErrorResponse(e.getMessage(), MENU_VIEW_UNAUTHORIZED.getCode()); + log.error("menuDeleteUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), MENU_DELETE_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(MenuDuplicateIdException.class) + public ErrorResponse menuDuplicateIdException(MenuDuplicateIdException e, WebRequest request) { + alarm(e, request); + log.error("menuDuplicateIdException", e); + return new ErrorResponse(e.getMessage(), MENU_DUPLICATE_ID.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(MenuImageEmptyException.class) + public ErrorResponse menuImageEmptyException(MenuImageEmptyException e, WebRequest request) { + alarm(e, request); + log.error("menuImageEmptyException", e); + return new ErrorResponse(e.getMessage(), IMAGE_FILE_EMPTY.getCode()); + } + + @ResponseStatus(NOT_FOUND) + @ExceptionHandler(MenuNotFoundException.class) + public ErrorResponse menuNotFoundException(MenuNotFoundException e, WebRequest request) { + alarm(e, request); + log.error("menuNotFoundException", e); + return new ErrorResponse(e.getMessage(), MENU_NOT_FOUND.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(MenuInvalidSortOrderException.class) + public ErrorResponse menuInvalidSortOrderException(MenuInvalidSortOrderException e, WebRequest request) { + alarm(e, request); + log.error("menuInvalidSortOrderException", e); + return new ErrorResponse(e.getMessage(), MENU_INVALID_SORT_ORDER.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(MenuParamEmptyException.class) + public ErrorResponse menuParamEmptyException(MenuParamEmptyException e, WebRequest request) { + alarm(e, request); + log.error("menuParamEmptyException", e); + return new ErrorResponse(e.getMessage(), MENU_PARAMETER_EMPTY.getCode()); } @ResponseStatus(FORBIDDEN) @@ -231,19 +417,63 @@ public ErrorResponse menuUpdateUnauthorizedException(MenuUpdateUnauthorizedExcep } @ResponseStatus(FORBIDDEN) - @ExceptionHandler(MenuDeleteUnauthorizedException.class) - public ErrorResponse menuDeleteUnauthorizedException(MenuDeleteUnauthorizedException e, WebRequest request) { + @ExceptionHandler(MenuToggleUnauthorizedException.class) + public ErrorResponse menuToggleUnauthorizedException(MenuToggleUnauthorizedException e, WebRequest request) { alarm(e, request); - log.error("menuDeleteUnauthorizedException", e); - return new ErrorResponse(e.getMessage(), MENU_DELETE_UNAUTHORIZED.getCode()); + log.error("menuToggleUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), MENU_TOGGLE_UNAUTHORIZED.getCode()); } @ResponseStatus(FORBIDDEN) - @ExceptionHandler(StoreViewUnauthorizedException.class) - public ErrorResponse storeViewUnauthorizedException(StoreViewUnauthorizedException e, WebRequest request) { + @ExceptionHandler(MenuViewUnauthorizedException.class) + public ErrorResponse menuViewUnauthorizedException(MenuViewUnauthorizedException e, WebRequest request) { alarm(e, request); - log.error("storeViewUnauthorizedException", e); - return new ErrorResponse(e.getMessage(), STORE_VIEW_UNAUTHORIZED.getCode()); + log.error("menuViewUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), MENU_VIEW_UNAUTHORIZED.getCode()); + } + + + /** + * 주점 관련 예외 처리 + */ + @ResponseStatus(FORBIDDEN) + @ExceptionHandler(StoreDeleteUnauthorizedException.class) + public ErrorResponse storeDeleteUnauthorizedException(StoreDeleteUnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("storeDeleteUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), STORE_DELETE_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(StoreImageEmptyException.class) + public ErrorResponse storeImageEmptyException(StoreImageEmptyException e, WebRequest request) { + alarm(e, request); + log.error("storeImageEmptyException", e); + return new ErrorResponse(e.getMessage(), IMAGE_FILE_EMPTY.getCode()); + } + + @ResponseStatus(NOT_FOUND) + @ExceptionHandler(StoreImageNotFoundException.class) + public ErrorResponse storeImageNotFoundException(StoreImageNotFoundException e, WebRequest request) { + alarm(e, request); + log.error("storeImageNotFoundException", e); + return new ErrorResponse(e.getMessage(), IMAGE_FILE_NOT_FOUND.getCode()); + } + + @ResponseStatus(NOT_FOUND) + @ExceptionHandler(StoreNotFoundException.class) + public ErrorResponse storeNotFoundException(StoreNotFoundException e, WebRequest request) { + alarm(e, request); + log.error("storeNotFoundException", e); + return new ErrorResponse(e.getMessage(), STORE_NOT_FOUND.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(StoreParamEmptyException.class) + public ErrorResponse storeParamEmptyException(StoreParamEmptyException e, WebRequest request) { + alarm(e, request); + log.error("storeParamEmptyException", e); + return new ErrorResponse(e.getMessage(), STORE_PARAMETER_EMPTY.getCode()); } @ResponseStatus(FORBIDDEN) @@ -255,13 +485,98 @@ public ErrorResponse storeUpdateUnauthorizedException(StoreUpdateUnauthorizedExc } @ResponseStatus(FORBIDDEN) - @ExceptionHandler(StoreDeleteUnauthorizedException.class) - public ErrorResponse storeDeleteUnauthorizedException(StoreDeleteUnauthorizedException e, WebRequest request) { + @ExceptionHandler(StoreViewUnauthorizedException.class) + public ErrorResponse storeViewUnauthorizedException(StoreViewUnauthorizedException e, WebRequest request) { alarm(e, request); - log.error("storeDeleteUnauthorizedException", e); - return new ErrorResponse(e.getMessage(), STORE_DELETE_UNAUTHORIZED.getCode()); + log.error("storeViewUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), STORE_VIEW_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(FORBIDDEN) + @ExceptionHandler(StoreWaitingDisabledException.class) + public ErrorResponse storeWaitingDisabledException(StoreWaitingDisabledException e, WebRequest request) { + alarm(e, request); + log.error("storeWaitingDisabledException", e); + return new ErrorResponse(e.getMessage(), STORE_WAITING_DISABLED.getCode()); + } + + /** + * 결제 수단 관련 예외처리 + */ + @ResponseStatus(CONFLICT) + @ExceptionHandler(StorePaymentAlreadyExistsException.class) + public ErrorResponse storePaymentAlreadyExistsException(StorePaymentAlreadyExistsException e, WebRequest request) { + alarm(e, request); + log.error("storePaymentAlreadyExistsException", e); + return new ErrorResponse(e.getMessage(), STORE_PAYMENT_ALREADY_EXISTS.getCode()); + } + + @ResponseStatus(FORBIDDEN) + @ExceptionHandler(StorePaymentCreationUnauthorizedException.class) + public ErrorResponse storePaymentCreationUnauthorizedException(StorePaymentCreationUnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("storePaymentCreationUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), STORE_PAYMENT_CREATION_UNAUTHORIZED.getCode()); } + @ResponseStatus(NOT_FOUND) + @ExceptionHandler(StorePaymentNotFoundException.class) + public ErrorResponse storePaymentNotFoundException(StorePaymentNotFoundException e, WebRequest request) { + alarm(e, request); + log.error("storePaymentNotFoundException", e); + return new ErrorResponse(e.getMessage(), STORE_PAYMENT_NOT_FOUND.getCode()); + } + + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(StorePaymentParamEmptyException.class) + public ErrorResponse storePaymentParamEmptyException(StorePaymentParamEmptyException e, WebRequest request) { + alarm(e, request); + log.error("storePaymentParamEmptyException", e); + return new ErrorResponse(e.getMessage(), STORE_PAYMENT_PARAMETER_EMPTY.getCode()); + } + + @ResponseStatus(FORBIDDEN) + @ExceptionHandler(StorePaymentUpdateUnauthorizedException.class) + public ErrorResponse storePaymentUpdateUnauthorizedException(StorePaymentUpdateUnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("storePaymentUpdateUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), STORE_PAYMENT_UPDATE_UNAUTHORIZED.getCode()); + } + + @ResponseStatus(FORBIDDEN) + @ExceptionHandler(StorePaymentViewUnauthorizedException.class) + public ErrorResponse storePaymentViewUnauthorizedException(StorePaymentViewUnauthorizedException e, WebRequest request) { + alarm(e, request); + log.error("storePaymentViewUnauthorizedException", e); + return new ErrorResponse(e.getMessage(), STORE_PAYMENT_VIEW_UNAUTHORIZED.getCode()); + } + + /** + * redis 관련 예외처리 + */ + @ResponseStatus(INTERNAL_SERVER_ERROR) + @ExceptionHandler(MenuCounterUpdateException.class) + public ErrorResponse menuCounterUpdateException(MenuCounterUpdateException e, WebRequest request) { + alarm(e, request); + log.error("menuCounterUpdateException", e); + return new ErrorResponse(e.getMessage(), MENU_COUNTER_UPDATE.getCode()); + } + + /** + * * 그 외 예외처리 + */ + @ResponseStatus(INTERNAL_SERVER_ERROR) + @ExceptionHandler(Exception.class) + public ErrorResponse handleUnexpectedException(Exception e, WebRequest request) { + alarm(e, request); + log.error("handleUnexpectedException", e); + return new ErrorResponse( + ErrorMessage.UNEXPECTED_ERROR.getMessage(), + ErrorMessage.UNEXPECTED_ERROR.getCode() + ); + } + + // 공통 에러 Map 생성 private static Map getErrors(MethodArgumentNotValidException e) { return e.getBindingResult() diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java index 2bdd5589..50805e1c 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java @@ -17,13 +17,18 @@ import com.nowait.domaincorerdb.menu.entity.Menu; import com.nowait.domaincorerdb.menu.entity.MenuImage; import com.nowait.domaincorerdb.menu.exception.MenuCreationUnauthorizedException; +import com.nowait.domaincorerdb.menu.exception.MenuCrossStoreConflictException; import com.nowait.domaincorerdb.menu.exception.MenuDeleteUnauthorizedException; +import com.nowait.domaincorerdb.menu.exception.MenuDuplicateIdException; +import com.nowait.domaincorerdb.menu.exception.MenuInvalidSortOrderException; import com.nowait.domaincorerdb.menu.exception.MenuNotFoundException; import com.nowait.domaincorerdb.menu.exception.MenuParamEmptyException; import com.nowait.domaincorerdb.menu.exception.MenuUpdateUnauthorizedException; import com.nowait.domaincorerdb.menu.exception.MenuViewUnauthorizedException; import com.nowait.domaincorerdb.menu.repository.MenuImageRepository; import com.nowait.domaincorerdb.menu.repository.MenuRepository; +import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; +import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException; import com.nowait.domaincorerdb.user.entity.MemberDetails; import com.nowait.domaincorerdb.user.entity.User; import com.nowait.domaincorerdb.user.exception.UserNotFoundException; @@ -42,12 +47,10 @@ public class MenuService { @Transactional public MenuCreateResponse createMenu(MenuCreateRequest request, MemberDetails memberDetails) { // 사용자 정보 가져오기 - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - + User user = getUser(memberDetails); // 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인 - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(request.getStoreId())) { - throw new MenuCreationUnauthorizedException(); - } + validateViewAuthorization(user, request.getStoreId()); + // 메뉴 생성 로직 Menu toSave = request.toEntity(); Menu saved = menuRepository.save(toSave); @@ -58,10 +61,10 @@ public MenuCreateResponse createMenu(MenuCreateRequest request, MemberDetails me @Transactional(readOnly = true) public MenuReadResponse getAllMenusByStoreId(Long storeId, MemberDetails memberDetails) { // 사용자 정보 가져오기 - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - + User user = getUser(memberDetails); // 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인 - validateMenuViewAuthorization(user, storeId); + validateViewAuthorization(user, storeId); + List menus = menuRepository.findAllByStoreIdAndDeletedFalseOrderBySortOrder(storeId); List menuReadResponse = menus.stream() @@ -83,11 +86,10 @@ public MenuReadDto getMenuById(Long storeId, Long menuId, MemberDetails memberDe throw new MenuParamEmptyException(); } // 사용자 정보 가져오기 - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - Menu menu = menuRepository.findByStoreIdAndIdAndDeletedFalse(storeId, menuId) - .orElseThrow(MenuNotFoundException::new); + User user = getUser(memberDetails); + Menu menu = getMenu(menuId); // 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인 - validateMenuViewAuthorization(user, menu.getStoreId()); + validateViewAuthorization(user, menu.getStoreId()); List images = menuImageRepository.findByMenu(menu); List imageDto = images.stream() @@ -100,13 +102,10 @@ public MenuReadDto getMenuById(Long storeId, Long menuId, MemberDetails memberDe @Transactional public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - Menu menu = menuRepository.findByIdAndDeletedFalse(menuId) - .orElseThrow(MenuNotFoundException::new); + User user = getUser(memberDetails); + Menu menu = getMenu(menuId); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(menu.getStoreId())) { - throw new MenuUpdateUnauthorizedException(); - } + validateUpdateAuthorization(user, menu.getStoreId()); menu.updateInfo( request.getAdminDisplayName(), @@ -138,11 +137,11 @@ public String updateMenuSortOrder(List requests, MemberDe } if (requests.stream().map(MenuSortUpdateRequest::getMenuId).distinct().count() != requests.size()) { - throw new IllegalArgumentException("중복된 메뉴 ID가 포함되어 있습니다."); + throw new MenuDuplicateIdException(); } if (requests.stream().anyMatch(r -> r.getSortOrder() == null || r.getSortOrder() < 0)) { - throw new IllegalArgumentException("잘못된 정렬 순서가 포함되어 있습니다. 정렬 순서는 0 이상의 정수여야 합니다."); + throw new MenuInvalidSortOrderException(); } List ids = requests.stream().map(MenuSortUpdateRequest::getMenuId).toList(); @@ -153,7 +152,7 @@ public String updateMenuSortOrder(List requests, MemberDe Long storeId = menus.get(0).getStoreId(); if (!menus.stream().allMatch(m -> storeId.equals(m.getStoreId()))) { - throw new IllegalArgumentException("다른 매장의 메뉴가 있습니다."); + throw new MenuCrossStoreConflictException(); } Map idToSort = requests.stream() @@ -169,12 +168,10 @@ public String updateMenuSortOrder(List requests, MemberDe @Transactional public String deleteMenu(Long menuId, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - Menu menu = menuRepository.findById(menuId).orElseThrow(MenuNotFoundException::new); + User user = getUser(memberDetails); + Menu menu = getMenu(menuId); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(menu.getStoreId())) { - throw new MenuDeleteUnauthorizedException(); - } + validateDeleteAuthorization(user, menu.getStoreId()); menu.markAsDeleted(); menuRepository.save(menu); @@ -191,10 +188,34 @@ public Boolean toggleSoldOut(Long menuId) { return menu.getIsSoldOut(); } - private static void validateMenuViewAuthorization(User user, Long storeId) { - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + + private void validateViewAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { throw new MenuViewUnauthorizedException(); } } + private void validateUpdateAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { + throw new MenuUpdateUnauthorizedException(); + } + } + + private void validateDeleteAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { + throw new MenuDeleteUnauthorizedException(); + } + } + + private User getUser(MemberDetails memberDetails) { + return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + } + + private Menu getMenu(Long menuId) { + return menuRepository.findByIdAndDeletedFalse(menuId) + .orElseThrow(MenuNotFoundException::new); + } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java index cfd26222..0ef84e73 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java @@ -46,11 +46,10 @@ public class OrderService { @Transactional(readOnly = true) public List findAllOrders(Long storeId, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); storeRepository.findByStoreIdAndDeletedFalse(storeId).orElseThrow(StoreNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new OrderViewUnauthorizedException(); - } + + validateViewAuthorization(user, storeId); LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul")); LocalDateTime startDateTime = today.atStartOfDay(); @@ -64,11 +63,12 @@ public List findAllOrders(Long storeId, MemberDetails memberDe @Transactional public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus newStatus, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); UserOrder userOrder = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(userOrder.getStore().getStoreId())) { - throw new OrderUpdateUnauthorizedException(); - } + Long storeId = userOrder.getStore().getStoreId(); + + validateUpdateAuthorization(user, storeId); + userOrder.updateStatus(newStatus); if (OrderStatus.COOKED.equals(newStatus)) { @@ -92,12 +92,10 @@ public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus @Transactional public OrderStatusUpdateResponseDto cancelOrder(Long orderId, CancelOrderRequest cancelOrderRequest, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); UserOrder userOrder = orderRepository.findById(orderId).orElseThrow(OrderNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(userOrder.getStore().getStoreId())) { - throw new OrderUpdateUnauthorizedException(); - } + validateUpdateAuthorization(user, userOrder.getStore().getStoreId()); userOrder.cancelOrder(); @@ -115,25 +113,43 @@ public OrderStatusUpdateResponseDto cancelOrder(Long orderId, CancelOrderRequest @Transactional(readOnly = true) public OrderSalesSumDetail getSaleSumByStoreId(MemberDetails memberDetails, LocalDate date) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); Long storeId = user.getStoreId(); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new OrderViewUnauthorizedException(); - } + validateViewAuthorization(user, storeId); return statisticCustomRepository.findSalesSumByStoreId(storeId, date); } + // 현재는 사용하지 않음. 향후 관리자 통계 페이지 확장 시 활용 가능 @Transactional(readOnly = true) public List getTop5StoresBySalesToday(MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); Long storeId = user.getStoreId(); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { + validateUpdateAuthorization(user, storeId); + + return statisticCustomRepository.getTop4PlusMine(storeId); + } + + private void validateViewAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { throw new OrderViewUnauthorizedException(); } + } - return statisticCustomRepository.getTop4PlusMine(storeId); + private void validateUpdateAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { + throw new OrderUpdateUnauthorizedException(); + } + } + + private User getUser(MemberDetails memberDetails) { + if (memberDetails == null) { + throw new OrderViewUnauthorizedException(); + } + return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java index d962de4c..3d5eaf74 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java @@ -96,4 +96,22 @@ public ResponseEntity updateEntry( )); } + @PatchMapping("/admin/{storeId}/{reservationNumber}") + @Operation(summary = "예약팀 상태 업데이트 처리", description = "특정 예약에 대한 입장 완료 처리") + @ApiResponse(responseCode = "200", description = "예약팀 상태 변경 : CALLING -> CONFIRMED") + public ResponseEntity updateEntryWithReservationNumber( + @PathVariable Long storeId, + @PathVariable String reservationNumber, + @RequestBody ReservationStatusRequest request, + @AuthenticationPrincipal MemberDetails memberDetails + ) { + EntryStatusResponseDto response = reservationService.processEntryStatusByReservationNumber(storeId, reservationNumber, memberDetails, request.getStatus()); + return ResponseEntity + .status(HttpStatus.OK) + .body( + ApiUtils.success( + response + )); + } + } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java index fb461266..6860d13f 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java @@ -27,9 +27,14 @@ import com.nowait.common.enums.ReservationStatus; import com.nowait.common.enums.Role; import com.nowait.domaincorerdb.reservation.entity.Reservation; +import com.nowait.domaincorerdb.reservation.exception.InvalidReservationParameterException; +import com.nowait.domaincorerdb.reservation.exception.InvalidReservationStatusTransitionException; +import com.nowait.domaincorerdb.store.exception.StoreViewUnauthorizedException; +import com.nowait.domaincoreredis.reservation.exception.ReservationDataInconsistencyException; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationViewUnauthorizedException; +import com.nowait.domaincorerdb.reservation.exception.UnsupportedReservationStatusException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; import com.nowait.domaincorerdb.store.repository.StoreRepository; import com.nowait.domaincorerdb.user.entity.MemberDetails; @@ -56,10 +61,8 @@ public class ReservationService { //TODO 성능 비교를 위해 남겨둔 로직 @Transactional(readOnly = true) public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new ReservationViewUnauthorizedException(); - } + User user = getUser(memberDetails); + validateViewAuthorization(user, storeId); List reservations = reservationRepository.findAllByStore_StoreIdOrderByRequestedAtAsc(storeId); // 상태별 카운트 집계 @@ -93,13 +96,11 @@ public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId, Mem @Transactional public CallGetResponseDto updateReservationStatus(Long reservationId, ReservationStatusUpdateRequestDto requestDto, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); Reservation reservation = reservationRepository.findById(reservationId) .orElseThrow(ReservationNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId() - .equals(reservation.getStore().getStoreId())) { - throw new ReservationUpdateUnauthorizedException(); - } + validateUpdateAuthorization(user, reservation.getStore().getStoreId()); + reservation.markUpdated(LocalDateTime.now(), requestDto.getStatus()); return CallGetResponseDto.fromEntity(reservation); } @@ -194,7 +195,8 @@ public List getAllWaitingUserDetails(Long storeId) { // 완료 or 취소 처리된 대기 리스트 조회 @Transactional(readOnly = true) public List getCompletedWaitingUserDetails(Long storeId, MemberDetails memberDetails) { - authorize(storeId, memberDetails); + User user = getUser(memberDetails); + validateViewAuthorization(user, storeId); List reservations = findTodayWaiting(storeId); return reservations.stream() @@ -212,7 +214,12 @@ public List getCompletedWaitingUserDetails(Long storeId, Me public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, MemberDetails member, ReservationStatus newStatus) { - authorize(storeId, member); + User user = getUser(member); + validateUpdateAuthorization(user, storeId); + + if (userId == null || userId.isBlank()) { + throw new InvalidReservationParameterException("userId 값이 비어있습니다."); + } String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; @@ -222,6 +229,17 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me Double score = redisTemplate.opsForZSet().score(queueKey, userId); Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); + if (partySize == null || partySize <= 0) { + throw new InvalidReservationParameterException("partySize가 유효하지 않습니다. (storeId=" + storeId + ", userId=" + userId + ")"); + } + + if (reservationNumber == null || currStatus == null) { + throw new ReservationDataInconsistencyException( + String.format("storeId=%d, userId=%s, reservationNumber=%s, status=%s, partySize=%s", + storeId, userId, reservationNumber, currStatus, partySize) + ); + } + LocalDateTime requestedAt = score != null ? Instant.ofEpochMilli(score.longValue()).atZone(ZoneId.of("Asia/Seoul")).toLocalDateTime() : LocalDateTime.now(); @@ -230,7 +248,9 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me switch (newStatus) { case CALLING: if (!ReservationStatus.WAITING.name().equals(currStatus)) { - throw new IllegalStateException("WAITING 상태에서만 CALLING 가능합니다."); + throw new InvalidReservationStatusTransitionException( + ReservationStatus.valueOf(currStatus), ReservationStatus.CALLING + ); } waitingRedisRepository.setWaitingStatus(storeId, userId, ReservationStatus.CALLING.name()); waitingRedisRepository.setWaitingCalledAt(storeId, userId, @@ -294,7 +314,9 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me List.of(ReservationStatus.CANCELLED), start, end - ).orElseThrow(() -> new IllegalStateException("취소된 예약이 없습니다.")); + ).orElseThrow(() -> new ReservationDataInconsistencyException( + String.format("취소된 예약이 DB에 존재하지 않습니다. (storeId=%d, userId=%s)", storeId, userId) + )); existing.markUpdated(LocalDateTime.now(ZoneId.of("Asia/Seoul")), ReservationStatus.CONFIRMED); Reservation saved = reservationRepository.save(existing); @@ -304,7 +326,9 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me case CANCELLED: if (!(ReservationStatus.WAITING.name().equals(currStatus) || ReservationStatus.CALLING.name().equals(currStatus))) { - throw new IllegalStateException("WAITING/CALLING 상태에서만 취소 가능합니다."); + throw new InvalidReservationStatusTransitionException( + ReservationStatus.valueOf(currStatus), ReservationStatus.CANCELLED + ); } if (reservationNumber != null) { @@ -329,10 +353,162 @@ public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, Me return EntryStatusResponseDto.fromEntity(saved); default: - throw new IllegalArgumentException("지원하지 않는 상태: " + newStatus); + throw new UnsupportedReservationStatusException(newStatus); } } + + @Transactional + public EntryStatusResponseDto processEntryStatusByReservationNumber(Long storeId, String reservationNumber, + MemberDetails member, ReservationStatus newStatus) { + + User user = getUser(member); + validateUpdateAuthorization(user, storeId); + + if (reservationNumber == null || reservationNumber.isBlank()) { + throw new InvalidReservationParameterException("reservationNumber 값이 비어있습니다."); + } + + // Redis에서 userId 역추적 + String userId = waitingRedisRepository.getUserIdByReservationNumber(storeId, reservationNumber); + if (userId == null) { + throw new ReservationDataInconsistencyException( + String.format("storeId=%d, reservationNumber=%s 에 해당하는 userId를 찾을 수 없습니다.", storeId, reservationNumber) + ); + } + + String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + + String currStatus = waitingRedisRepository.getWaitingStatus(storeId, userId); + Double score = redisTemplate.opsForZSet().score(queueKey, userId); + Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); + + + if (partySize == null || partySize <= 0) { + throw new InvalidReservationParameterException("partySize가 유효하지 않습니다. (storeId=" + storeId + ", reservationNumber=" + reservationNumber + ")"); + } + + if (currStatus == null) { + throw new ReservationDataInconsistencyException( + String.format("storeId=%d, reservationNumber=%s 의 상태값을 찾을 수 없습니다.", storeId, reservationNumber) + ); + } + + LocalDateTime requestedAt = score != null + ? Instant.ofEpochMilli(score.longValue()).atZone(ZoneId.of("Asia/Seoul")).toLocalDateTime() + : LocalDateTime.now(); + LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Seoul")); + + switch (newStatus) { + case CALLING: + if (!ReservationStatus.WAITING.name().equals(currStatus)) { + throw new InvalidReservationStatusTransitionException( + ReservationStatus.valueOf(currStatus), ReservationStatus.CALLING + ); + } + waitingRedisRepository.setWaitingStatus(storeId, userId, ReservationStatus.CALLING.name()); + waitingRedisRepository.setWaitingCalledAt(storeId, userId, + now.atZone(ZoneId.of("Asia/Seoul")).toInstant().toEpochMilli()); + + return EntryStatusResponseDto.builder() + .reservationNumber(reservationNumber) + .userId(userId) + .partySize(partySize) + .userName(userRepository.getReferenceById(Long.valueOf(userId)).getNickname()) + .createdAt(requestedAt) + .status("CALLING") + .updatedAt(now) + .message("호출되었습니다.") + .build(); + + case CONFIRMED: + // 1) 기존 대기 중이거나 호출 중일 때: Redis → DB 최초 저장 + if (ReservationStatus.WAITING.name().equals(currStatus) || ReservationStatus.CALLING.name() + .equals(currStatus)) { + + if (reservationNumber != null) { + waitingPermitLuaRepository.removeActiveMember( + userId, String.valueOf(storeId), reservationNumber + ); + } + + // 새 Reservation 생성 & 저장 + Reservation r = Reservation.builder() + .reservationNumber(reservationNumber) + .store(storeRepository.getReferenceById(storeId)) + .user(userRepository.getReferenceById(Long.valueOf(userId))) + .partySize(partySize) + .requestedAt(requestedAt) + .updatedAt(LocalDateTime.now(ZoneId.of("Asia/Seoul"))) + .status(ReservationStatus.valueOf(currStatus)) + .build(); + + // 호출 시각 반영 + r.markUpdated(LocalDateTime.now(), ReservationStatus.CONFIRMED); + Reservation saved = reservationRepository.save(r); + // Redis 전부 삭제 + waitingRedisRepository.deleteWaiting(storeId, userId); + return EntryStatusResponseDto.fromEntity(saved); + } else { + if (reservationNumber != null) { + try { + waitingPermitLuaRepository.removeActiveMember(userId, String.valueOf(storeId), + reservationNumber); + } catch (Exception ignore) { + } + } + + // 2) 이미 취소(CANCELLED)된 경우: DB 레코드 찾아 바로 CONFIRMED 로 전환 + LocalDateTime start = LocalDate.now().atStartOfDay(); + LocalDateTime end = LocalDate.now().atTime(LocalTime.MAX); + Reservation existing = reservationRepository + .findFirstByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetweenOrderByRequestedAtDesc( + storeId, + Long.valueOf(userId), + List.of(ReservationStatus.CANCELLED), + start, + end + ).orElseThrow(() -> new ReservationDataInconsistencyException( + String.format("취소된 예약이 DB에 존재하지 않습니다. (storeId=%d, userId=%s)", storeId, userId) + )); + + existing.markUpdated(LocalDateTime.now(ZoneId.of("Asia/Seoul")), ReservationStatus.CONFIRMED); + Reservation saved = reservationRepository.save(existing); + return EntryStatusResponseDto.fromEntity(saved); + } + + case CANCELLED: + if (!(ReservationStatus.WAITING.name().equals(currStatus) + || ReservationStatus.CALLING.name().equals(currStatus))) { + throw new InvalidReservationStatusTransitionException( + ReservationStatus.valueOf(currStatus), ReservationStatus.CANCELLED + ); + } + + waitingPermitLuaRepository.removeActiveMember(userId, String.valueOf(storeId), reservationNumber); + + Reservation r = Reservation.builder() + .reservationNumber(reservationNumber) + .store(storeRepository.getReferenceById(storeId)) + .user(userRepository.getReferenceById(Long.valueOf(userId))) + .partySize(partySize) + .requestedAt(requestedAt) + .updatedAt(now) + .status(ReservationStatus.valueOf(currStatus)) + .build(); + + r.markUpdated(now, ReservationStatus.CANCELLED); + Reservation saved = reservationRepository.save(r); + waitingRedisRepository.deleteWaiting(storeId, userId); + + return EntryStatusResponseDto.fromEntity(saved); + + default: + throw new UnsupportedReservationStatusException(newStatus); + } + } + + /** * 공통 메서드 */ @@ -350,13 +526,25 @@ private List findTodayWaiting(Long storeId) { } // 사용자 인증 - private User authorize(Long storeId, MemberDetails member) { - User u = userRepository.findById(member.getId()) - .orElseThrow(UserNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(u.getRole()) && !storeId.equals(u.getStoreId())) { + private void validateViewAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { + throw new ReservationViewUnauthorizedException(); + } + } + + private void validateUpdateAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { + throw new ReservationUpdateUnauthorizedException(); + } + } + + private User getUser(MemberDetails memberDetails) { + if (memberDetails == null) { throw new ReservationViewUnauthorizedException(); } - return u; + return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java index abdc5837..da356da0 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java @@ -15,6 +15,8 @@ import com.nowait.common.enums.Role; import com.nowait.domaincorerdb.department.entity.Department; import com.nowait.domaincorerdb.department.repository.DepartmentRepository; +import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; +import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException; import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.entity.StoreImage; @@ -57,10 +59,9 @@ public StoreCreateResponse createStore(StoreCreateRequest request) { @Transactional(readOnly = true) public StoreDetailReadResponse getStoreByStoreId(Long storeId, MemberDetails memberDetails) { if (storeId == null) throw new StoreParamEmptyException(); - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StoreViewUnauthorizedException(); - } + User user = getUser(memberDetails); + validateViewAuthorization(user, storeId); + Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) .orElseThrow(StoreNotFoundException::new); @@ -79,11 +80,11 @@ public StoreDetailReadResponse getStoreByStoreId(Long storeId, MemberDetails mem @Override @Transactional public StoreReadDto updateStore(Long storeId, StoreUpdateRequest request, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); if (storeId == null || request == null) throw new StoreParamEmptyException(); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StoreUpdateUnauthorizedException(); - } + + User user = getUser(memberDetails); + validateUpdateAuthorization(user, storeId); + Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) .orElseThrow(StoreNotFoundException::new); @@ -109,13 +110,13 @@ public StoreReadDto updateStore(Long storeId, StoreUpdateRequest request, Member @Override @Transactional public String deleteStore(Long storeId, MemberDetails memberDetails) { - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + if (storeId == null) { throw new StoreParamEmptyException(); } - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StoreDeleteUnauthorizedException(); - } + + User user = getUser(memberDetails); + validateUpdateAuthorization(user, storeId); Store store = storeRepository.findByStoreIdAndDeletedFalse(storeId) .orElseThrow(StoreNotFoundException::new); @@ -129,9 +130,30 @@ public String deleteStore(Long storeId, MemberDetails memberDetails) { @Transactional public Boolean toggleActive(Long storeId) { Store store = storeRepository.findById(storeId) - .orElseThrow(() -> new IllegalArgumentException("해당 스토어가 존재하지 않습니다.")); + .orElseThrow(StoreNotFoundException::new); store.toggleActive(); return store.getIsActive(); } + + private void validateViewAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { + throw new StoreViewUnauthorizedException(); + } + } + + private void validateUpdateAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())))) { + throw new StoreUpdateUnauthorizedException(); + } + } + + private User getUser(MemberDetails memberDetails) { + if (memberDetails == null) { + throw new StoreViewUnauthorizedException(); + } + return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + } } diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java index 5bcf314d..203aea70 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java @@ -5,6 +5,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; diff --git a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java index a7852824..95aab0e3 100644 --- a/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java +++ b/nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java @@ -13,6 +13,7 @@ import com.nowait.domaincorerdb.storepayment.entity.StorePayment; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentAlreadyExistsException; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentCreationUnauthorizedException; +import com.nowait.domaincorerdb.storepayment.exception.StorePaymentDeleteUnauthorizedException; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentNotFoundException; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentParamEmptyException; import com.nowait.domaincorerdb.storepayment.exception.StorePaymentUpdateUnauthorizedException; @@ -37,14 +38,13 @@ public class StorePaymentServiceImpl implements StorePaymentService { public StorePaymentCreateResponse createStorePayment(StorePaymentCreateRequest request, MemberDetails memberDetails) { if (request == null) throw new StorePaymentParamEmptyException(); - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); Long storeId = user.getStoreId(); if (storePaymentRepository.findByStoreId(storeId).isPresent()) { throw new StorePaymentAlreadyExistsException(); } - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StorePaymentCreationUnauthorizedException(); - } + validateCreationAuthorization(user, storeId); + StorePayment toSave = request.toEntity(storeId); StorePayment saved = storePaymentRepository.save(toSave); @@ -56,11 +56,9 @@ public StorePaymentCreateResponse createStorePayment(StorePaymentCreateRequest r public Optional getStorePaymentByStoreId(MemberDetails memberDetails) { if (memberDetails == null) throw new StorePaymentParamEmptyException(); - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); Long storeId = user.getStoreId(); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StorePaymentViewUnauthorizedException(); - } + validateViewAuthorization(user, storeId); return storePaymentRepository.findByStoreId(storeId) .map(StorePaymentReadDto::fromEntity); @@ -71,11 +69,9 @@ public Optional getStorePaymentByStoreId(MemberDetails memb public StorePaymentReadDto updateStorePayment(StorePaymentUpdateRequest request, MemberDetails memberDetails) { if (request == null) throw new StorePaymentParamEmptyException(); - User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails); Long storeId = user.getStoreId(); - if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { - throw new StorePaymentUpdateUnauthorizedException(); - } + validateUpdateAuthorization(user, storeId); StorePayment storePayment = storePaymentRepository.findByStoreId(storeId) .orElseThrow(StorePaymentNotFoundException::new); @@ -89,4 +85,32 @@ public StorePaymentReadDto updateStorePayment(StorePaymentUpdateRequest request, return StorePaymentReadDto.fromEntity(storePayment); } + + private User getUser(MemberDetails memberDetails) { + return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + } + + private void validateViewAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { + throw new StorePaymentViewUnauthorizedException(); + } + } + + private void validateCreationAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { + throw new StorePaymentCreationUnauthorizedException(); + } + } + + private void validateUpdateAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { + throw new StorePaymentUpdateUnauthorizedException(); + } + } + + private void validateDeleteAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { + throw new StorePaymentDeleteUnauthorizedException(); + } + } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index 9b4c5f4d..cf09ca46 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -172,7 +172,7 @@ public ErrorResponse handleAlreadyDeleteBookmark( public ErrorResponse userNotFoundException(UserNotFoundException e, WebRequest request) { alarm(e, request); log.error("userNotFoundException", e); - return new ErrorResponse(e.getMessage(), NOTFOUND_USER.getCode()); + return new ErrorResponse(e.getMessage(), NOT_FOUND_USER.getCode()); } @ResponseStatus(BAD_REQUEST) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java index 5b317dcc..4f7f5b9e 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java @@ -17,16 +17,19 @@ import com.nowait.applicationuser.order.dto.OrderMenuDto; import com.nowait.applicationuser.order.dto.OrderResponseDto; import com.nowait.domaincorerdb.menu.entity.Menu; +import com.nowait.domaincorerdb.menu.exception.MenuNotFoundException; import com.nowait.domaincorerdb.menu.repository.MenuRepository; import com.nowait.domaincorerdb.order.entity.OrderItem; import com.nowait.domaincorerdb.order.entity.OrderStatus; import com.nowait.domaincorerdb.order.entity.UserOrder; +import com.nowait.domaincorerdb.order.exception.DepositorNameTooLongException; import com.nowait.domaincorerdb.order.exception.DuplicateOrderException; import com.nowait.domaincorerdb.order.exception.OrderItemsEmptyException; import com.nowait.domaincorerdb.order.exception.OrderParameterEmptyException; import com.nowait.domaincorerdb.order.repository.OrderItemRepository; import com.nowait.domaincorerdb.order.repository.OrderRepository; import com.nowait.domaincorerdb.store.entity.Store; +import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; import com.nowait.domaincorerdb.store.repository.StoreRepository; import lombok.RequiredArgsConstructor; @@ -38,6 +41,7 @@ public class OrderService { private final StoreRepository storeRepository; private final MenuRepository menuRepository; private final OrderItemRepository orderItemRepository; + @Transactional public OrderCreateResponseDto createOrder(String publicCode, Long tableId, OrderCreateRequestDto orderCreateRequestDto, String sessionId) { @@ -49,7 +53,7 @@ public OrderCreateResponseDto createOrder(String publicCode, Long tableId, // 1. Store 조회 Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode) - .orElseThrow(() -> new IllegalArgumentException("store not found")); + .orElseThrow(StoreNotFoundException::new); // 2. UserOrder 생성 및 signature 저장 UserOrder order = UserOrder.builder() @@ -77,7 +81,7 @@ public OrderCreateResponseDto createOrder(String publicCode, Long tableId, List orderItems = orderCreateRequestDto.getItems().stream() .map(item -> { Menu menu = Optional.ofNullable(menuMap.get(item.getMenuId())) - .orElseThrow(() -> new IllegalArgumentException("menu not found: " + item.getMenuId())); + .orElseThrow(MenuNotFoundException::new); return OrderItem.builder() .userOrder(savedOrder) .menu(menu) @@ -86,18 +90,18 @@ public OrderCreateResponseDto createOrder(String publicCode, Long tableId, }) .collect(Collectors.toList()); - orderItemRepository.saveAll(orderItems); // 5. 응답 반환 - return OrderCreateResponseDto.fromEntity(savedOrder,orderItems); + return OrderCreateResponseDto.fromEntity(savedOrder, orderItems); } @Transactional(readOnly = true) public List getOrderItemsGroupByOrderId( String publicCode, Long tableId, String sessionId) { - List userOrders = orderRepository.findByStore_PublicCodeAndTableIdAndSessionId(publicCode, tableId, sessionId); + List userOrders = orderRepository.findByStore_PublicCodeAndTableIdAndSessionId(publicCode, tableId, + sessionId); // orderId 기준으로 바로 변환 return userOrders.stream() @@ -115,21 +119,24 @@ public List getOrderItemsGroupByOrderId( .toList(); } - - private static void parameterValidation(String publicCode, Long tableId, OrderCreateRequestDto orderCreateRequestDto) { + private static void parameterValidation(String publicCode, Long tableId, + OrderCreateRequestDto orderCreateRequestDto) { if (publicCode == null || tableId == null || orderCreateRequestDto == null) { - throw new OrderParameterEmptyException(); + throw new OrderParameterEmptyException(); } if (orderCreateRequestDto.getItems() == null || orderCreateRequestDto.getItems().isEmpty()) { - throw new OrderItemsEmptyException(); + throw new OrderItemsEmptyException(); } - if (orderCreateRequestDto.getDepositorName() == null || orderCreateRequestDto.getDepositorName().trim().isEmpty()) { - throw new OrderParameterEmptyException(); + if (orderCreateRequestDto.getDepositorName() == null || orderCreateRequestDto.getDepositorName() + .trim() + .isEmpty()) { + throw new OrderParameterEmptyException(); } if (orderCreateRequestDto.getDepositorName().length() > 20) { - throw new IllegalArgumentException("Depositor name is too long"); + throw new DepositorNameTooLongException(); } } + private String generateOrderSignature(String storeId, Long tableId, List items) { String cartString = items.stream() .sorted((a, b) -> a.getMenuId().compareTo(b.getMenuId())) // 메뉴 ID 기준 정렬 diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java index 50c84d83..d11e5a30 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java @@ -37,6 +37,7 @@ public String addToWaitingQueue(Long storeId, String userId, Integer partySize, String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; String seqKey = RedisKeyUtils.buildReservationSeqKey(storeId); String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); + String userMapKey = RedisKeyUtils.buildReservationUserKey(storeId); Boolean added = redisTemplate.opsForZSet().addIfAbsent(queueKey, userId, timestamp); String reservationId; @@ -47,7 +48,7 @@ public String addToWaitingQueue(Long storeId, String userId, Integer partySize, // 5) Hash에 저장 redisTemplate.opsForHash().put(numberMapKey, userId, reservationId); - + redisTemplate.opsForHash().put(userMapKey, reservationId, userId); // 6) 기존 partySize, status, TTL 설정 redisTemplate.opsForHash().put(partyKey, userId, partySize.toString()); redisTemplate.opsForHash().put(statusKey, userId, "WAITING"); diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index be45f308..754b4ba6 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -12,7 +12,7 @@ public enum ErrorMessage { DOES_NOT_MATCH_REFRESH_TOKEN("기존 리프레시 토큰이 일치하지 않습니다.", "token002"), // user - NOTFOUND_USER("저장된 사용자 정보가 없습니다.", "user001"), + NOT_FOUND_USER("저장된 사용자 정보가 없습니다.", "user001"), //order ORDER_PARAMETER_EMPTY("주문 생성 시 파라미터 정보가 없습니다.", "order001"), @@ -22,6 +22,8 @@ public enum ErrorMessage { ORDER_VIEW_UNAUTHORIZED("주문 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "order005"), ORDER_NOT_FOUND("해당 주문을 찾을 수 없습니다.", "order006"), ORDER_UPDATE_UNAUTHORIZED("주문 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "order007"), + ORDER_ALREADY_CANCELLED("이미 취소된 주문입니다.", "order008"), + INVALID_ORDER_STATUS_TRANSITION("유효하지 않은 주문 상태 변경입니다. (현재: %s, 요청: %s)", "order009"), //reservation NOTFOUND_RESERVATION("저장된 예약 정보가 없습니다.", "reservation001"), @@ -31,6 +33,14 @@ public enum ErrorMessage { USER_WAITING_LIMIT_EXCEEDED("유저당 웨이팅 가능 개수(3개)를 초과했습니다.", "reservation005"), RESERVATION_NUMBER_ISSUE_FAIL("예약 번호 발급에 실패했습니다.", "reservation006"), RESERVATION_ADD_UNAUTHORIZED("MANAGER는 예약 대기를 할 수 없습니다.", "reservation007"), + INVALID_RESERVATION_STATUS_TRANSITION("유효하지 않은 예약 상태 변경입니다. (현재: %s, 요청: %s)", "reservation008"), + RESERVATION_ALREADY_CONFIRMED("이미 확정된 예약입니다.", "reservation009"), + RESERVATION_ALREADY_CANCELLED("이미 취소된 예약입니다.", "reservation010"), + UNSUPPORTED_RESERVATION_STATUS("지원하지 않는 예약 상태입니다: %s", "reservation011"), + INVALID_RESERVATION_PARAMETER("잘못된 예약 요청 파라미터입니다. (%s)", "reservation012"), + + // redis + RESERVATION_DATA_INCONSISTENCY("예약 데이터가 Redis와 DB 간 일치하지 않습니다. (%s)", "redis001"), // bookmark DUPLICATE_BOOKMARK("이미 북마크한 주점입니다.", "bookmark001"), @@ -38,7 +48,6 @@ public enum ErrorMessage { NOT_FOUND_BOOKMARK("북마크를 찾을 수 없습니다", "bookmark003"), ALREADY_DELETED_BOOKMARK("이미 삭제된 북마크입니다.", "bookmark004"), - // menu MENU_PARAMETER_EMPTY("메뉴 생성 시 파라미터 정보가 없습니다.", "menu001"), MENU_NOT_FOUND("해당 메뉴를 찾을 수 없습니다.", "menu002"), @@ -46,6 +55,11 @@ public enum ErrorMessage { MENU_VIEW_UNAUTHORIZED("메뉴 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "menu004"), MENU_UPDATE_UNAUTHORIZED("메뉴 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "menu005"), MENU_DELETE_UNAUTHORIZED("메뉴 삭제 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "menu006"), + MENU_INVALID_SORT_ORDER("잘못된 정렬 순서가 포함되어 있습니다. 정렬 순서는 0 이상의 정수여야 합니다.", "menu007"), + MENU_DUPLICATE_ID("중복된 메뉴 ID가 포함되어 있습니다.", "menu008"), + MENU_CROSS_STORE_CONFLICT("서로 다른 매장의 메뉴가 포함되어 있습니다.", "menu009"), + MENU_ALREADY_DELETED("이미 삭제된 메뉴입니다.", "menu010"), + MENU_TOGGLE_UNAUTHORIZED("메뉴 품절 상태 변경 권한이 없습니다.", "menu011"), // store STORE_PARAMETER_EMPTY("주점 생성 시 파라미터 정보가 없습니다.", "store001"), @@ -60,20 +74,23 @@ public enum ErrorMessage { STORE_PAYMENT_NOT_FOUND("해당 주점 결제 정보를 찾을 수 없습니다.", "storePayment002"), STORE_PAYMENT_VIEW_UNAUTHORIZED("주점 결제 정보 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment003"), STORE_PAYMENT_CREATION_UNAUTHORIZED("주점 결제 정보 생성 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment004"), - STORE_PAYMENT_UPDATE_UNAUTHORIZED("주점 결제 정보 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment004"), - STORE_PAYMENT_DELETE_UNAUTHORIZED("주점 결제 정보 삭제 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment005"), - STORE_PAYMENT_ALREADY_EXISTS("이미 존재하는 주점 결제 정보입니다.", "storePayment006"), + STORE_PAYMENT_UPDATE_UNAUTHORIZED("주점 결제 정보 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment005"), + STORE_PAYMENT_DELETE_UNAUTHORIZED("주점 결제 정보 삭제 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "storePayment006"), + STORE_PAYMENT_ALREADY_EXISTS("이미 존재하는 주점 결제 정보입니다.", "storePayment007"), // Statistics STATISTIC_VIEW_UNAUTHORIZED("통계 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "statistics001"), MENU_COUNTER_UPDATE("메뉴 카운터 업데이트 실패", "statistics002"), // image - IMAGE_FILE_EMPTY("이미지 파일을 업로드 해주세요", "image001"), - IMAGE_FILE_NOT_FOUND("이미지 파일을 업로드 해주세요", "image001"), + IMAGE_FILE_EMPTY("업로드 된 이미지 파일이 없습니다.", "image001"), + IMAGE_FILE_NOT_FOUND("DB에 해당 이미지 메타데이터가 존재하지 않습니다.", "image002"), // search - SEARCH_PARAMETER_EMPTY("검색어가 비어있습니다.", "search001"); + SEARCH_PARAMETER_EMPTY("검색어가 비어있습니다.", "search001"), + + // common + UNEXPECTED_ERROR("예상하지 못한 오류가 발생했습니다.", "common999"); private final String message; private final String code; @@ -90,4 +107,8 @@ public String getMessage() { public String getCode() { return code; } + + public String format(Object... args) { + return String.format(message, args); + } } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuAlreadyDeletedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuAlreadyDeletedException.java new file mode 100644 index 00000000..0a2202a3 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuAlreadyDeletedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuAlreadyDeletedException extends RuntimeException { + public MenuAlreadyDeletedException() { + super(ErrorMessage.MENU_ALREADY_DELETED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCrossStoreConflictException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCrossStoreConflictException.java new file mode 100644 index 00000000..70caeae1 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCrossStoreConflictException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuCrossStoreConflictException extends RuntimeException { + public MenuCrossStoreConflictException() { + super(ErrorMessage.MENU_CROSS_STORE_CONFLICT.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDuplicateIdException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDuplicateIdException.java new file mode 100644 index 00000000..761595ac --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDuplicateIdException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuDuplicateIdException extends RuntimeException { + public MenuDuplicateIdException() { + super(ErrorMessage.MENU_DUPLICATE_ID.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuInvalidSortOrderException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuInvalidSortOrderException.java new file mode 100644 index 00000000..55570c3a --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuInvalidSortOrderException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuInvalidSortOrderException extends RuntimeException { + public MenuInvalidSortOrderException() { + super(ErrorMessage.MENU_INVALID_SORT_ORDER.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuToggleUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuToggleUnauthorizedException.java new file mode 100644 index 00000000..9dea0004 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuToggleUnauthorizedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.menu.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class MenuToggleUnauthorizedException extends RuntimeException { + public MenuToggleUnauthorizedException() { + super(ErrorMessage.MENU_TOGGLE_UNAUTHORIZED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java index 54fd36a9..c26ac87e 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java @@ -4,6 +4,8 @@ import java.util.List; import com.nowait.domaincorerdb.base.entity.BaseTimeEntity; +import com.nowait.domaincorerdb.order.exception.InvalidOrderStatusTransitionException; +import com.nowait.domaincorerdb.order.exception.OrderAlreadyCancelledException; import com.nowait.domaincorerdb.store.entity.Store; import jakarta.persistence.CascadeType; @@ -62,14 +64,25 @@ public class UserOrder extends BaseTimeEntity { private Integer totalPrice; public void updateStatus(OrderStatus newStatus) { + if (!isValidTransition(this.status, newStatus)) { + throw new InvalidOrderStatusTransitionException(this.status, newStatus); + } this.status = newStatus; } public void cancelOrder() { if (this.status == OrderStatus.CANCELLED) { - return; + throw new OrderAlreadyCancelledException(); } this.status = OrderStatus.CANCELLED; } + private boolean isValidTransition(OrderStatus current, OrderStatus target) { + return switch (current) { + case WAITING_FOR_PAYMENT, COOKED -> target == OrderStatus.COOKING || target == OrderStatus.CANCELLED; + case COOKING -> target == OrderStatus.COOKED || target == OrderStatus.CANCELLED; + case CANCELLED -> false; + }; + } + } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/InvalidOrderStatusTransitionException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/InvalidOrderStatusTransitionException.java new file mode 100644 index 00000000..571c9b26 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/InvalidOrderStatusTransitionException.java @@ -0,0 +1,10 @@ +package com.nowait.domaincorerdb.order.exception; + +import com.nowait.common.exception.ErrorMessage; +import com.nowait.domaincorerdb.order.entity.OrderStatus; + +public class InvalidOrderStatusTransitionException extends RuntimeException { + public InvalidOrderStatusTransitionException(OrderStatus current, OrderStatus target) { + super(ErrorMessage.INVALID_ORDER_STATUS_TRANSITION.format(current, target)); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderAlreadyCancelledException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderAlreadyCancelledException.java new file mode 100644 index 00000000..2d650670 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderAlreadyCancelledException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.order.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class OrderAlreadyCancelledException extends RuntimeException { + public OrderAlreadyCancelledException() { + super(ErrorMessage.ORDER_ALREADY_CANCELLED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java index da5ec2bf..4874444e 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java @@ -3,6 +3,9 @@ import java.time.LocalDateTime; import com.nowait.common.enums.ReservationStatus; +import com.nowait.domaincorerdb.reservation.exception.InvalidReservationStatusTransitionException; +import com.nowait.domaincorerdb.reservation.exception.ReservationAlreadyCancelledException; +import com.nowait.domaincorerdb.reservation.exception.ReservationAlreadyConfirmedException; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.user.entity.User; @@ -59,8 +62,31 @@ public class Reservation { @Column(name = "party_size", nullable = false) private Integer partySize; - public void markUpdated(LocalDateTime ts, ReservationStatus status) { - this.status = status; - this.updatedAt = ts; + public void markUpdated(LocalDateTime updatedAt, ReservationStatus newStatus) { + if (this.status == newStatus) { + switch (newStatus) { + case CONFIRMED -> throw new ReservationAlreadyConfirmedException(); + case CANCELLED -> throw new ReservationAlreadyCancelledException(); + default -> {} + } + } + + if (!isValidTransition(this.status, newStatus)) { + throw new InvalidReservationStatusTransitionException(this.status, newStatus); + } + this.status = newStatus; + this.updatedAt = updatedAt; + } + + private boolean isValidTransition(ReservationStatus current, ReservationStatus target) { + return switch (current) { + case WAITING -> target == ReservationStatus.CALLING + || target == ReservationStatus.CONFIRMED + || target == ReservationStatus.CANCELLED; + case CALLING -> target == ReservationStatus.CONFIRMED + || target == ReservationStatus.CANCELLED; + case CONFIRMED -> target == ReservationStatus.CANCELLED; + case CANCELLED -> false; + }; } } diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationParameterException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationParameterException.java new file mode 100644 index 00000000..2db8dd4a --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationParameterException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class InvalidReservationParameterException extends RuntimeException { + public InvalidReservationParameterException(String details) { + super(ErrorMessage.INVALID_RESERVATION_PARAMETER.format(details)); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationStatusTransitionException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationStatusTransitionException.java new file mode 100644 index 00000000..cc6fe02c --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationStatusTransitionException.java @@ -0,0 +1,10 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.enums.ReservationStatus; +import com.nowait.common.exception.ErrorMessage; + +public class InvalidReservationStatusTransitionException extends RuntimeException { + public InvalidReservationStatusTransitionException(ReservationStatus current, ReservationStatus target) { + super(ErrorMessage.INVALID_RESERVATION_STATUS_TRANSITION.format(current, target)); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyCancelledException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyCancelledException.java new file mode 100644 index 00000000..1d2a7666 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyCancelledException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class ReservationAlreadyCancelledException extends RuntimeException { + public ReservationAlreadyCancelledException() { + super(ErrorMessage.RESERVATION_ALREADY_CANCELLED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyConfirmedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyConfirmedException.java new file mode 100644 index 00000000..724b1678 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyConfirmedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class ReservationAlreadyConfirmedException extends RuntimeException { + public ReservationAlreadyConfirmedException() { + super(ErrorMessage.RESERVATION_ALREADY_CONFIRMED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UnsupportedReservationStatusException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UnsupportedReservationStatusException.java new file mode 100644 index 00000000..98295756 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UnsupportedReservationStatusException.java @@ -0,0 +1,10 @@ +package com.nowait.domaincorerdb.reservation.exception; + +import com.nowait.common.enums.ReservationStatus; +import com.nowait.common.exception.ErrorMessage; + +public class UnsupportedReservationStatusException extends RuntimeException { + public UnsupportedReservationStatusException(ReservationStatus status) { + super(ErrorMessage.UNSUPPORTED_RESERVATION_STATUS.format(status)); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreKeywordEmptyException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreKeywordEmptyException.java deleted file mode 100644 index e69ad30b..00000000 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreKeywordEmptyException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.nowait.domaincorerdb.store.exception; - -import com.nowait.common.exception.ErrorMessage; - -public class StoreKeywordEmptyException extends RuntimeException { - public StoreKeywordEmptyException() { - super(ErrorMessage.STORE_PARAMETER_EMPTY.getMessage()); - } -} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentDeleteUnauthorizedException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentDeleteUnauthorizedException.java new file mode 100644 index 00000000..c65f5e55 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentDeleteUnauthorizedException.java @@ -0,0 +1,9 @@ +package com.nowait.domaincorerdb.storepayment.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class StorePaymentDeleteUnauthorizedException extends RuntimeException{ + public StorePaymentDeleteUnauthorizedException() { + super(ErrorMessage.STORE_PAYMENT_DELETE_UNAUTHORIZED.getMessage()); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java index aef89ab3..ec742363 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java @@ -4,6 +4,6 @@ public class UserNotFoundException extends RuntimeException { public UserNotFoundException() { - super(ErrorMessage.NOTFOUND_USER.getMessage()); + super(ErrorMessage.NOT_FOUND_USER.getMessage()); } } diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java index f40d5690..1f81d2f8 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java @@ -76,6 +76,10 @@ public static String buildReservationNumberKey(Long storeId) { return String.format("reservation:number:%d", storeId); } + public static String buildReservationUserKey(Long storeId) { + return String.format("reservation:user:%d", storeId); + } + /** * 대기 호출 시각(hash)에 사용할 키 접두사 */ diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/ReservationDataInconsistencyException.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/ReservationDataInconsistencyException.java new file mode 100644 index 00000000..22cc5151 --- /dev/null +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/ReservationDataInconsistencyException.java @@ -0,0 +1,10 @@ +package com.nowait.domaincoreredis.reservation.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class ReservationDataInconsistencyException extends RuntimeException { + public ReservationDataInconsistencyException(String details) { + super(ErrorMessage.RESERVATION_DATA_INCONSISTENCY.format(details)); + } +} + diff --git a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java index 0799786e..f9eb921b 100644 --- a/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java +++ b/nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java @@ -25,7 +25,6 @@ public List> getAllWaitingWithScore(Long store return waitingSet == null ? List.of() : new ArrayList<>(waitingSet); } - public List getAllWaitingUserIds(Long storeId) { String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; // 0부터 -1까지: 전체 범위 @@ -33,7 +32,6 @@ public List getAllWaitingUserIds(Long storeId) { return userIds == null ? List.of() : new ArrayList<>(userIds); } - // 상태값 저장 및 변경 public void setWaitingStatus(Long storeId, String userId, String status) { String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; @@ -54,7 +52,7 @@ public Integer getWaitingPartySize(Long storeId, String userId) { return value == null ? null : Integer.valueOf(value.toString()); } - // ReservationNumber 조회 + // userId → reservationNumber 조회 public String getReservationId(Long storeId, String userId) { String status = getWaitingStatus(storeId, userId); if (!"WAITING".equals(status) && !"CALLING".equals(status)) { @@ -67,7 +65,27 @@ public String getReservationId(Long storeId, String userId) { return val != null ? val.toString() : null; } + // reservationNumber → userId 조회 + public String getUserIdByReservationNumber(Long storeId, String reservationNumber) { + String userMapKey = RedisKeyUtils.buildReservationUserKey(storeId); + Object val = redisTemplate.opsForHash().get(userMapKey, reservationNumber); + return val == null ? null : val.toString(); + } + public void deleteWaiting(Long storeId, String userId) { + String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); + String userMapKey = RedisKeyUtils.buildReservationUserKey(storeId); + + Object reservationNumber = redisTemplate.opsForHash().get(numberMapKey, userId); + + // userId → reservationNumber 삭제 + redisTemplate.opsForHash().delete(numberMapKey, userId); + + // reservationNumber → userId 삭제 + if (reservationNumber != null) { + redisTemplate.opsForHash().delete(userMapKey, reservationNumber); + } + String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; redisTemplate.opsForHash().delete(statusKey, userId); @@ -77,11 +95,8 @@ public void deleteWaiting(Long storeId, String userId) { String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; redisTemplate.opsForHash().delete(partyKey, userId); - String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); - redisTemplate.opsForHash().delete(numberMapKey, userId); - - String key = RedisKeyUtils.buildWaitingCalledAtKeyPrefix() + storeId; - redisTemplate.opsForHash().delete(key, userId); + String calledAtKey = RedisKeyUtils.buildWaitingCalledAtKeyPrefix() + storeId; + redisTemplate.opsForHash().delete(calledAtKey, userId); } // 호출 시각 기록