Skip to content

[FEAT] 예약 견적서 작성, 조회, 수정 api 구현#27

Merged
buzz0331 merged 17 commits into
developfrom
feat/#25-reservation-info
Sep 19, 2025
Merged

[FEAT] 예약 견적서 작성, 조회, 수정 api 구현#27
buzz0331 merged 17 commits into
developfrom
feat/#25-reservation-info

Conversation

@buzz0331

@buzz0331 buzz0331 commented Sep 19, 2025

Copy link
Copy Markdown
Contributor

#️⃣연관된 이슈

closes #25

📝작업 내용

기본 cru API이기 때문에 큰 문제는 없었습니다.
다만, 유효성 검증이 api 개발에 주요 고려사항이였습니다!

예약 견적서 작성

  • 사장님만 가능
  • 자기가 소유한 푸드트럭만 가능
  • 자기가 소유한 푸드트럭은 예약 불가

예약 조회

  • 사장님과 일반유저 모두 가능

예약 수정

  • 사장님과 일반 유저 모두 가능

리팩토링 내용

  1. Transactional 레이어 수정 (파사드 레벨로 변경)
  2. ReservationStatus에 요청을 의미하는 상수 추가
  3. ReservationInfo에서 address를 address, detailAddress로 분리 (address: 시/동/구, detailAddress: 상세 주소) => 이에 따라, 기존 예약 내역 조회 api에서 reservationInfo.getFullAddress() 메서드를 호출하여 전체 address를 반환하도록 수정했습니다.
  4. ReservationInfo에 reservation이 포함된 키워드 컬럼명 수정 (노션 더미데이터 쿼리에도 반영해두었습니다)
  5. 엔티티 소유 여부를 판단하는 검증 로직을 도메인 내부에서 동작하도록 캡슐화

스크린샷 (선택)

스크린샷 2025-09-19 오후 6 13 50 스크린샷 2025-09-19 오후 6 14 27 스크린샷 2025-09-19 오후 6 24 54

💬리뷰 요구사항(선택)

ReservationDate 객체에서 fromJson이 잘 구현되어 있어서 손쉽게 구현할 수 있었습니다~ shout out to 퐁퐁균

카톡에서 말씀 드렸던 것처럼 dev.yml 파일은 개발 동안에는 ddl-auto: create으로 두도록 하겠습니다!

Summary by CodeRabbit

  • New Features
    • 예약 생성/조회/수정 API를 추가했습니다.
    • 예약 상태에 ‘확정 요청’, ‘취소 요청’을 추가하고 문구를 개선했습니다.
    • 본인 소유 푸드트럭은 예약할 수 없도록 제한하고 관련 오류 응답을 제공합니다.
  • Refactor
    • 사용자·사장님 예약 내역/상세 응답에서 주소가 전체 주소로 표시되도록 변경했습니다.
  • Documentation
    • Swagger에 예약 API와 검증 규칙, 오류 케이스를 추가/보강했습니다.

@coderabbitai

coderabbitai Bot commented Sep 19, 2025

Copy link
Copy Markdown

Walkthrough

예약 견적서 생성/조회/수정 API를 추가하고, 예약/푸드트럭 소유 검증을 도메인 메서드로 위임했습니다. 예약 도메인과 값 객체(ReservationInfo, ReservationDateList)를 확장/리네이밍하고 상태 Enum을 보강했습니다. 멤버/오너 서비스의 트랜잭션 구성을 재조정했으며, 여러 응답 DTO의 주소 값을 full address로 변경했습니다.

Changes

Cohort / File(s) Summary
예약 API 추가
src/main/java/.../reservation/presentation/ReservationController.java, .../reservation/application/ReservationService.java, .../reservation/application/reservationinfo/ReservationInfoService.java, .../reservation/presentation/dto/request/CreateReservationRequest.java, .../reservation/presentation/dto/request/UpdateReservationRequest.java, .../reservation/presentation/dto/response/ReservationIdResponse.java, .../reservation/presentation/dto/response/ReservationResponse.java
예약 생성/조회/수정 엔드포인트, 서비스/도메인 매핑, DTO(요청/응답) 신설.
도메인 소유/권한 검증 위임
.../foodtruck/domain/FoodTruck.java, .../reservation/domain/model/Reservation.java, .../owner/application/myfoodtruck/MyFoodTruckService.java, .../owner/application/reservation/OwnerReservationService.java, .../member/application/reservation/MemberReservationService.java
서비스 레이어의 boolean 체크 제거, 도메인 메서드 validateOwner, validateFoodTruckOwner, validateReservedBy로 위임. Reservation에 create/update 추가.
예약 값 객체 리팩토링
.../reservation/domain/value/ReservationInfo.java, .../reservation/domain/value/ReservationDateList.java, .../reservation/domain/value/ReservationStatus.java
필드 리네이밍/추가(address/detailAddress, reservationDates, deposit), 빌더/업데이트 메서드/포맷터 추가, 날짜 리스트 접근자 추가, 상태 Enum 항목 및 라벨 보강.
응답 주소 필드 변경(FullAddress)
.../member/presentation/dto/response/MemberReservationDetailResponse.java, .../member/presentation/dto/response/MemberReservationHistoryResponse.java, .../member/presentation/dto/response/ReservationForRatingResponse.java, .../owner/presentation/dto/response/OwnerReservationDetailResponse.java, .../owner/presentation/dto/response/OwnerReservationHistoryResponse.java
주소 소스 변경: getReservationAddress()getFullAddress().
트랜잭션 구성 재조정
.../member/application/MemberService.java, .../member/application/foodtruck/SavedFoodTruckService.java, .../member/application/rating/RatingService.java
MemberService에 클래스 기본 readOnly 추가 및 일부 메서드에 쓰기 트랜잭션 지정. SavedFoodTruckService/RatingService의 트랜잭션 어노테이션 제거.
에러/스웨거 확장
.../global/common/exception/code/ErrorCode.java, .../global/common/swagger/SwaggerResponseDescription.java
에러코드 CANNOT_RESERVE_OWN_FOOD_TRUCK 추가. Swagger 응답 설명에 예약 3종(CREATE/GET/UPDATE) 추가.
개발 설정 변경
src/main/resources/application-dev.yml
Hibernate DDL Auto: updatecreate.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Owner as Owner(UserId)
  participant C as ReservationController
  participant S as ReservationService
  participant V1 as OwnerValidator
  participant V2 as MemberValidator
  participant I as ReservationInfoService
  participant F as FoodTruckRepository
  participant R as ReservationRepository

  Owner->>C: POST /reservations (CreateReservationRequest)
  C->>S: createReservation(request, ownerId)
  S->>V1: validate/find owner(ownerId)
  S->>V2: validate/find member(request.reservationUserId)
  S->>I: createReservation(request, owner, member)
  I->>F: findById(request.foodTruckId)
  I->>R: save(Reservation.create(...))
  R-->>I: reservationId
  I-->>S: reservationId
  S-->>C: ReservationIdResponse
  C-->>Owner: 200 OK
