-
Notifications
You must be signed in to change notification settings - Fork 0
fix(Reservation):예약 에러 수정 #160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "feature/#158_Store\uC608\uC57D\uB9CE\uC740\uC21C\uBC18\uD658\uD0C0\uC785\uAC1C\uC120"
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package com.nowait.applicationadmin.reservation.dto; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
|
|
||
| // DTO: 호출 시각(calledAt) 또는 완료 메시지(message)를 선택적으로 담습니다. | ||
| @Getter | ||
| @Builder | ||
| public class EntryStatusResponseDto { | ||
| @Schema(description = "예약 ID", example = "1201") | ||
| private String id; // reservationId | ||
|
|
||
| @Schema(description = "유저 ID", example = "16") | ||
| private String userId; | ||
|
|
||
| @Schema(description = "파티 인원", example = "3") | ||
| private Integer partySize; | ||
|
|
||
| @Schema(description = "사용자 이름(닉네임)", example = "혜민이") | ||
| private String userName; | ||
|
|
||
| @Schema(description = "대기 등록 시각", example = "2025-07-22T16:00:00") | ||
| private LocalDateTime createdAt; | ||
|
|
||
| @Schema(description = "대기 상태", example = "CALLING") | ||
| private String status; | ||
|
|
||
| @Schema(description = "대기 순번/점수", example = "2.0") | ||
| private Double score; | ||
|
|
||
| @Schema(description = "호출 메시지", example = "호출 메시지") | ||
| private String message; | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| package com.nowait.applicationadmin.reservation.service; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.time.LocalDateTime; | ||
| import java.time.LocalTime; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
|
|
@@ -11,6 +13,7 @@ | |
|
|
||
| import com.nowait.applicationadmin.reservation.dto.CallGetResponseDto; | ||
| import com.nowait.applicationadmin.reservation.dto.CallingWaitingResponseDto; | ||
| import com.nowait.applicationadmin.reservation.dto.EntryStatusResponseDto; | ||
| import com.nowait.applicationadmin.reservation.dto.ReservationGetResponseDto; | ||
| import com.nowait.applicationadmin.reservation.dto.ReservationStatusSummaryDto; | ||
| import com.nowait.applicationadmin.reservation.dto.ReservationStatusUpdateRequestDto; | ||
|
|
@@ -23,6 +26,7 @@ | |
| import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException; | ||
| import com.nowait.domaincorerdb.reservation.exception.ReservationViewUnauthorizedException; | ||
| import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; | ||
| import com.nowait.domaincorerdb.store.entity.Store; | ||
| import com.nowait.domaincorerdb.store.repository.StoreRepository; | ||
| import com.nowait.domaincorerdb.user.entity.MemberDetails; | ||
| import com.nowait.domaincorerdb.user.entity.User; | ||
|
|
@@ -132,59 +136,112 @@ public List<WaitingUserResponse> getCompletedWaitingUserDetails(Long storeId) { | |
| .toList(); | ||
| } | ||
|
|
||
| // 대기 객체 호출 (WAITING -> CALLING) | ||
| @Transactional | ||
| public CallingWaitingResponseDto callWaiting(Long storeId, String userId, MemberDetails memberDetails) { | ||
| User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); | ||
| if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(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())) { | ||
| throw new ReservationViewUnauthorizedException(); | ||
| } | ||
| String status = waitingRedisRepository.getWaitingStatus(storeId, userId); | ||
| if (!"WAITING".equals(status)) { | ||
| throw new IllegalStateException("이미 호출되었거나 없는 예약입니다."); | ||
| } | ||
| Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); | ||
| waitingRedisRepository.setWaitingStatus(storeId, userId, "CALLING"); | ||
| // [2] DB에 상태 영구 저장 (없으면 생성, 있으면 상태만 변경) | ||
| Reservation reservation = reservationRepository.findByStore_StoreIdAndUserId(storeId, Long.valueOf(userId)) | ||
| .orElse( | ||
| Reservation.builder() | ||
| .store(storeRepository.getReferenceById(storeId)) | ||
| .user(user) | ||
| .requestedAt(LocalDateTime.now()) | ||
| .partySize(partySize) | ||
| .build() | ||
| ); | ||
| reservation.updateStatus(ReservationStatus.CALLING); // setter 대신 빌더로 새 객체 or withStatus 패턴 추천 | ||
| reservationRepository.save(reservation); | ||
| return CallingWaitingResponseDto.builder() | ||
| .storeId(storeId) | ||
| .userId(userId) | ||
| .status(reservation.getStatus().name()) | ||
| .calledAt(reservation.getRequestedAt()) | ||
| .build(); | ||
| return u; | ||
| } | ||
|
|
||
| // 공통: 오늘 날짜 예약 조회 | ||
| private Reservation findTodayReservation(Long storeId, String userId) { | ||
| LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); | ||
| LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX); | ||
|
|
||
| return reservationRepository | ||
| .findByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetween( | ||
| storeId, | ||
| Long.valueOf(userId), | ||
| List.of(ReservationStatus.WAITING, ReservationStatus.CALLING), | ||
| startOfDay, | ||
| endOfDay | ||
| ) | ||
| .orElseThrow(() -> new IllegalArgumentException("오늘 날짜의 예약이 존재하지 않습니다.")); | ||
| } | ||
| // 대기 객체 상태 변경 | ||
|
|
||
| /** | ||
| * 상태를 하나의 메서드에서 처리합니다. | ||
| * - CALLING : Redis 상태를 CALLING으로 변경 → DB 저장 → calledAt 반환 | ||
| * - CONFIRMED : Redis에서 삭제 → DB 저장 → 완료 메시지 반환 | ||
| * - CANCELLED : Redis에서 삭제 → DB 저장 → 취소 메시지 반환 | ||
| */ | ||
| @Transactional | ||
| public String processEntryStatus(Long storeId, String userId, MemberDetails memberDetails, ReservationStatus status) { | ||
| // (권한 체크 필요시 여기에 추가) | ||
| User user = userRepository.findById(memberDetails.getId()) | ||
| .orElseThrow(UserNotFoundException::new); | ||
| if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) { | ||
| throw new ReservationViewUnauthorizedException(); | ||
| public EntryStatusResponseDto processEntryStatus( | ||
| Long storeId, | ||
| String userId, | ||
| MemberDetails member, | ||
| ReservationStatus newStatus | ||
| ) { | ||
| User manager = authorize(storeId, member); | ||
| User user = userRepository.findById(Long.valueOf(userId)).orElseThrow(UserNotFoundException::new); | ||
|
|
||
| String message = null; | ||
| Reservation reservation; | ||
|
|
||
| switch (newStatus) { | ||
| case CALLING: | ||
| // 1) Redis 상태 검사 & 변경 | ||
| String curr = waitingRedisRepository.getWaitingStatus(storeId, userId); | ||
| if (!ReservationStatus.WAITING.name().equals(curr)) { | ||
| throw new IllegalStateException("이미 호출되었거나 없는 예약입니다."); | ||
| } | ||
| waitingRedisRepository.setWaitingStatus(storeId, userId, ReservationStatus.CALLING.name()); | ||
|
|
||
| // 2) 파티 인원, 호출 시각 | ||
| Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); | ||
| LocalDateTime now = LocalDateTime.now(); | ||
|
|
||
| // 3) DB에 무조건 새로 저장 | ||
| Store store = storeRepository.getReferenceById(storeId); | ||
| reservation = Reservation.builder() | ||
| .store(store) | ||
| .user(user) | ||
| .partySize(partySize) | ||
| .requestedAt(now) | ||
| .status(ReservationStatus.CALLING) | ||
| .build(); | ||
| reservationRepository.save(reservation); | ||
|
|
||
| break; | ||
|
|
||
| case CONFIRMED: | ||
| case CANCELLED: | ||
| // 1) Redis에서 제거 | ||
| waitingRedisRepository.deleteWaiting(storeId, userId); | ||
|
|
||
| // 2) 오늘 날짜 예약 조회 & 상태 변경 | ||
| reservation = findTodayReservation(storeId, userId); | ||
| reservation.updateStatus(newStatus); | ||
| reservationRepository.save(reservation); | ||
|
|
||
| // 3) 완료/취소 메시지 | ||
| message = String.format( | ||
| "%s님의 예약이 %s 처리되었습니다.", | ||
| user.getNickname(), | ||
| newStatus == ReservationStatus.CONFIRMED ? "입장 완료" : "입장 취소" | ||
| ); | ||
| break; | ||
|
|
||
| default: | ||
| throw new IllegalArgumentException("지원하지 않는 상태입니다: " + newStatus); | ||
| } | ||
| // 1. DB status 업데이트 | ||
| Reservation reservation = reservationRepository.findByStore_StoreIdAndUserId(storeId, Long.valueOf(userId)) | ||
| .orElseThrow(() -> new IllegalArgumentException("해당 예약이 존재하지 않습니다.")); | ||
| reservation.updateStatus(status); | ||
| // 2. Redis에서 삭제 | ||
| waitingRedisRepository.deleteWaiting(storeId, userId); | ||
|
|
||
| // 메시지 동적 반환 | ||
| String action = (status == ReservationStatus.CONFIRMED) ? "입장 완료" : "입장 취소"; | ||
| return user.getNickname() + "님의 예약이 " + action + " 처리되었습니다."; | ||
|
|
||
| // 5) 공통 DTO 반환 | ||
| return EntryStatusResponseDto.builder() | ||
| .id( reservation.getId().toString()) | ||
| .userId( userId) | ||
| .partySize( reservation.getPartySize()) | ||
| .userName( user.getNickname()) | ||
| .createdAt( reservation.getRequestedAt()) | ||
| .status( reservation.getStatus().name()) | ||
| .message( message) | ||
| .build(); | ||
| } | ||
|
Comment on lines
+172
to
242
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 상태 처리 통합 메서드의 로직 검증 필요
다음과 같은 개선을 고려해보세요: case CALLING:
// 1) Redis 상태 검사 & 변경을 원자적으로 처리
- String curr = waitingRedisRepository.getWaitingStatus(storeId, userId);
- if (!ReservationStatus.WAITING.name().equals(curr)) {
- throw new IllegalStateException("이미 호출되었거나 없는 예약입니다.");
- }
- waitingRedisRepository.setWaitingStatus(storeId, userId, ReservationStatus.CALLING.name());
+ if (!waitingRedisRepository.compareAndSetWaitingStatus(storeId, userId,
+ ReservationStatus.WAITING.name(), ReservationStatus.CALLING.name())) {
+ throw new IllegalStateException("이미 호출되었거나 없는 예약입니다.");
+ }
🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
|
|
||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
당일 예약 조회 로직의 잠재적 문제
findTodayReservation메서드에서 리포지토리의findByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetween메서드를 사용하고 있는데, 이 메서드는Optional<Reservation>을 반환하므로 동일 조건의 복수 예약 존재 시NonUniqueResultException이 발생할 수 있습니다.또한 158라인의 상태 필터가
WAITING,CALLING으로 제한되어 있어, 다른 상태의 당일 예약은 조회되지 않습니다.리포지토리 메서드 수정이 필요하거나, 비즈니스 로직상 복수 예약이 가능하다면 다음과 같이 수정을 고려하세요:
🤖 Prompt for AI Agents