Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.eatsfine.eatsfine.domain.booking.entity.Booking;
import com.eatsfine.eatsfine.domain.booking.enums.BookingStatus;
import com.eatsfine.eatsfine.domain.store.entity.Store;
import com.eatsfine.eatsfine.domain.user.entity.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand Down Expand Up @@ -114,4 +115,18 @@ List<Long> findTableIdsWithFutureBookings(
@Param("currentDate") LocalDate currentDate,
@Param("currentTime") LocalTime currentTime
);
@Query("select count(b) from Booking b " +
"where b.store = :store " +
"and b.status in (com.eatsfine.eatsfine.domain.booking.enums.BookingStatus.CONFIRMED, " +
"com.eatsfine.eatsfine.domain.booking.enums.BookingStatus.PENDING, " +
"com.eatsfine.eatsfine.domain.booking.enums.BookingStatus.COMPLETED)")
Long countActiveBookings(@Param("store") Store store);
Comment on lines +118 to +123
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

rg -nP '\bcountActiveBookings\b' --type=java -C2

Repository: Eatsfine/BE

Length of output: 784


🏁 Script executed:

rg -nP '\bcountActiveBookingsByStores\b' --type=java -C2

Repository: Eatsfine/BE

Length of output: 1312


countActiveBookings 메서드는 사용되지 않으므로 제거해주세요.

코드베이스에서 이 메서드가 호출되는 곳이 없으며, StoreQueryServiceImpl에서는 countActiveBookingsByStores bulk 쿼리만 사용 중입니다. N+1 문제를 해결하기 위한 bulk 쿼리만 필요하므로 단건 메서드는 삭제하는 것이 좋습니다.

🤖 Prompt for AI Agents
In
`@src/main/java/com/eatsfine/eatsfine/domain/booking/repository/BookingRepository.java`
around lines 106 - 111, Remove the unused single-entity repository method
countActiveBookings from BookingRepository: delete the entire method declaration
(the `@Query` and Long countActiveBookings(`@Param`("store") Store store);) since
StoreQueryServiceImpl only uses the bulk method countActiveBookingsByStores;
ensure no other callers reference countActiveBookings and run tests/compile to
verify removal.


@Query("select b.store.id, count(b) from Booking b " +
"where b.store in :stores " +
"and b.status in (com.eatsfine.eatsfine.domain.booking.enums.BookingStatus.CONFIRMED, " +
"com.eatsfine.eatsfine.domain.booking.enums.BookingStatus.PENDING, " +
"com.eatsfine.eatsfine.domain.booking.enums.BookingStatus.COMPLETED) " +
"group by b.store.id")
List<Object[]> countActiveBookingsByStores(@Param("stores") List<Store> stores);
Comment on lines +118 to +131
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

상태 필터 조건의 중복 및 쿼리 스타일 불일치

두 가지 개선 포인트가 있습니다:

  1. 상태 필터 중복: CONFIRMED, PENDING, COMPLETED 조건이 두 메서드에서 동일하게 반복됩니다. 새 상태 추가 시 양쪽 모두 수정해야 하므로 유지보수 위험이 있습니다.

  2. 쿼리 스타일 불일치: 이 파일 내 기존 쿼리(Lines 29, 36, 44 등)는 문자열 리터럴('CONFIRMED')을, 새 쿼리는 FQN enum 참조를 사용합니다. FQN 방식이 더 타입 안전하지만, 파일 내 일관성을 위해 통일하는 것이 좋습니다. 가능하다면 기존 쿼리도 FQN 방식으로 통일하는 것을 권장합니다.