Loading
sequenceDiagram
  autonumber
  actor User as Member/Owner(UserId)
  participant C as ReservationController
  participant S as ReservationService
  participant V as MemberValidator
  participant I as ReservationInfoService
  participant R as ReservationRepository
  participant D as Reservation(domain)

  User->>C: GET /reservations/{id}
  C->>S: getReservation(id, userId)
  S->>V: validate/find user(userId)
  S->>I: getReservation(id, user)
  I->>R: findById(id)
  R-->>I: Reservation
  alt role == OWNER
    I->>D: validateFoodTruckOwner(userId)
  else role != OWNER
    I->>D: validateReservedBy(userId)
  end
  I-->>S: ReservationResponse.of(Reservation)
  S-->>C: ReservationResponse
  C-->>User: 200 OK
Loading
sequenceDiagram
  autonumber
  actor User as Member(UserId)
  participant C as ReservationController
  participant S as ReservationService
  participant V as MemberValidator
  participant I as ReservationInfoService
  participant R as ReservationRepository
  participant D as Reservation(domain)

  User->>C: PUT /reservations/{id} (UpdateReservationRequest)
  C->>S: updateReservation(id, request, userId)
  S->>V: validate/find user(userId)
  S->>I: updateReservation(id, request, user)
  I->>R: findById(id)
  R-->>I: Reservation
  opt access check
    I->>D: validateFoodTruckOwner(userId) / validateReservedBy(userId)
  end
  I->>D: update(...)
  I-->>S: reservationId
  S-->>C: ReservationIdResponse
  C-->>User: 200 OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • ksg1227

Poem

