From 097cb0e7f8dba821305cd04f1693de0679fbb82c Mon Sep 17 00:00:00 2001 From: youbeen Date: Thu, 12 Jun 2025 14:42:49 +0900 Subject: [PATCH] store update --- .../hi/store/biz/usecase/out/EventPort.java | 2 +- .../biz/usecase/out/MenuRepositoryPort.java | 79 ++++++++ .../com/ktds/hi/store/domain/Coordinates.java | 2 +- .../java/com/ktds/hi/store/domain/Store.java | 188 +++++++++++++++++- .../hi/store/domain/StoreSearchCriteria.java | 2 +- .../com/ktds/hi/store/domain/StoreStatus.java | 70 +++++++ .../com/ktds/hi/store/domain/StoreTag.java | 2 +- .../hi/store/infra/gateway/EventAdapter.java | 63 +++--- .../infra/gateway/MenuRepositoryAdapter.java | 53 ++++- 9 files changed, 411 insertions(+), 50 deletions(-) create mode 100644 store/src/main/java/com/ktds/hi/store/biz/usecase/out/MenuRepositoryPort.java create mode 100644 store/src/main/java/com/ktds/hi/store/domain/StoreStatus.java diff --git a/store/src/main/java/com/ktds/hi/store/biz/usecase/out/EventPort.java b/store/src/main/java/com/ktds/hi/store/biz/usecase/out/EventPort.java index c947e6e..a708595 100644 --- a/store/src/main/java/com/ktds/hi/store/biz/usecase/out/EventPort.java +++ b/store/src/main/java/com/ktds/hi/store/biz/usecase/out/EventPort.java @@ -1,6 +1,6 @@ package com.ktds.hi.store.biz.usecase.out; -import com.ktds.hi.store.biz.domain.Store; +import com.ktds.hi.store.domain.Store; /** * 이벤트 포트 인터페이스 diff --git a/store/src/main/java/com/ktds/hi/store/biz/usecase/out/MenuRepositoryPort.java b/store/src/main/java/com/ktds/hi/store/biz/usecase/out/MenuRepositoryPort.java new file mode 100644 index 0000000..a8eadf4 --- /dev/null +++ b/store/src/main/java/com/ktds/hi/store/biz/usecase/out/MenuRepositoryPort.java @@ -0,0 +1,79 @@ +package com.ktds.hi.store.biz.usecase.out; + +import com.ktds.hi.store.biz.domain.Menu; + +import java.util.List; +import java.util.Optional; + +/** + * 메뉴 리포지토리 포트 인터페이스 + * 메뉴 데이터 영속성 기능을 정의 + * + * @author 하이오더 개발팀 + * @version 1.0.0 + */ +public interface MenuRepositoryPort { + + /** + * 매장 ID로 메뉴 목록 조회 + * + * @param storeId 매장 ID + * @return 메뉴 목록 + */ + List findMenusByStoreId(Long storeId); + + /** + * 메뉴 ID로 메뉴 조회 + * + * @param menuId 메뉴 ID + * @return 메뉴 정보 (Optional) + */ + Optional findMenuById(Long menuId); + + /** + * 메뉴 저장 + * + * @param menu 저장할 메뉴 정보 + * @return 저장된 메뉴 정보 + */ + Menu saveMenu(Menu menu); + + /** + * 메뉴 삭제 + * + * @param menuId 삭제할 메뉴 ID + */ + void deleteMenu(Long menuId); + + /** + * 매장의 이용 가능한 메뉴 목록 조회 + * + * @param storeId 매장 ID + * @return 이용 가능한 메뉴 목록 + */ + List findAvailableMenusByStoreId(Long storeId); + + /** + * 카테고리별 메뉴 목록 조회 + * + * @param storeId 매장 ID + * @param category 카테고리 + * @return 카테고리별 메뉴 목록 + */ + List findMenusByStoreIdAndCategory(Long storeId, String category); + + /** + * 메뉴 여러 개 저장 + * + * @param menus 저장할 메뉴 목록 + * @return 저장된 메뉴 목록 + */ + List saveMenus(List menus); + + /** + * 매장의 모든 메뉴 삭제 + * + * @param storeId 매장 ID + */ + void deleteMenusByStoreId(Long storeId); +} \ No newline at end of file diff --git a/store/src/main/java/com/ktds/hi/store/domain/Coordinates.java b/store/src/main/java/com/ktds/hi/store/domain/Coordinates.java index 000ca65..80178a7 100644 --- a/store/src/main/java/com/ktds/hi/store/domain/Coordinates.java +++ b/store/src/main/java/com/ktds/hi/store/domain/Coordinates.java @@ -1,4 +1,4 @@ -package com.ktds.hi.store.biz.domain; +package com.ktds.hi.store.domain; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/store/src/main/java/com/ktds/hi/store/domain/Store.java b/store/src/main/java/com/ktds/hi/store/domain/Store.java index 68eb7b7..a7fbfd8 100644 --- a/store/src/main/java/com/ktds/hi/store/domain/Store.java +++ b/store/src/main/java/com/ktds/hi/store/domain/Store.java @@ -6,11 +6,14 @@ import lombok.Getter; import lombok.NoArgsConstructor; import java.time.LocalDateTime; -import java.time.LocalTime; import java.util.List; /** - * 매장 도메인 엔티티 + * 매장 도메인 클래스 + * 매장 정보를 담는 도메인 객체 + * + * @author 하이오더 개발팀 + * @version 1.0.0 */ @Getter @Builder @@ -20,19 +23,182 @@ public class Store { private Long id; private Long ownerId; - private String name; - private String description; + private String storeName; private String address; - private String phoneNumber; - private String category; private Double latitude; private Double longitude; - private LocalTime openTime; - private LocalTime closeTime; + private String category; + private String description; + private String phone; + private String operatingHours; + private List tags; + private StoreStatus status; + private Double rating; + private Integer reviewCount; private String imageUrl; - private Boolean isActive; private LocalDateTime createdAt; private LocalDateTime updatedAt; - private List tags; - private List menus; + + /** + * 매장 기본 정보 업데이트 + */ + public Store updateBasicInfo(String storeName, String address, String description, + String phone, String operatingHours) { + return Store.builder() + .id(this.id) + .ownerId(this.ownerId) + .storeName(storeName) + .address(address) + .latitude(this.latitude) + .longitude(this.longitude) + .category(this.category) + .description(description) + .phone(phone) + .operatingHours(operatingHours) + .tags(this.tags) + .status(this.status) + .rating(this.rating) + .reviewCount(this.reviewCount) + .imageUrl(this.imageUrl) + .createdAt(this.createdAt) + .updatedAt(LocalDateTime.now()) + .build(); + } + + /** + * 매장 위치 정보 업데이트 + */ + public Store updateLocation(Double latitude, Double longitude) { + return Store.builder() + .id(this.id) + .ownerId(this.ownerId) + .storeName(this.storeName) + .address(this.address) + .latitude(latitude) + .longitude(longitude) + .category(this.category) + .description(this.description) + .phone(this.phone) + .operatingHours(this.operatingHours) + .tags(this.tags) + .status(this.status) + .rating(this.rating) + .reviewCount(this.reviewCount) + .imageUrl(this.imageUrl) + .createdAt(this.createdAt) + .updatedAt(LocalDateTime.now()) + .build(); + } + + /** + * 매장 평점 및 리뷰 수 업데이트 + */ + public Store updateRating(Double rating, Integer reviewCount) { + return Store.builder() + .id(this.id) + .ownerId(this.ownerId) + .storeName(this.storeName) + .address(this.address) + .latitude(this.latitude) + .longitude(this.longitude) + .category(this.category) + .description(this.description) + .phone(this.phone) + .operatingHours(this.operatingHours) + .tags(this.tags) + .status(this.status) + .rating(rating) + .reviewCount(reviewCount) + .imageUrl(this.imageUrl) + .createdAt(this.createdAt) + .updatedAt(LocalDateTime.now()) + .build(); + } + + /** + * 매장 활성화 + */ + public Store activate() { + return Store.builder() + .id(this.id) + .ownerId(this.ownerId) + .storeName(this.storeName) + .address(this.address) + .latitude(this.latitude) + .longitude(this.longitude) + .category(this.category) + .description(this.description) + .phone(this.phone) + .operatingHours(this.operatingHours) + .tags(this.tags) + .status(StoreStatus.ACTIVE) + .rating(this.rating) + .reviewCount(this.reviewCount) + .imageUrl(this.imageUrl) + .createdAt(this.createdAt) + .updatedAt(LocalDateTime.now()) + .build(); + } + + /** + * 매장 비활성화 + */ + public Store deactivate() { + return Store.builder() + .id(this.id) + .ownerId(this.ownerId) + .storeName(this.storeName) + .address(this.address) + .latitude(this.latitude) + .longitude(this.longitude) + .category(this.category) + .description(this.description) + .phone(this.phone) + .operatingHours(this.operatingHours) + .tags(this.tags) + .status(StoreStatus.INACTIVE) + .rating(this.rating) + .reviewCount(this.reviewCount) + .imageUrl(this.imageUrl) + .createdAt(this.createdAt) + .updatedAt(LocalDateTime.now()) + .build(); + } + + /** + * 매장 활성 상태 확인 + */ + public boolean isActive() { + return StoreStatus.ACTIVE.equals(this.status); + } + + /** + * 매장 소유권 확인 + */ + public boolean isOwnedBy(Long ownerId) { + return this.ownerId != null && this.ownerId.equals(ownerId); + } + + /** + * 두 좌표 간의 거리 계산 (킬로미터) + */ + public Double calculateDistance(Double targetLatitude, Double targetLongitude) { + if (this.latitude == null || this.longitude == null || + targetLatitude == null || targetLongitude == null) { + return null; + } + + final int EARTH_RADIUS = 6371; // 지구 반지름 (킬로미터) + + double latDistance = Math.toRadians(targetLatitude - this.latitude); + double lonDistance = Math.toRadians(targetLongitude - this.longitude); + + double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2) + + Math.cos(Math.toRadians(this.latitude)) * Math.cos(Math.toRadians(targetLatitude)) + * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2); + + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + return EARTH_RADIUS * c; + } } \ No newline at end of file diff --git a/store/src/main/java/com/ktds/hi/store/domain/StoreSearchCriteria.java b/store/src/main/java/com/ktds/hi/store/domain/StoreSearchCriteria.java index 1230435..df18f3c 100644 --- a/store/src/main/java/com/ktds/hi/store/domain/StoreSearchCriteria.java +++ b/store/src/main/java/com/ktds/hi/store/domain/StoreSearchCriteria.java @@ -1,4 +1,4 @@ -package com.ktds.hi.store.biz.domain; +package com.ktds.hi.store.domain; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/store/src/main/java/com/ktds/hi/store/domain/StoreStatus.java b/store/src/main/java/com/ktds/hi/store/domain/StoreStatus.java new file mode 100644 index 0000000..d81a6d6 --- /dev/null +++ b/store/src/main/java/com/ktds/hi/store/domain/StoreStatus.java @@ -0,0 +1,70 @@ +package com.ktds.hi.store.domain; + +/** + * 매장 상태 열거형 + * 매장의 운영 상태를 정의 + * + * @author 하이오더 개발팀 + * @version 1.0.0 + */ +public enum StoreStatus { + + /** + * 활성 상태 - 정상 운영 중 + */ + ACTIVE("활성"), + + /** + * 비활성 상태 - 임시 휴업 + */ + INACTIVE("비활성"), + + /** + * 일시 정지 상태 - 관리자에 의한 일시 정지 + */ + SUSPENDED("일시정지"), + + /** + * 삭제 상태 - 영구 삭제 (소프트 삭제) + */ + DELETED("삭제"); + + private final String description; + + StoreStatus(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + /** + * 문자열로부터 StoreStatus 변환 + */ + public static StoreStatus fromString(String status) { + if (status == null) { + return INACTIVE; + } + + try { + return StoreStatus.valueOf(status.toUpperCase()); + } catch (IllegalArgumentException e) { + return INACTIVE; + } + } + + /** + * 매장이 서비스 가능한 상태인지 확인 + */ + public boolean isServiceable() { + return this == ACTIVE; + } + + /** + * 매장이 삭제된 상태인지 확인 + */ + public boolean isDeleted() { + return this == DELETED; + } +} \ No newline at end of file diff --git a/store/src/main/java/com/ktds/hi/store/domain/StoreTag.java b/store/src/main/java/com/ktds/hi/store/domain/StoreTag.java index 2a4ff66..bea397d 100644 --- a/store/src/main/java/com/ktds/hi/store/domain/StoreTag.java +++ b/store/src/main/java/com/ktds/hi/store/domain/StoreTag.java @@ -1,4 +1,4 @@ -package com.ktds.hi.store.biz.domain; +package com.ktds.hi.store.domain; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/store/src/main/java/com/ktds/hi/store/infra/gateway/EventAdapter.java b/store/src/main/java/com/ktds/hi/store/infra/gateway/EventAdapter.java index e8c69ea..a094ed5 100644 --- a/store/src/main/java/com/ktds/hi/store/infra/gateway/EventAdapter.java +++ b/store/src/main/java/com/ktds/hi/store/infra/gateway/EventAdapter.java @@ -1,6 +1,6 @@ package com.ktds.hi.store.infra.gateway; -import com.ktds.hi.store.biz.domain.Store; +import com.ktds.hi.store.domain.Store; import com.ktds.hi.store.biz.usecase.out.EventPort; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -10,95 +10,104 @@ import org.springframework.stereotype.Component; /** * 이벤트 어댑터 클래스 * Event Port를 구현하여 이벤트 발행 기능을 제공 + * + * @author 하이오더 개발팀 + * @version 1.0.0 */ @Component @RequiredArgsConstructor @Slf4j public class EventAdapter implements EventPort { - + private final ApplicationEventPublisher eventPublisher; - + @Override public void publishStoreCreatedEvent(Store store) { - log.info("매장 생성 이벤트 발행: storeId={}", store.getId()); - + log.info("매장 생성 이벤트 발행: storeId={}, storeName={}", store.getId(), store.getStoreName()); + try { StoreCreatedEvent event = new StoreCreatedEvent(store); eventPublisher.publishEvent(event); - + log.info("매장 생성 이벤트 발행 완료: storeId={}", store.getId()); - + } catch (Exception e) { log.error("매장 생성 이벤트 발행 실패: storeId={}, error={}", store.getId(), e.getMessage(), e); } } - + @Override public void publishStoreUpdatedEvent(Store store) { - log.info("매장 수정 이벤트 발행: storeId={}", store.getId()); - + log.info("매장 수정 이벤트 발행: storeId={}, storeName={}", store.getId(), store.getStoreName()); + try { StoreUpdatedEvent event = new StoreUpdatedEvent(store); eventPublisher.publishEvent(event); - + log.info("매장 수정 이벤트 발행 완료: storeId={}", store.getId()); - + } catch (Exception e) { log.error("매장 수정 이벤트 발행 실패: storeId={}, error={}", store.getId(), e.getMessage(), e); } } - + @Override public void publishStoreDeletedEvent(Long storeId) { log.info("매장 삭제 이벤트 발행: storeId={}", storeId); - + try { StoreDeletedEvent event = new StoreDeletedEvent(storeId); eventPublisher.publishEvent(event); - + log.info("매장 삭제 이벤트 발행 완료: storeId={}", storeId); - + } catch (Exception e) { log.error("매장 삭제 이벤트 발행 실패: storeId={}, error={}", storeId, e.getMessage(), e); } } - + /** - * 매장 이벤트 클래스들 + * 매장 생성 이벤트 클래스 */ public static class StoreCreatedEvent { private final Store store; - + public StoreCreatedEvent(Store store) { this.store = store; } - + public Store getStore() { return store; } } - + + /** + * 매장 수정 이벤트 클래스 + */ public static class StoreUpdatedEvent { private final Store store; - + public StoreUpdatedEvent(Store store) { this.store = store; } - + public Store getStore() { return store; } } - + + /** + * 매장 삭제 이벤트 클래스 + */ public static class StoreDeletedEvent { private final Long storeId; - + public StoreDeletedEvent(Long storeId) { this.storeId = storeId; } - + public Long getStoreId() { return storeId; } } -} +} \ No newline at end of file diff --git a/store/src/main/java/com/ktds/hi/store/infra/gateway/MenuRepositoryAdapter.java b/store/src/main/java/com/ktds/hi/store/infra/gateway/MenuRepositoryAdapter.java index cfc829a..b46b76b 100644 --- a/store/src/main/java/com/ktds/hi/store/infra/gateway/MenuRepositoryAdapter.java +++ b/store/src/main/java/com/ktds/hi/store/infra/gateway/MenuRepositoryAdapter.java @@ -14,13 +14,16 @@ import java.util.stream.Collectors; /** * 메뉴 리포지토리 어댑터 클래스 * Menu Repository Port를 구현하여 데이터 영속성 기능을 제공 + * + * @author 하이오더 개발팀 + * @version 1.0.0 */ @Component @RequiredArgsConstructor public class MenuRepositoryAdapter implements MenuRepositoryPort { - + private final MenuJpaRepository menuJpaRepository; - + @Override public List findMenusByStoreId(Long storeId) { return menuJpaRepository.findByStoreId(storeId) @@ -28,25 +31,59 @@ public class MenuRepositoryAdapter implements MenuRepositoryPort { .map(this::toDomain) .collect(Collectors.toList()); } - + @Override public Optional findMenuById(Long menuId) { return menuJpaRepository.findById(menuId) .map(this::toDomain); } - + @Override public Menu saveMenu(Menu menu) { MenuEntity entity = toEntity(menu); MenuEntity saved = menuJpaRepository.save(entity); return toDomain(saved); } - + @Override public void deleteMenu(Long menuId) { menuJpaRepository.deleteById(menuId); } - + + @Override + public List findAvailableMenusByStoreId(Long storeId) { + return menuJpaRepository.findByStoreIdAndIsAvailableTrue(storeId) + .stream() + .map(this::toDomain) + .collect(Collectors.toList()); + } + + @Override + public List findMenusByStoreIdAndCategory(Long storeId, String category) { + return menuJpaRepository.findByStoreIdAndCategoryAndIsAvailableTrue(storeId, category) + .stream() + .map(this::toDomain) + .collect(Collectors.toList()); + } + + @Override + public List saveMenus(List menus) { + List entities = menus.stream() + .map(this::toEntity) + .collect(Collectors.toList()); + + List savedEntities = menuJpaRepository.saveAll(entities); + + return savedEntities.stream() + .map(this::toDomain) + .collect(Collectors.toList()); + } + + @Override + public void deleteMenusByStoreId(Long storeId) { + menuJpaRepository.deleteByStoreId(storeId); + } + /** * Entity를 Domain으로 변환 */ @@ -64,7 +101,7 @@ public class MenuRepositoryAdapter implements MenuRepositoryPort { .updatedAt(entity.getUpdatedAt()) .build(); } - + /** * Domain을 Entity로 변환 */ @@ -82,4 +119,4 @@ public class MenuRepositoryAdapter implements MenuRepositoryPort { .updatedAt(domain.getUpdatedAt()) .build(); } -} +} \ No newline at end of file