🤖 Prompt for AI Agents
In
`@src/main/java/com/eatsfine/eatsfine/domain/booking/repository/BookingRepository.java`
around lines 83 - 96, Both methods countActiveBookings and
countActiveBookingsByStores duplicate the same status filter and use a different
enum reference style than other queries; change both queries to accept a single
parameter (e.g., :statuses) of type
List<com.eatsfine.eatsfine.domain.booking.enums.BookingStatus> and use "b.status
in :statuses" to centralize the filter, and update other queries in the file to
use the fully-qualified BookingStatus enum
(com.eatsfine.eatsfine.domain.booking.enums.BookingStatus) for consistency so
adding a new status only requires updating the call site that builds the
statuses list rather than modifying multiple repository queries.

Comment on lines +125 to +131
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

List<Object[]> 대신 타입 안전한 반환 타입 사용을 권장합니다.

List<Object[]>는 인덱스 기반 접근이 필요하여 호출부에서 캐스팅 실수가 발생하기 쉽습니다. DTO 프로젝션이나 record를 사용하면 타입 안전성과 가독성이 향상됩니다.

♻️ 인터페이스 프로젝션 예시
public interface StoreBookingCount {
    Long getStoreId();
    Long getBookingCount();
}
-    `@Query`("select b.store.id, count(b) from Booking b " +
+    `@Query`("select b.store.id as storeId, count(b) as bookingCount from Booking b " +
             "where b.store in :stores " +
             "and b.status in (com.eatsfine.eatsfine.domain.booking.enums.BookingStatus.CONFIRMED, " +
             "com.eatsfine.eatsfine.domain.booking.enums.BookingStatus.PENDING, " +
             "com.eatsfine.eatsfine.domain.booking.enums.BookingStatus.COMPLETED) " +
             "group by b.store.id")
-    List<Object[]> countActiveBookingsByStores(`@Param`("stores") List<Store> stores);
+    List<StoreBookingCount> countActiveBookingsByStores(`@Param`("stores") List<Store> stores);
🤖 Prompt for AI Agents
In
`@src/main/java/com/eatsfine/eatsfine/domain/booking/repository/BookingRepository.java`
around lines 113 - 119, The repository method countActiveBookingsByStores
currently returns List<Object[]> which is error-prone; replace it with a
type-safe projection (either an interface or a record) such as StoreBookingCount
(with methods/getters getStoreId() and getBookingCount()) and change the
BookingRepository method signature to return List<StoreBookingCount>; update the
`@Query` to select the projected fields (store id and count) matching the
projection and keep the same parameter `@Param`("stores") List<Store> stores so
callers receive a typed result instead of Object[].

}
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,16 @@ public ApiResponse<StoreResDto.GetMainImageDto> getMainImage(
return ApiResponse.of(StoreSuccessStatus._STORE_MAIN_IMAGE_GET_SUCCESS, storeQueryService.getMainImage(storeId));
}