꼬깃꼬깃 양배추 메모에, 예약 흐름을 꾹꾹 써두고 🥕
주소는 쭉—상세까지, 한 줄로 이어 붙였지
도메인이 말하길: “주인은 나야!” 하고 깡총
컨트롤러는 점프, 서비스는 착지
새 상태 깃발 펄럭—확정, 취소도 대기 중
오늘도 토끼는 머지 버튼 앞에서, 콩콩콩!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.52% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 제목 '[FEAT] 예약 견적서 작성, 조회, 수정 api 구현'은 PR의 핵심 변경사항인 예약(CRUD 중 Create/Read/Update) API 구현을 명확히 요약하고 있으며 불필요한 파일 목록이나 이모지 없이 간결합니다. PR에 포함된 리팩터링 및 도메인 캡슐화 변경은 보조적 범위로 보이며 제목이 오해를 일으키지 않습니다. 따라서 제목 검사는 통과로 판단합니다.
Linked Issues Check ✅ Passed 연결된 이슈 [#25]의 주요 코딩 요구사항(예약 견적서 생성/조회/수정 API 구현, 사장 권한·소유 푸드트럭 검증, 본인 소유 푸드트럭 예약 금지, 입력 유효성 검증, 트랜잭션 레이어 리팩터링)은 컨트롤러·서비스·DTO·도메인 변화(Reservation.create·validate*, ReservationInfo 필드 분리·getFullAddress, ErrorCode 추가 등)를 통해 전반적으로 구현되어 있습니다. 조회·수정에 대한 권한 분기와 생성 시 소유자/회원 검증 로직이 도메인 레벨로 캡슐화되어 있어 요구사항을 충족하는 것으로 보입니다. 다만 ReservationDateList.fromJson의 파싱 동작과 경계 케이스 테스트 여부는 원본 코드만으로 확인이 불가하므로 병합 전 해당 부분의 동작 검증과 단위 테스트 추가를 권고합니다.
Out of Scope Changes Check ✅ Passed 제공된 PR 목표와 변경 요약을 대조한 결과, 대부분의 변경사항(트랜잭션 레이어 이동, 도메인 소유권 캡슐화, Reservation API/DTO/도메인 추가·수정, ReservationStatus 확장 등)은 PR 목적에 부합합니다. 특별히 범위 밖으로 보이는 기능적 변경은 발견되지 않았으나 개발용 설정 변경(application-dev.yml의 ddl-auto: create)은 의도된 개발 환경 설정으로 보이며 배포 영향이 없도록 주의가 필요합니다. 전반적으로 PR에는 목표 외의 불필요한 변경이 포함되어 있지 않습니다.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#25-reservation-info

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/konkuk/chacall/domain/reservation/domain/value/ReservationDateList.java (1)

50-53: 날짜 범위 유효성(start <= end) 검증 추가 필요

역전된 범위를 방치하면 이후 로직(포맷/필터)이 깨질 수 있습니다.

-            LocalDate start = parseDot(dates[0].trim());
-            LocalDate end   = parseDot(dates[1].trim());
-            list.add(new DateRange(start, end));
+            LocalDate start = parseDot(dates[0].trim());
+            LocalDate end   = parseDot(dates[1].trim());
+            if (end.isBefore(start)) {
+                throw new DomainRuleException(ErrorCode.INVALID_DATE_INPUT);
+            }
+            list.add(new DateRange(start, end));
🧹 Nitpick comments (21)
src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationHistoryResponse.java (1)

16-17: Swagger 예시 주소 갱신 제안

상세주소 포함 가능성을 반영해 예시를 업데이트하면 좋습니다.

- @Schema(description = "예약 주소", example = "서울 광진구 화양동")
+ @Schema(description = "예약 주소", example = "서울 광진구 화양동 123-4 1층")
src/main/java/konkuk/chacall/domain/member/presentation/dto/response/ReservationForRatingResponse.java (1)

26-27: Swagger 예시 업데이트 권장

상세주소 포함을 반영한 예시로 갱신해 주세요.

- @Schema(description = "예약 주소", example = "서울 광진구 화양동")
+ @Schema(description = "예약 주소", example = "서울 광진구 화양동 123-4 1층")
src/main/java/konkuk/chacall/domain/owner/application/reservation/OwnerReservationService.java (2)

68-75: 고객 조회 누락/빈 목록 처리 보강 필요

  • reservations가 빈 경우 쿼리 스킵 권장.
  • userRepository로부터 일부 회원이 누락되면 customerMap.get(...)가 null이 되어 NPE 가능성이 있습니다(OwnerReservationHistoryResponse.of에 null 전달).
 private Map<Long, User> getCustomerMap(List<Reservation> reservations) {
-    List<Long> customerIds = reservations.stream()
-            .map(reservation -> reservation.getMember().getUserId())
-            .toList();
-
-    return userRepository.findAllByUserIdInAndRoleAndStatus(customerIds, Role.MEMBER, BaseStatus.ACTIVE).stream()
-            .collect(Collectors.toMap(User::getUserId, user -> user));
+    List<Long> customerIds = reservations.stream()
+            .map(r -> r.getMember().getUserId())
+            .distinct()
+            .toList();
+    if (customerIds.isEmpty()) return Map.of();
+
+    Map<Long, User> map = userRepository
+            .findAllByUserIdInAndRoleAndStatus(customerIds, Role.MEMBER, BaseStatus.ACTIVE)
+            .stream()
+            .collect(Collectors.toMap(User::getUserId, u -> u));
+    return map;
 }
 
 private List<OwnerReservationHistoryResponse> mapToReservationHistory(List<Reservation> reservations, Map<Long, User> customerMap) {
     return reservations.stream()
             .map(reservation -> {
-                User customer = customerMap.get(reservation.getMember().getUserId());
+                User customer = customerMap.get(reservation.getMember().getUserId());
+                if (customer == null) {
+                    // 누락된 사용자는 마스킹/기본값 처리 또는 스킵
+                    throw new EntityNotFoundException(ErrorCode.USER_NOT_FOUND);
+                }
                 return OwnerReservationHistoryResponse.of(reservation, customer);
             })
             .toList();
 }

Also applies to: 80-86


13-15: 불필요한 import 제거

BusinessException가 더 이상 사용되지 않습니다.

-import konkuk.chacall.global.common.exception.BusinessException;
src/main/java/konkuk/chacall/domain/reservation/domain/value/ReservationDateList.java (3)

32-34: 불변 리스트 중복 래핑

of()에서 이미 List.copyOf로 불변을 보장하므로 Collections.unmodifiableList는 중복입니다. 직접 반환해도 됩니다.

-    public List<DateRange> getRanges() {
-        return Collections.unmodifiableList(ranges);
-    }
+    public List<DateRange> getRanges() {
+        return ranges;
+    }

28-30: Null 불가 보장 시 불필요한 null 체크 제거 제안

rangesof()에서 null이면 List.of()로 치환되어 null 아님이 보장됩니다. 간소화 가능.

-    public boolean isEmpty() {
-        return ranges == null || ranges.isEmpty();
-    }
+    public boolean isEmpty() {
+        return ranges.isEmpty();
+    }

8-8: 사용되지 않는 Lombok import

@Getter를 사용하지 않으므로 제거하세요.

-import lombok.Getter;
src/main/java/konkuk/chacall/domain/foodtruck/domain/FoodTruck.java (1)

78-86: 도메인 소유자 검증 도입 좋습니다

엔티티 스스로 규칙을 보장하게 되어 응집도가 높아졌습니다.

isOwnedBy의 가시성을 package-private로 열어 도메인 단위 테스트에서 직접 검증 가능하게 하는 것도 고려해 보세요.

-    private boolean isOwnedBy(Long ownerId) {
+    /* package-private */ boolean isOwnedBy(Long ownerId) {
         return this.owner.getUserId().equals(ownerId);
     }
src/main/java/konkuk/chacall/domain/member/application/reservation/MemberReservationService.java (1)

10-10: 불필요한 import 제거

BusinessException 미사용입니다.

-import konkuk.chacall.global.common.exception.BusinessException;
src/main/java/konkuk/chacall/domain/reservation/presentation/ReservationController.java (1)

43-54: 조회 API의 권한 검증 로직 개선 고려

예약 조회 시 사장님과 일반 유저를 구분하는 로직이 서비스 레이어에서 user.getRole()로 처리되고 있습니다. 그러나 컨트롤러에서는 모든 사용자를 userId로 받고 있어, 권한에 따른 처리가 명확하지 않습니다.

권한별로 다른 엔드포인트를 제공하거나, 최소한 OpenAPI 문서에 권한별 동작 차이를 명시하는 것을 고려해보세요:

 @Operation(
         summary = "예약 견적서 조회",
-        description = "사장님이 작성한 예약 견적서를 조회합니다. 사장님, 일반 유저 모두 조회 가능합니다."
+        description = "사장님이 작성한 예약 견적서를 조회합니다. 사장님은 본인 푸드트럭의 예약을, 일반 유저는 본인이 예약한 건만 조회 가능합니다."
 )
src/main/java/konkuk/chacall/domain/reservation/application/ReservationService.java (1)

38-43: updateReservation 메서드의 권한 검증 불일치 가능성

getReservation과 동일하게 memberValidator만 사용하여 검증하고 있어, Owner가 자신의 푸드트럭 예약을 수정하려고 할 때 문제가 발생할 수 있습니다.

Owner와 Member를 모두 처리할 수 있도록 수정이 필요합니다:

 @Transactional
 public Long updateReservation(Long reservationId, UpdateReservationRequest request, Long userId) {
-    User user = memberValidator.validateAndGetMember(userId);
+    // UserValidator 또는 UserRepository를 통해 Role에 관계없이 유저 조회
+    User user = userValidator.validateAndGetUser(userId);
 
     return reservationInfoService.updateReservation(reservationId, request, user);
 }
src/main/java/konkuk/chacall/domain/reservation/presentation/dto/response/ReservationResponse.java (1)

17-21: 정규식 패턴이 중복 정의되어 있음

reservationDates 필드의 검증 패턴이 여기서 정의되어 있는데, 이는 응답 DTO이므로 검증이 필요하지 않습니다. 요청 DTO에만 검증을 적용하는 것이 적절합니다.

응답 DTO에서는 검증 어노테이션을 제거하세요:

 @Schema(description = "예약 날짜 (형식: YYYY.MM.DD ~ YYYY.MM.DD)", example = "[\"2025.09.20 ~ 2025.09.20\", \"2025.09.25 ~ 2025.09.25\"]")
-List<@Pattern(
-        regexp = "^\\d{4}\\.\\d{2}\\.\\d{2} ~ \\d{4}\\.\\d{2}\\.\\d{2}$",
-        message = "예약 날짜 형식이 올바르지 않습니다. (예: 2025.09.20 ~ 2025.09.20)"
-) String> reservationDates,
+List<String> reservationDates,
src/main/java/konkuk/chacall/domain/reservation/domain/value/ReservationInfo.java (9)

15-19: Embeddable에 공개 AllArgsConstructor 노출은 남용 여지 — private로 제한 권고

값객체 무결성 보존 관점에서 생성 경로를 Builder로만 고정하는 편이 안전합니다.

-@AllArgsConstructor
+@AllArgsConstructor(access = lombok.AccessLevel.PRIVATE)

22-27: 주소 필드에 Bean Validation 추가 권고(@notblank)

DB 제약(null)만으로는 빈 문자열(" ")을 막지 못합니다. 입력단 유효성 일관성을 위해 @notblank를 권고합니다.

-@Column(nullable = false)
-private String address; // 주소 (시/동/구)
+@Column(nullable = false)
+@jakarta.validation.constraints.NotBlank
+private String address; // 주소 (시/구/동)
 
-@Column(nullable = false)
-private String detailAddress; // 상세 주소
+@Column(nullable = false)
+@jakarta.validation.constraints.NotBlank
+private String detailAddress; // 상세 주소

32-35: 운영시간을 문자열로 보관하는 대신 값객체화를 고려하세요

"HH:mm ~ HH:mm" 패턴 검증/정렬/비교가 빈번할 경우 OperationHourRange(시작/종료 LocalTime) 값객체가 안정적입니다.


38-40: 예약금 음수/널 방지: @NotNull, @min(0) 권고

도메인 규칙 강화 차원에서 Bean Validation 추가를 권고합니다.

-@Column(nullable = false)
-private Integer deposit; // 예약금
+@Column(nullable = false)
+@jakarta.validation.constraints.NotNull
+@jakarta.validation.constraints.Min(0)
+private Integer deposit; // 예약금

47-57: DateTimeFormatter 상수화 및 널/빈 목록 안전 처리

  • Formatter 재생성 비용/중복 제거
  • reservationDates null/empty일 때 빈 리스트 반환
-    public List<String> getFormattedDateTimeInfos() {
-
-        DateTimeFormatter DOT = DateTimeFormatter.ofPattern("yyyy.MM.dd");
-
-        return this.reservationDates.getRanges().stream()
+    private static final DateTimeFormatter DOT = DateTimeFormatter.ofPattern("yyyy.MM.dd");
+
+    public List<String> getFormattedDateTimeInfos() {
+        if (this.reservationDates == null || this.reservationDates.isEmpty()) return List.of();
+        return this.reservationDates.getRanges().stream()
                 .map(date -> date.startDate().format(DOT) + " ~ " + date.endDate().format(DOT)
                         + " " + this.operationHour)
                 .toList();
     }
 
-    public List<String> getFormattedDateInfos() {
-        DateTimeFormatter DOT = DateTimeFormatter.ofPattern("yyyy.MM.dd");
-
-        return this.reservationDates.getRanges().stream()
+    public List<String> getFormattedDateInfos() {
+        if (this.reservationDates == null || this.reservationDates.isEmpty()) return List.of();
+        return this.reservationDates.getRanges().stream()
                 .map(date -> date.startDate().format(DOT) + " ~ " + date.endDate().format(DOT))
                 .toList();
     }

Also applies to: 59-68


70-72: full address 조합 시 공백/널 안전 처리

detailAddress가 비거나 null이면 말줄임/이중 공백이 생깁니다. 간단히 정리하세요.

-    public String getFullAddress() {
-        return this.address + " " + this.detailAddress;
-    }
+    public String getFullAddress() {
+        String base = address == null ? "" : address.trim();
+        if (detailAddress == null || detailAddress.isBlank()) return base;
+        return base.isEmpty() ? detailAddress.trim() : base + " " + detailAddress.trim();
+    }

78-80: 예약금 포맷팅: 메서드명 개선 + 천단위 구분자

가독성을 위해 NumberFormat 사용과 메서드명 정리를 권장합니다.

-    public String parsingReservationDeposit() {
-        return deposit + "원";
-    }
+    public String formatDeposit() {
+        java.text.NumberFormat nf = java.text.NumberFormat.getInstance(java.util.Locale.KOREA);
+        return nf.format(deposit) + "원";
+    }

82-100: update 파라미터 네이밍 일관성(reservationDate → reservationDates) 및 기본 검증 권고

  • 필드명과 일치하도록 복수형으로 통일
  • 업데이트 경로에서도 널/음수 방지(Bean Validation로 1차 방어, 필요 시 도메인 예외 추가)
-    public void updateReservationInfo(
-            String reservationAddress,
-            String reservationDetailAddress,
-            ReservationDateList reservationDate,
-            String operationHour,
-            String menu,
-            Integer reservationDeposit,
-            boolean isUseElectricity,
-            String etcRequest
-    ) {
+    public void updateReservationInfo(
+            String reservationAddress,
+            String reservationDetailAddress,
+            ReservationDateList reservationDates,
+            String operationHour,
+            String menu,
+            Integer reservationDeposit,
+            boolean isUseElectricity,
+            String etcRequest
+    ) {
-        this.address = reservationAddress;
-        this.detailAddress = reservationDetailAddress;
-        this.reservationDates = reservationDate;
+        this.address = reservationAddress;
+        this.detailAddress = reservationDetailAddress;
+        this.reservationDates = reservationDates;
         this.operationHour = operationHour;
         this.menu = menu;
         this.deposit = reservationDeposit;
         this.isUseElectricity = isUseElectricity;
         this.etcRequest = etcRequest;
     }

74-76: 메서드명 오탈자 수정(및 도메인에서의 표현 로직 제거 권장)

parsingIsUserElectricity()의 "User"는 오타입니다 — 빠른 수정: 메서드명 formatUseElectricity()로 변경하고 호출부 2곳 업데이트. 권장: 도메인은 boolean 접근자(isUseElectricity())만 제공하고 DTO에서 '가능'/'불가능'으로 포맷하세요.
수정 필요 호출부: src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationDetailResponse.java:53, src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationDetailResponse.java:52.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c17cea and 71f3f1f.

📒 Files selected for processing (26)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/FoodTruck.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/member/application/MemberService.java (3 hunks)
  • src/main/java/konkuk/chacall/domain/member/application/foodtruck/SavedFoodTruckService.java (0 hunks)
  • src/main/java/konkuk/chacall/domain/member/application/rating/RatingService.java (0 hunks)
  • src/main/java/konkuk/chacall/domain/member/application/reservation/MemberReservationService.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationDetailResponse.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationHistoryResponse.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/member/presentation/dto/response/ReservationForRatingResponse.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/myfoodtruck/MyFoodTruckService.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/reservation/OwnerReservationService.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationDetailResponse.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationHistoryResponse.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/reservation/application/ReservationService.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/reservation/application/reservationinfo/ReservationInfoService.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/reservation/domain/model/Reservation.java (3 hunks)
  • src/main/java/konkuk/chacall/domain/reservation/domain/value/ReservationDateList.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/reservation/domain/value/ReservationInfo.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/reservation/domain/value/ReservationStatus.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/reservation/presentation/ReservationController.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/reservation/presentation/dto/request/CreateReservationRequest.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/reservation/presentation/dto/request/UpdateReservationRequest.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/reservation/presentation/dto/response/ReservationIdResponse.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/reservation/presentation/dto/response/ReservationResponse.java (1 hunks)
  • src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java (1 hunks)
  • src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java (2 hunks)
  • src/main/resources/application-dev.yml (1 hunks)
💤 Files with no reviewable changes (2)
  • src/main/java/konkuk/chacall/domain/member/application/rating/RatingService.java
  • src/main/java/konkuk/chacall/domain/member/application/foodtruck/SavedFoodTruckService.java
🧰 Additional context used
🧬 Code graph analysis (4)
src/main/java/konkuk/chacall/domain/member/application/MemberService.java (2)
src/main/java/konkuk/chacall/domain/reservation/application/ReservationService.java (1)
  • Service (14-44)
src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java (1)
  • RequiredArgsConstructor (19-139)
src/main/java/konkuk/chacall/domain/reservation/application/reservationinfo/ReservationInfoService.java (2)
src/main/java/konkuk/chacall/global/common/exception/EntityNotFoundException.java (1)
  • EntityNotFoundException (6-11)
src/main/java/konkuk/chacall/domain/reservation/application/ReservationService.java (1)
  • Service (14-44)
src/main/java/konkuk/chacall/domain/reservation/domain/value/ReservationInfo.java (2)
src/main/java/konkuk/chacall/domain/reservation/domain/value/ReservationDateList.java (1)
  • AllArgsConstructor (17-82)
src/main/java/konkuk/chacall/domain/reservation/domain/model/Reservation.java (1)
  • Getter (17-144)
src/main/java/konkuk/chacall/domain/reservation/application/ReservationService.java (2)
src/main/java/konkuk/chacall/domain/reservation/application/reservationinfo/ReservationInfoService.java (1)
  • Service (17-82)
src/main/java/konkuk/chacall/domain/member/application/MemberService.java (1)
  • Service (16-75)
🔇 Additional comments (31)
src/main/java/konkuk/chacall/domain/member/application/MemberService.java (5)

14-14: 필수 import 추가 승인

클래스 레벨에서 트랜잭션 관리를 위한 Transactional import가 적절히 추가되었습니다.


18-18: 읽기 전용 기본 트랜잭션 설정 승인

클래스 레벨에 @transactional(readOnly = true)를 설정하여 모든 메서드에 기본적으로 읽기 전용 트랜잭션을 적용하는 것은 적절한 패턴입니다. 메서드 레벨의 @transactional 어노테이션이 클래스 레벨 설정을 오버라이드할 수 있습니다.

이는 현재 PR 전반에 걸친 트랜잭션 관리 개선 작업과 일치하며, 다른 서비스(ReservationService, OwnerService 등)와 일관된 패턴을 유지합니다.


27-27: 쓰기 작업을 위한 트랜잭션 오버라이드 승인

클래스 레벨의 읽기 전용 설정을 메서드 레벨 @transactional로 오버라이드하여 쓰기 작업을 가능하게 하는 구현이 올바릅니다.


43-43: 쓰기 작업을 위한 트랜잭션 오버라이드 승인

평점 등록과 같은 쓰기 작업을 위해 @transactional로 클래스 레벨의 readOnly 설정을 적절히 오버라이드했습니다.


36-41: 읽기 전용 트랜잭션 컨텍스트에서 실행 승인

getSavedFoodTrucks 메서드는 클래스 레벨의 readOnly = true 트랜잭션 컨텍스트에서 실행되어 읽기 최적화가 적용됩니다.

src/main/resources/application-dev.yml (1)

13-13: 개발 환경 설정 확인

ddl-auto가 create로 변경되어 애플리케이션 시작 시 스키마가 초기화됩니다. 개발 데이터 손실 가능성을 인지하고 의도적인 변경인지 확인 부탁드립니다.

src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java (1)

59-59: LGTM!

새로운 에러 코드가 적절하게 추가되었습니다. 소유자가 본인 푸드트럭을 예약하는 것을 방지하는 비즈니스 규칙을 명확히 표현하고 있습니다.

src/main/java/konkuk/chacall/domain/reservation/domain/value/ReservationStatus.java (1)

10-16: 예약 상태 구조 개선

요청과 완료를 구분하는 상태 추가로 예약 생명주기를 더 세밀하게 관리할 수 있게 되었습니다. 상태 전이 순서도 논리적으로 잘 배치되어 있습니다.

src/main/java/konkuk/chacall/domain/reservation/presentation/dto/request/UpdateReservationRequest.java (1)

8-46: LGTM!

검증 애노테이션과 스키마 문서화가 잘 구성되어 있습니다. 특히 날짜 형식과 운영시간 형식에 대한 정규식 패턴이 명확하게 정의되어 있고, 에러 메시지도 사용자 친화적입니다.

src/main/java/konkuk/chacall/domain/reservation/presentation/dto/request/CreateReservationRequest.java (1)

8-54: LGTM!

검증 로직이 포괄적으로 구현되어 있습니다. 각 필드의 검증 규칙과 에러 메시지가 명확하며, API 문서화도 잘 되어 있습니다. 특히 날짜와 시간 형식에 대한 정규식 패턴이 정확합니다.

src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationDetailResponse.java (1)

48-48: 주소 정보 접근 방식 통일

getFullAddress() 메서드 사용으로 주소 정보 접근 방식이 통일되었습니다. ReservationInfo의 주소 필드 리팩토링과 일치하는 변경입니다.

src/main/java/konkuk/chacall/domain/owner/application/myfoodtruck/MyFoodTruckService.java (1)

58-58: 도메인 레벨 검증으로 캡슐화 개선

소유권 검증 로직이 도메인 객체로 이동하여 비즈니스 규칙이 적절한 위치에 캡슐화되었습니다. 도메인 주도 설계 원칙에 부합하는 좋은 리팩토링입니다.

src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationDetailResponse.java (1)

47-47: LGTM!

다른 응답 DTO들과 일관성 있게 getFullAddress() 메서드를 사용하도록 변경되었습니다. 코드베이스 전체의 주소 접근 방식이 통일되어 유지보수성이 향상되었습니다.

src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationHistoryResponse.java (2)

32-32: FullAddress 사용 변경 👍

도메인에 위임된 주소 포맷(getFullAddress) 사용 일관성 좋습니다.


16-17: Swagger 예시: 상세주소 포함으로 갱신 필요

상세주소(건물번호·호수 등)가 포함될 수 있으므로 @Schema의 example을 아래와 같이 업데이트하세요.

- @Schema(description = "예약 주소", example = "서울 광진구 화양동")
+ @Schema(description = "예약 주소", example = "서울 광진구 화양동 123-4 1층")

getFullAddress / getFormattedDateTimeInfos 구현 존재 여부와 "최대 2개" 제한 보장 여부를 확인해 주세요. (검색 스크립트 예시)

#!/bin/bash
rg -n -C3 --type java --no-ignore -S "getFullAddress\(" || true
rg -n -C3 --type java --no-ignore -S "getFormattedDateTimeInfos\(" || true
src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationHistoryResponse.java (1)

30-30: FullAddress 사용으로 변경된 점 OK

응답 일관성 측면에서 긍정적입니다.

src/main/java/konkuk/chacall/domain/member/presentation/dto/response/ReservationForRatingResponse.java (1)

41-41: FullAddress 사용 반영 👍

다른 DTO들과의 정합성 확보되었습니다.

src/main/java/konkuk/chacall/domain/owner/application/reservation/OwnerReservationService.java (2)

51-51: 소유자 검증을 도메인으로 위임한 점 Good

서비스 레이어 단순화와 규칙 응집이 좋아졌습니다. 예외 매핑(HTTP 상태/에러코드)이 기존과 동일하게 동작하는지 한 번만 확인 부탁드립니다.


61-63: 커서 페이지네이션 정렬 안정성 확인

findOwnerReservationsByStatusWithCursor가 일관된 정렬(예: reservation_id DESC)과 커서 조건을 함께 사용 중인지 확인 필요합니다. 불안정 정렬이면 중복/누락 가능성이 있습니다.

src/main/java/konkuk/chacall/domain/member/application/reservation/MemberReservationService.java (2)

43-43: 예약자 검증 도메인 위임 👍

규칙 집중화 OK. 트랜잭션이 파사드에서 열려 있는지(특히 LAZY 필드 접근 시)만 확인 부탁드립니다.


28-30: 커서 기반 조회의 정렬/커서 조건 점검

PageRequest.of(0, pageSize)와 커서 조건이 함께 안정된 정렬 기준으로 동작하는지 확인 필요합니다.

src/main/java/konkuk/chacall/domain/reservation/presentation/dto/response/ReservationIdResponse.java (1)

9-11: 단일 ID 응답 DTO 도입 👍

명세 명확해지고 추후 확장 용이합니다.

Jackson 설정(레코드 직렬화)이 이미 활성화되어 있는지 한번만 확인해 주세요.

src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java (1)

127-147: 예약 API 에러 코드 정의 확인

새로 추가된 예약 관련 Swagger 응답 설명이 적절하게 정의되어 있습니다. 각 API별로 발생 가능한 에러 코드들이 잘 매핑되어 있습니다.

src/main/java/konkuk/chacall/domain/reservation/presentation/dto/response/ReservationResponse.java (1)

38-50: of 메서드 구현이 적절함

Reservation 엔티티를 응답 DTO로 변환하는 팩토리 메서드가 잘 구현되어 있습니다. ReservationInfo의 각 필드를 적절히 매핑하고 있습니다.

src/main/java/konkuk/chacall/domain/reservation/domain/model/Reservation.java (3)

80-114: create 메서드 구현이 잘 되어 있음

예약 생성 팩토리 메서드가 적절하게 구현되어 있습니다. 유효성 검증, ReservationInfo 생성, 초기 상태 설정 등이 모두 적절합니다.


123-143: update 메서드 구현이 적절함

예약 정보 수정 메서드가 ReservationInfo에 위임하여 적절하게 구현되어 있습니다.


116-121: validateCreateReservation 검증 로직 — FoodTruck.validateOwner 구현 확인 필요

validateCreateReservation은 소유자 검증과 본인 예약 차단을 적절히 구현하고 있습니다. 다만 ast-grep 실행 결과 validateOwner 구현을 찾지 못해(출력 없음) 해당 메서드의 ID 비교 방식·null 처리·예외 던짐 여부를 직접 확인해 주세요. 확인용 명령(권장): rg -n --hidden -S '\bvalidateOwner\b' || rg -n --hidden -S 'class\s+FoodTruck'

src/main/java/konkuk/chacall/domain/reservation/application/ReservationService.java (1)

32-36: 권한 검증 불일치: Member vs Owner 확인 필요
ReservationService#getReservation이 memberValidator.validateAndGetMember(userId)로 Member만 검증합니다. ReservationInfoService가 Owner 권한도 처리한다면 권한 불일치 또는 권한 누락이 발생할 수 있으니 MemberValidator/OwnerValidator 구현을 확인하고 getReservation에 적절한 권한 분기 또는 Owner 허용을 적용하세요. 검증 스크립트가 출력되지 않아 Validator 구현을 확인할 수 없습니다.

src/main/java/konkuk/chacall/domain/reservation/presentation/ReservationController.java (1)

28-41: 컨트롤러에서 Owner 권한 검증 누락 — 서비스에서 검증 중 (검증 완료)

결과: @userid는 UserIdArgumentResolver로 주입(WebConfig에 등록)되며 ReservationController는 ownerId만 주입하고 컨트롤러 레벨의 역할(Owner) 검증은 없습니다. 권한 검증은 ReservationService.createReservation에서 OwnerValidator로 수행됩니다.

권고: 즉시 거부를 원하면 컨트롤러에 역할 검사(@PreAuthorize 또는 별도 체크) 추가하거나, 현재 서비스에서 던지는 권한 예외가 403 등으로 적절히 매핑되는지 확인하십시오.

참고 파일: src/main/java/konkuk/chacall/domain/reservation/presentation/ReservationController.java, src/main/java/konkuk/chacall/global/common/security/resolver/UserIdArgumentResolver.java, src/main/java/konkuk/chacall/global/config/WebConfig.java, src/main/java/konkuk/chacall/domain/reservation/application/ReservationService.java, src/main/java/konkuk/chacall/domain/owner/application/validator/OwnerValidator.java

src/main/java/konkuk/chacall/domain/reservation/domain/value/ReservationInfo.java (2)

28-31: 확인: ReservationDateListConverter가 빈 문자열/공백을 안전히 처리함

convertToEntityAttribute에서 dbData == null || dbData.isBlank()일 때 ReservationDateList.of(List.of())를 반환하고, convertToDatabaseColumn은 attribute == null || attribute.isEmpty()일 때 빈 문자열("")을 반환합니다. 빈 문자열/공백으로 인한 NPE나 역직렬화 문제 없음.


53-56: 확인 완료 — Java 17(toolchain) 사용 중이므로 Stream.toList() 안전
build.gradle에 JavaLanguageVersion.of(17)으로 toolchain이 설정되어 있어 toList() (JDK16+) 사용에 문제가 없습니다.

Comment on lines +44 to +56
public ReservationResponse getReservation(Long reservationId, User user) {
Reservation reservation = reservationRepository.findById(reservationId)
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.RESERVATION_NOT_FOUND));

// 예약 소유자 또는 푸드트럭 소유자인지 확인
if(user.getRole().equals(Role.OWNER)) {
reservation.validateFoodTruckOwner(user.getUserId());
} else {
reservation.validateReservedBy(user.getUserId());
}

return ReservationResponse.of(reservation);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

권한 검증 로직의 일관성 개선 필요

getReservation 메서드에서 Owner와 Member를 구분하는 로직이 있지만, Owner가 예약자로서 조회하려는 경우를 제대로 처리하지 못할 수 있습니다. Owner가 다른 사장님의 푸드트럭을 예약한 경우, validateFoodTruckOwner에서 실패할 가능성이 있습니다.

다음과 같이 수정하여 Owner가 예약자로서도 조회할 수 있도록 개선하세요:

 // 예약 소유자 또는 푸드트럭 소유자인지 확인
 if(user.getRole().equals(Role.OWNER)) {
-    reservation.validateFoodTruckOwner(user.getUserId());
+    // Owner가 푸드트럭 소유자이거나 예약자인 경우 모두 허용
+    try {
+        reservation.validateFoodTruckOwner(user.getUserId());
+    } catch (DomainRuleException e) {
+        reservation.validateReservedBy(user.getUserId());
+    }
 } else {
     reservation.validateReservedBy(user.getUserId());
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public ReservationResponse getReservation(Long reservationId, User user) {
Reservation reservation = reservationRepository.findById(reservationId)
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.RESERVATION_NOT_FOUND));
// 예약 소유자 또는 푸드트럭 소유자인지 확인
if(user.getRole().equals(Role.OWNER)) {
reservation.validateFoodTruckOwner(user.getUserId());
} else {
reservation.validateReservedBy(user.getUserId());
}
return ReservationResponse.of(reservation);
}
public ReservationResponse getReservation(Long reservationId, User user) {
Reservation reservation = reservationRepository.findById(reservationId)
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.RESERVATION_NOT_FOUND));
// 예약 소유자 또는 푸드트럭 소유자인지 확인
if(user.getRole().equals(Role.OWNER)) {
// Owner가 푸드트럭 소유자이거나 예약자인 경우 모두 허용
try {
reservation.validateFoodTruckOwner(user.getUserId());
} catch (DomainRuleException e) {
reservation.validateReservedBy(user.getUserId());
}
} else {
reservation.validateReservedBy(user.getUserId());
}
return ReservationResponse.of(reservation);
}
🤖 Prompt for AI Agents
In
src/main/java/konkuk/chacall/domain/reservation/application/reservationinfo/ReservationInfoService.java
around lines 44-56, the current role check only validates Owner as the foodtruck
owner which blocks Owners who made a reservation on another owner's truck.
Change the logic so Owners can access a reservation if they are either the
reservation maker or the foodtruck owner: for an Owner user, first check if they
are the reserving user and call validateReservedBy if so, otherwise call
validateFoodTruckOwner; for non-Owner users keep calling validateReservedBy. Use
the existing validation methods (validateReservedBy and validateFoodTruckOwner)
accordingly.

Comment on lines +58 to +81
public Long updateReservation(Long reservationId, UpdateReservationRequest request, User user) {
Reservation reservation = reservationRepository.findById(reservationId)
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.RESERVATION_NOT_FOUND));

