From 1b7a755107b2d8d2364569c3f70d2b6dc9040cf1 Mon Sep 17 00:00:00 2001 From: oroi2009 Date: Fri, 15 Aug 2025 23:04:55 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20refactor=20:=20Menu?= =?UTF-8?q?=20=EA=B3=B5=ED=86=B5=20DTO=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/menu/web/dto/MenuDetailsRes.java | 22 ++--------- .../domain/menu/web/dto/MenuItemRes.java | 39 +++++++++++++++++++ .../domain/store/service/StoreService.java | 2 + .../store/service/StoreServiceImpl.java | 6 +++ .../store/web/controller/StoreController.java | 18 +++++++-- .../domain/store/web/dto/StoreMenusRes.java | 24 ++++++++++++ 6 files changed, 88 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/example/Centralthon/domain/menu/web/dto/MenuItemRes.java create mode 100644 src/main/java/com/example/Centralthon/domain/store/web/dto/StoreMenusRes.java diff --git a/src/main/java/com/example/Centralthon/domain/menu/web/dto/MenuDetailsRes.java b/src/main/java/com/example/Centralthon/domain/menu/web/dto/MenuDetailsRes.java index 636c51a..3b29aea 100644 --- a/src/main/java/com/example/Centralthon/domain/menu/web/dto/MenuDetailsRes.java +++ b/src/main/java/com/example/Centralthon/domain/menu/web/dto/MenuDetailsRes.java @@ -2,35 +2,19 @@ import com.example.Centralthon.domain.menu.entity.Menu; import com.example.Centralthon.domain.menu.entity.enums.MenuCategory; +import com.fasterxml.jackson.annotation.JsonUnwrapped; import java.math.BigDecimal; import java.math.RoundingMode; public record MenuDetailsRes( - long menuId, - String name, - MenuCategory category, - int costPrice, - int salePrice, - int salePercent, + @JsonUnwrapped MenuItemRes item, String storeName ) { public static MenuDetailsRes from(Menu menu) { return new MenuDetailsRes( - menu.getId(), - menu.getName(), - menu.getCategory(), - menu.getCostPrice(), - menu.getSalePrice(), - calculateDiscount(menu.getCostPrice(), menu.getSalePrice()), + MenuItemRes.from(menu), menu.getStore().getName() ); } - - private static int calculateDiscount(int cost, int sale) { - return BigDecimal.valueOf(cost - sale) - .multiply(BigDecimal.valueOf(100)) - .divide(BigDecimal.valueOf(cost), 0, RoundingMode.HALF_UP) - .intValue(); - } } diff --git a/src/main/java/com/example/Centralthon/domain/menu/web/dto/MenuItemRes.java b/src/main/java/com/example/Centralthon/domain/menu/web/dto/MenuItemRes.java new file mode 100644 index 0000000..bdb0974 --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/menu/web/dto/MenuItemRes.java @@ -0,0 +1,39 @@ +package com.example.Centralthon.domain.menu.web.dto; + +import com.example.Centralthon.domain.menu.entity.Menu; +import com.example.Centralthon.domain.menu.entity.enums.MenuCategory; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +// 하위 공통 DTO +public record MenuItemRes( + Long menuId, + String name, + MenuCategory category, // 메뉴 카테고리 (예: 볶음) + int costPrice, + int salePrice, + int salePercent, // (costPrice - salePrice) * 100 / costPrice + int quantity) { + + public static MenuItemRes from(Menu menu) { + return new MenuItemRes( + menu.getId(), + menu.getName(), + menu.getCategory(), + menu.getCostPrice(), + menu.getSalePrice(), + calculateDiscount(menu.getCostPrice(), menu.getSalePrice()), + menu.getQuantity() + ); + } + + private static int calculateDiscount(int cost, int sale) { + return BigDecimal.valueOf(cost - sale) + .multiply(BigDecimal.valueOf(100)) + .divide(BigDecimal.valueOf(cost), 0, RoundingMode.HALF_UP) + .intValue(); + } +} + + diff --git a/src/main/java/com/example/Centralthon/domain/store/service/StoreService.java b/src/main/java/com/example/Centralthon/domain/store/service/StoreService.java index b8f59cb..801e2df 100644 --- a/src/main/java/com/example/Centralthon/domain/store/service/StoreService.java +++ b/src/main/java/com/example/Centralthon/domain/store/service/StoreService.java @@ -1,10 +1,12 @@ package com.example.Centralthon.domain.store.service; import com.example.Centralthon.domain.store.web.dto.NearbyStoresRes; +import com.example.Centralthon.domain.store.web.dto.StoreMenusRes; import java.util.List; public interface StoreService { List nearbyStores(double lat, double lng); + StoreMenusRes getStoreMenus(Long storeId); } diff --git a/src/main/java/com/example/Centralthon/domain/store/service/StoreServiceImpl.java b/src/main/java/com/example/Centralthon/domain/store/service/StoreServiceImpl.java index 7ffd7ad..f59d967 100644 --- a/src/main/java/com/example/Centralthon/domain/store/service/StoreServiceImpl.java +++ b/src/main/java/com/example/Centralthon/domain/store/service/StoreServiceImpl.java @@ -3,6 +3,7 @@ import com.example.Centralthon.domain.store.entity.Store; import com.example.Centralthon.domain.store.repository.StoreRepository; import com.example.Centralthon.domain.store.web.dto.NearbyStoresRes; +import com.example.Centralthon.domain.store.web.dto.StoreMenusRes; import com.example.Centralthon.global.util.geo.BoundingBox; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -33,4 +34,9 @@ public List nearbyStores(double lat, double lng) { .map(store -> NearbyStoresRes.from(store)) .toList(); } + + @Override + public StoreMenusRes getStoreMenus(Long storeId) { + return null; + } } diff --git a/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreController.java b/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreController.java index e0031d7..a9bf48b 100644 --- a/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreController.java +++ b/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreController.java @@ -2,14 +2,13 @@ import com.example.Centralthon.domain.store.service.StoreService; import com.example.Centralthon.domain.store.web.dto.NearbyStoresRes; +import com.example.Centralthon.domain.store.web.dto.StoreMenusRes; import com.example.Centralthon.global.response.SuccessResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + import java.util.List; @RestController @@ -28,4 +27,15 @@ public ResponseEntity>> nearbyStores( return ResponseEntity.status(HttpStatus.OK).body(SuccessResponse.from(stores)); } + + // 가게 판매 메뉴 목록 조회 + @GetMapping("/{storeId}") + public ResponseEntity> getStoreMenus( + @PathVariable Long storeId){ + + StoreMenusRes menus = storeService.getStoreMenus(storeId); + + return ResponseEntity.status(HttpStatus.OK).body(SuccessResponse.from(menus)); + } + } diff --git a/src/main/java/com/example/Centralthon/domain/store/web/dto/StoreMenusRes.java b/src/main/java/com/example/Centralthon/domain/store/web/dto/StoreMenusRes.java new file mode 100644 index 0000000..a5c3df6 --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/store/web/dto/StoreMenusRes.java @@ -0,0 +1,24 @@ +package com.example.Centralthon.domain.store.web.dto; + +import com.example.Centralthon.domain.menu.web.dto.MenuItemRes; +import com.example.Centralthon.domain.menu.web.dto.MenuItemRes; +import com.example.Centralthon.domain.store.entity.Store; +import com.example.Centralthon.domain.store.entity.enums.StoreCategory; + +import java.util.List; + +public record StoreMenusRes( + String name, + StoreCategory category, + double distance, + List menus +) { + public static StoreMenusRes from(Store store, double distance, List menuItems) { + return new StoreMenusRes( + store.getName(), + store.getCategory(), + distance, + menuItems + ); + } +} From 3a4007c32ae422d63fb7c8616b636006611702c5 Mon Sep 17 00:00:00 2001 From: oroi2009 Date: Fri, 15 Aug 2025 23:10:30 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9C=A8=20=20feat=20:=20=ED=95=B4?= =?UTF-8?q?=EB=8B=B9=20=EA=B0=80=EA=B2=8C=EB=A5=BC=20=EB=AA=BB=EC=B0=BE?= =?UTF-8?q?=EB=8A=94=20=EA=B2=BD=EC=9A=B0=EC=9D=98=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/store/exception/StoreErrorCode.java | 15 +++++++++++++++ .../store/exception/StoreNotFoundException.java | 9 +++++++++ 2 files changed, 24 insertions(+) create mode 100644 src/main/java/com/example/Centralthon/domain/store/exception/StoreErrorCode.java create mode 100644 src/main/java/com/example/Centralthon/domain/store/exception/StoreNotFoundException.java diff --git a/src/main/java/com/example/Centralthon/domain/store/exception/StoreErrorCode.java b/src/main/java/com/example/Centralthon/domain/store/exception/StoreErrorCode.java new file mode 100644 index 0000000..aa0c96e --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/store/exception/StoreErrorCode.java @@ -0,0 +1,15 @@ +package com.example.Centralthon.domain.store.exception; + +import com.example.Centralthon.global.response.code.BaseResponseCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum StoreErrorCode implements BaseResponseCode { + STORE_NOT_FOUND("STORE_NOT_FOUND_404_1",404,"존재하지 않는 가게 입니다."); + + private final String code; + private final int httpStatus; + private final String message; +} diff --git a/src/main/java/com/example/Centralthon/domain/store/exception/StoreNotFoundException.java b/src/main/java/com/example/Centralthon/domain/store/exception/StoreNotFoundException.java new file mode 100644 index 0000000..fb85dbf --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/store/exception/StoreNotFoundException.java @@ -0,0 +1,9 @@ +package com.example.Centralthon.domain.store.exception; + +import com.example.Centralthon.global.exception.BaseException; + +public class StoreNotFoundException extends BaseException { + public StoreNotFoundException( ) { + super(StoreErrorCode.STORE_NOT_FOUND); + } +} From 8d0164f5fb284a3a4c8b8071f7d65c92a151534d Mon Sep 17 00:00:00 2001 From: oroi2009 Date: Fri, 15 Aug 2025 23:47:07 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9C=A8=20=20feat=20:=20=EA=B0=80?= =?UTF-8?q?=EA=B2=8C=20=EB=A9=94=EB=89=B4=20=EC=A1=B0=ED=9A=8C=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/repository/MenuRepository.java | 11 ++++++- .../domain/store/service/StoreService.java | 2 +- .../store/service/StoreServiceImpl.java | 30 +++++++++++++++++-- .../store/web/controller/StoreController.java | 8 +++-- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/example/Centralthon/domain/menu/repository/MenuRepository.java b/src/main/java/com/example/Centralthon/domain/menu/repository/MenuRepository.java index d1afa6e..83b64b2 100644 --- a/src/main/java/com/example/Centralthon/domain/menu/repository/MenuRepository.java +++ b/src/main/java/com/example/Centralthon/domain/menu/repository/MenuRepository.java @@ -10,6 +10,7 @@ import java.time.LocalDateTime; @Repository public interface MenuRepository extends JpaRepository { + // BoundingBox 안에 존재하는 모든 메뉴 반환 @Query(value = """ SELECT m.* FROM menus m JOIN stores s ON m.store_id = s.store_id @@ -27,6 +28,14 @@ List findNearbyMenus( @Param("maxLng") double maxLng ); - + // 해당 가게에서 판매 하는 메뉴 반환 + @Query(""" + SELECT m FROM Menu m + JOIN FETCH m.store s + WHERE s.id = :storeId AND m.quantity > 0 AND m.deadline > :now + """) + List findByStoreId( + @Param("storeId") Long storeId, + @Param("now") LocalDateTime now); } diff --git a/src/main/java/com/example/Centralthon/domain/store/service/StoreService.java b/src/main/java/com/example/Centralthon/domain/store/service/StoreService.java index 801e2df..695cf08 100644 --- a/src/main/java/com/example/Centralthon/domain/store/service/StoreService.java +++ b/src/main/java/com/example/Centralthon/domain/store/service/StoreService.java @@ -8,5 +8,5 @@ public interface StoreService { List nearbyStores(double lat, double lng); - StoreMenusRes getStoreMenus(Long storeId); + StoreMenusRes getStoreMenus(Long storeId, double lat, double lng); } diff --git a/src/main/java/com/example/Centralthon/domain/store/service/StoreServiceImpl.java b/src/main/java/com/example/Centralthon/domain/store/service/StoreServiceImpl.java index f59d967..4bda5b6 100644 --- a/src/main/java/com/example/Centralthon/domain/store/service/StoreServiceImpl.java +++ b/src/main/java/com/example/Centralthon/domain/store/service/StoreServiceImpl.java @@ -1,10 +1,15 @@ package com.example.Centralthon.domain.store.service; +import com.example.Centralthon.domain.menu.entity.Menu; +import com.example.Centralthon.domain.menu.repository.MenuRepository; +import com.example.Centralthon.domain.menu.web.dto.MenuItemRes; import com.example.Centralthon.domain.store.entity.Store; import com.example.Centralthon.domain.store.repository.StoreRepository; import com.example.Centralthon.domain.store.web.dto.NearbyStoresRes; import com.example.Centralthon.domain.store.web.dto.StoreMenusRes; +import com.example.Centralthon.domain.store.exception.StoreNotFoundException; import com.example.Centralthon.global.util.geo.BoundingBox; +import com.example.Centralthon.global.util.geo.GeoUtils; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -18,6 +23,7 @@ @RequiredArgsConstructor public class StoreServiceImpl implements StoreService { private final StoreRepository storeRepository; + private final MenuRepository menuRepository; @Override @Transactional(readOnly = true) @@ -36,7 +42,27 @@ public List nearbyStores(double lat, double lng) { } @Override - public StoreMenusRes getStoreMenus(Long storeId) { - return null; + @Transactional(readOnly = true) + public StoreMenusRes getStoreMenus(Long storeId, double lat, double lng) { + LocalDateTime now = LocalDateTime.now(); + + // 가게 id가 없을 경우 -> StoreNotFoundException + Store store = storeRepository.findById(storeId) + .orElseThrow(StoreNotFoundException::new); + + // 사용자와 가게 간의 거리 구하기 + double distance = GeoUtils.calculateDistance(lat, lng, store.getLatitude(), store.getLongitude()); + + List menus = menuRepository.findByStoreId(storeId, now); + + List menuItems = menus.stream() + .map(MenuItemRes::from) + .toList(); + + return StoreMenusRes.from( + store, + distance, + menuItems + ); } } diff --git a/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreController.java b/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreController.java index a9bf48b..c5bc02e 100644 --- a/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreController.java +++ b/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreController.java @@ -29,11 +29,13 @@ public ResponseEntity>> nearbyStores( } // 가게 판매 메뉴 목록 조회 - @GetMapping("/{storeId}") + @GetMapping("/{storeId}/menus") public ResponseEntity> getStoreMenus( - @PathVariable Long storeId){ + @PathVariable Long storeId, + @RequestParam("lat") Double lat, + @RequestParam("lng") Double lng){ - StoreMenusRes menus = storeService.getStoreMenus(storeId); + StoreMenusRes menus = storeService.getStoreMenus(storeId, lat, lng); return ResponseEntity.status(HttpStatus.OK).body(SuccessResponse.from(menus)); }