@Operation(
summary = "내 가게 리스트 조회",
description = "사장님이 등록한 모든 가게 리스트를 조회합니다."
)
@GetMapping("/stores/my")
@PreAuthorize("hasRole('OWNER')")
public ApiResponse<StoreResDto.MyStoreListDto> getMyStores(
@CurrentUser User user
) {
return ApiResponse.of(StoreSuccessStatus._MY_STORE_LIST_FOUND, storeQueryService.getMyStores(user.getUsername()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,24 @@ public static StoreResDto.GetMainImageDto toGetMainImageDto(Long storeId, String
.mainImageUrl(key)
.build();
}
}

public static StoreResDto.MyStoreDto toMyStoreDto(Store store, boolean isOpenNow, String mainImageUrl, Long totalBookingCount) {
return StoreResDto.MyStoreDto.builder()
.storeId(store.getId())
.storeName(store.getStoreName())
.address(store.getAddress())
.category(store.getCategory())
.rating(store.getRating())
.totalBookingCount(totalBookingCount)
.reviewCount(null) // 리뷰 도메인 구현 이후 추가 예정
.mainImageUrl(mainImageUrl)
.isOpenNow(isOpenNow)
.build();
}

public static StoreResDto.MyStoreListDto toMyStoreListDto(List<StoreResDto.MyStoreDto> stores) {
return StoreResDto.MyStoreListDto.builder()
.stores(stores)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.eatsfine.eatsfine.domain.store.enums.Category;
import com.eatsfine.eatsfine.domain.store.enums.DepositRate;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

import java.math.BigDecimal;
Expand Down Expand Up @@ -91,4 +92,35 @@ public record GetMainImageDto(
String mainImageUrl
) {}

// 내 가게 관리 리스트 응답
@Builder
@Schema(description = "사장님용 내 가게 관리 단건 DTO")
public record MyStoreDto(
@Schema(description = "가게 ID", example = "1")
Long storeId,
@Schema(description = "가게명", example = "더 플레이스 강남점")
String storeName,
@Schema(description = "가게 주소", example = "서울 강남구 테헤란로 123")
String address,
@Schema(description = "카테고리", example = "ITALIAN")
Category category,
@Schema(description = "평점", example = "4.8")
BigDecimal rating,
@Schema(description = "누적 총 예약 수", example = "1234")
Long totalBookingCount, // 총 예약 수
@Schema(description = "리뷰 개수", example = "256")
Long reviewCount,
@Schema(description = "대표 이미지 URL", example = "https://s3.amazonaws.com/thumb.jpg")
String mainImageUrl,
@Schema(description = "현재 영업 여부", example = "true")
boolean isOpenNow
){}

@Builder
@Schema(description = "사장님용 내 가게 관리 목록 응답")
public record MyStoreListDto(
@Schema(description = "소유한 가게 목록")
List<MyStoreDto> stores
){}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.eatsfine.eatsfine.domain.store.enums;

public enum Category {
KOREAN, CHINESE, JAPANESE, WESTERN, CAFE
KOREAN, CHINESE, JAPANESE, WESTERN, ITALIAN, CAFE
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.eatsfine.eatsfine.domain.store.repository;

import com.eatsfine.eatsfine.domain.store.entity.Store;
import com.eatsfine.eatsfine.domain.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;


Expand All @@ -19,4 +21,7 @@ public interface StoreRepository extends JpaRepository<Store, Long>, StoreReposi
""")
Optional<Store> findByIdWithMenus(@Param("id") Long id);

@Query("select s from Store s left join fetch s.businessHours where s.owner = :owner")
List<Store> findAllByOwner(User owner);

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ StoreResDto.StoreSearchResDto search(

boolean isOpenNow(Store store, LocalDateTime now);

StoreResDto.MyStoreListDto getMyStores(String username);

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.eatsfine.eatsfine.domain.store.service;

import com.eatsfine.eatsfine.domain.businesshours.entity.BusinessHours;
import com.eatsfine.eatsfine.domain.booking.repository.BookingRepository;
import com.eatsfine.eatsfine.domain.store.condition.StoreSearchCondition;
import com.eatsfine.eatsfine.domain.store.converter.StoreConverter;
import com.eatsfine.eatsfine.domain.store.dto.StoreResDto;
Expand All @@ -9,6 +10,10 @@
import com.eatsfine.eatsfine.domain.store.exception.StoreException;
import com.eatsfine.eatsfine.domain.store.repository.StoreRepository;
import com.eatsfine.eatsfine.domain.store.status.StoreErrorStatus;
import com.eatsfine.eatsfine.domain.user.entity.User;
import com.eatsfine.eatsfine.domain.user.exception.UserException;
import com.eatsfine.eatsfine.domain.user.repository.UserRepository;
import com.eatsfine.eatsfine.domain.user.status.UserErrorStatus;
import com.eatsfine.eatsfine.global.s3.S3Service;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
Expand All @@ -21,13 +26,17 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class StoreQueryServiceImpl implements StoreQueryService {

private final StoreRepository storeRepository;
private final BookingRepository bookingRepository;
private final UserRepository userRepository;
private final S3Service s3Service;

// 식당 검색
Expand Down Expand Up @@ -142,4 +151,37 @@ private boolean isEffectiveOpen(BusinessHours bh, LocalTime time, boolean isToda

return false; // 영업 시간 자체가 아님
}

// 내 가게 리스트 조회
@Override
public StoreResDto.MyStoreListDto getMyStores(String email) {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new UserException(UserErrorStatus.MEMBER_NOT_FOUND));

List<Store> myStores = storeRepository.findAllByOwner(user);

if(myStores.isEmpty()) {
return StoreConverter.toMyStoreListDto(List.of());
}
// N+1 문제 해결을 위한 Bulk Query 실행
List<Object[]> bookingCounts = bookingRepository.countActiveBookingsByStores(myStores);
Map<Long, Long> bookingCountMap = bookingCounts.stream()
.collect(Collectors.toMap(
row -> (Long) row[0],
row -> (Long) row[1]
));
Comment on lines +167 to +172
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

List<Object[]> 반환 타입의 타입 안전성 부족

bookingCounts의 각 원소를 (Long)으로 캐스팅하고 있는데, 이는 런타임에서만 검증됩니다. JPQL count() 결과가 Long을 보장하지만, 쿼리 변경 시 ClassCastException 위험이 있습니다. 타입 안전한 DTO projection(record 또는 인터페이스) 사용을 권장합니다.

♻️ 타입 안전한 projection 예시

BookingRepository에서:

public record StoreBookingCount(Long storeId, Long count) {}

`@Query`("select new com.eatsfine.eatsfine.domain.booking.repository.BookingRepository.StoreBookingCount(" +
       "b.store.id, count(b)) from Booking b " +
       "where b.store in :stores " +
       "and b.status in (...) " +
       "group by b.store.id")
List<StoreBookingCount> countActiveBookingsByStores(`@Param`("stores") List<Store> stores);

서비스에서:

Map<Long, Long> bookingCountMap = bookingCounts.stream()
        .collect(Collectors.toMap(StoreBookingCount::storeId, StoreBookingCount::count));
🤖 Prompt for AI Agents
In
`@src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreQueryServiceImpl.java`
around lines 129 - 134, Replace the raw List<Object[]> projection from
bookingRepository.countActiveBookingsByStores with a type-safe DTO projection
(e.g., a record or interface like StoreBookingCount) returned by the repository
method, update the repository method signature (countActiveBookingsByStores) to
return List<StoreBookingCount> and construct the projection in the JPQL/Query,
then update the service mapping that builds bookingCountMap to collect from
StoreBookingCount using its accessors (StoreBookingCount::storeId,
StoreBookingCount::count) instead of casting Object[] elements.


LocalDateTime now = LocalDateTime.now();

List<StoreResDto.MyStoreDto> storeDtos = myStores.stream()
.map(store -> {
boolean isOpen = isOpenNow(store, now);
Long totalBookingCount = bookingCountMap.getOrDefault(store.getId(), 0L);
String mainImageUrl = s3Service.toUrl(store.getMainImageKey());
return StoreConverter.toMyStoreDto(store, isOpen, mainImageUrl, totalBookingCount);
})
.toList();

return StoreConverter.toMyStoreListDto(storeDtos);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ public enum StoreSuccessStatus implements BaseCode {

_STORE_MAIN_IMAGE_UPLOAD_SUCCESS(HttpStatus.OK, "STORE2005", "성공적으로 가게 대표 이미지를 업로드했습니다."),

_STORE_MAIN_IMAGE_GET_SUCCESS(HttpStatus.OK, "STORE2005", "성공적으로 가게 대표 이미지를 조회했습니다.")
_STORE_MAIN_IMAGE_GET_SUCCESS(HttpStatus.OK, "STORE2006", "성공적으로 가게 대표 이미지를 조회했습니다."),

_MY_STORE_LIST_FOUND(HttpStatus.OK, "STORE2007", "성공적으로 내 가게 리스트를 조회했습니다.")
;


Expand Down