// 예약 소유자 또는 푸드트럭 소유자인지 확인
if(user.getRole().equals(Role.OWNER)) {
reservation.validateFoodTruckOwner(user.getUserId());
} else {
reservation.validateReservedBy(user.getUserId());
}

reservation.update(
request.address(),
request.detailAddress(),
request.reservationDates(),
request.operationHour(),
request.menu(),
request.deposit(),
request.isUseElectricity(),
request.etcRequest()
);

return reservation.getReservationId();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

updateReservation 메서드의 권한 검증 로직 중복

getReservation과 동일한 권한 검증 로직이 중복되어 있습니다. 또한 동일한 문제점을 가지고 있습니다.

권한 검증 로직을 별도 메서드로 추출하여 재사용하세요:

+private void validateReservationAccess(Reservation reservation, User user) {
+    if(user.getRole().equals(Role.OWNER)) {
+        // Owner가 푸드트럭 소유자이거나 예약자인 경우 모두 허용
+        try {
+            reservation.validateFoodTruckOwner(user.getUserId());
+        } catch (DomainRuleException e) {
+            reservation.validateReservedBy(user.getUserId());
+        }
+    } else {
+        reservation.validateReservedBy(user.getUserId());
+    }
+}

 public ReservationResponse getReservation(Long reservationId, User user) {
     Reservation reservation = reservationRepository.findById(reservationId)
             .orElseThrow(() -> new EntityNotFoundException(ErrorCode.RESERVATION_NOT_FOUND));
 
-    // 예약 소유자 또는 푸드트럭 소유자인지 확인
-    if(user.getRole().equals(Role.OWNER)) {
-        reservation.validateFoodTruckOwner(user.getUserId());
-    } else {
-        reservation.validateReservedBy(user.getUserId());
-    }
+    validateReservationAccess(reservation, user);
 
     return ReservationResponse.of(reservation);
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
src/main/java/konkuk/chacall/domain/reservation/application/reservationinfo/ReservationInfoService.java
around lines 58-81, the role-based permission check in updateReservation is
duplicated with getReservation; extract this logic into a single private helper
(e.g., validateReservationAccess(Reservation reservation, User user)) and call
it from both getReservation and updateReservation. The helper should check
user.getRole() and, for OWNER, call
reservation.validateFoodTruckOwner(user.getUserId()), otherwise call
reservation.validateReservedBy(user.getUserId()); then replace the duplicated
block in updateReservation with a call to the new helper and ensure imports and
visibility are correct.

@ksg1227 ksg1227 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

야무지네요 👍🏻
거의 도메인 역할 분담에 달인이 다 되셨슈
세부 기능들은 사실상 완벽한 것 같고, 궁금한 부분이랑 한 가지 개선해보면 어떨까 싶은 부분 리뷰 담겼으니 확인부탁드림다~

Comment on lines +49 to +54
// 본인이 푸드트럭 소유자인지 검증
public void validateFoodTruckOwner(Long ownerId) {
if (isForFoodTruckOwnedBy(ownerId)) {
throw new DomainRuleException(RESERVATION_NOT_OWNED);
}
}

@ksg1227 ksg1227 Sep 19, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

p2 : 이 부분은 개인적으로 코드래빗 리뷰처럼 코드의 의미가 살짝 모호하다는 느낌이 드는 것 같습니다!

isForFoodTruckOwnedBy -> 푸드트럭을 소유했다면 true 반환 / 소유하지 않았다면 false 반환 이런 형식의 반환값을 가질 것이라 예측되는데, 실제로는 푸드트럭을 소유한 경우 false 반환 / 소유하지 않은 경우 true 를 반환하는 것 같아서

private boolean isForFoodTruckOwnedBy(Long ownerId) {
    return this.foodTruck.getOwner().getUserId().equals(ownerId);
}

...

public void validateFoodTruckOwner(Long ownerId) {
    if (!isForFoodTruckOwnedBy(ownerId)) { <- 여기에 ! 붙이기
        throw new DomainRuleException(RESERVATION_NOT_OWNED);
    }
}

위와 같은 형태로 구현해두는 것이 메서드 이름의 의미상 좀 더 명확하지 않나 생각합니다!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

맞네요! 리팩토링하면서 놓쳤던 것 같습니다! 굿굿

Comment on lines +48 to +53
// 예약 소유자 또는 푸드트럭 소유자인지 확인
if(user.getRole().equals(Role.OWNER)) {
reservation.validateFoodTruckOwner(user.getUserId());
} else {
reservation.validateReservedBy(user.getUserId());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

.reservationId(null)
.reservationStatus(ReservationStatus.PENDING) // 기본 상태: 예약 대기
.reservationInfo(reservationInfo)
.pdfUrl(null)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

제 기억으로는 예약 객체를 생성하는 시점은 예약 견적서가 발급되는 시점인 것 같은데 맞나요?
그렇다면 이 때에는 pdf 가 생성되지 않은 채로 예약 객체가 생성되는 것인지 궁금합니다!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

일단 지금 생각은 처음에는 pdfUrl을 null로 두고 처음 다운로드가 일어나면 그때부터 pdfUrl을 주입할 생각이긴 했습니다!

제가 생각한 pdf 다운로드 API 흐름은 다음과 같습니다.

  • pdfUrl = null이라면, html을 pdf로 변환 -> s3에 pdf 업로드 -> pdfUrl을 테이블에 업데이트 -> pdfUrl 반환
  • pdfUrl != null이라면, pdfUrl 반환

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

음 근데 생각해보니까 예약 수정 같은 api가 호출되면 그때마다 pdf를 갱신해줘야겠네요.. 그러면 pdfUrl이 null이 아니더라도 매번 pdf를 변환해서 s3에 업로드해야하는 이슈가 발생하겠네요..

그러면.. 예약 견적서 작성 또는 수정시에 pdf까지 변환해서 s3에 업로드를 해놔야할까요?? 살짝 헷갈리네요

@ksg1227 ksg1227 Sep 19, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

일단 지금 생각은 처음에는 pdfUrl을 null로 두고 처음 다운로드가 일어나면 그때부터 pdfUrl을 주입할 생각이긴 했습니다!

제가 생각한 pdf 다운로드 API 흐름은 다음과 같습니다.

  • pdfUrl = null이라면, html을 pdf로 변환 -> s3에 pdf 업로드 -> pdfUrl을 테이블에 업데이트 -> pdfUrl 반환

  • pdfUrl != null이라면, pdfUrl 반환

이 형태라면 조회시에 pdf를 업로드 하는 로직이 포함될텐데, 그렇다면 최초 조회 성능이 약간 떨어질 것 같다는 단점이 있을 것 같긴 합니다!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

음 근데 생각해보니까 예약 수정 같은 api가 호출되면 그때마다 pdf를 갱신해줘야겠네요.. 그러면 pdfUrl이 null이 아니더라도 매번 pdf를 변환해서 s3에 업로드해야하는 이슈가 발생하겠네요..

그러면.. 예약 견적서 작성 또는 수정시에 pdf까지 변환해서 s3에 업로드를 해놔야할까요?? 살짝 헷갈리네요

수정시에는 기존 pdf url 기반으로 s3에 존재하던 기존 pdf 삭제 + 수정된 정보를 기반으로 새 pdf 업로드 후 db 업데이트

생성시에도 pdf 업로드

이런 흐름이 되어야하지 않을까 싶긴 하네요

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

음 그렇다면 우선 현재는 pdf를 null로 두고 추후에 pdf 다운로드 api를 구현할때 s3 연동 작업 및 업로드 작업을 추가해도 괜찮을까요??

Comment on lines +116 to +121
private static void validateCreateReservation(User owner, User member, FoodTruck foodTruck) {
foodTruck.validateOwner(owner.getUserId());
if (foodTruck.getOwner().getUserId().equals(member.getUserId())) {
throw new DomainRuleException(CANNOT_RESERVE_OWN_FOOD_TRUCK);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

팩토리 메서드에서 사용하기 위한 검증 로직 좋네유 👍🏻

Comment on lines +32 to +34
public List<DateRange> getRanges() {
return Collections.unmodifiableList(ranges);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@buzz0331 buzz0331 merged commit cf5476e into develop Sep 19, 2025
1 check passed
@buzz0331 buzz0331 deleted the feat/#25-reservation-info branch September 19, 2025 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 예약견적서 작성, 조회, 수정 api

2 participants