store update

This commit is contained in:
youbeen 2025-06-12 14:42:49 +09:00
parent b576f9b321
commit 097cb0e7f8
9 changed files with 411 additions and 50 deletions

View File

@ -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;
/**
* 이벤트 포트 인터페이스

View File

@ -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<Menu> findMenusByStoreId(Long storeId);
/**
* 메뉴 ID로 메뉴 조회
*
* @param menuId 메뉴 ID
* @return 메뉴 정보 (Optional)
*/
Optional<Menu> findMenuById(Long menuId);
/**
* 메뉴 저장
*
* @param menu 저장할 메뉴 정보
* @return 저장된 메뉴 정보
*/
Menu saveMenu(Menu menu);
/**
* 메뉴 삭제
*
* @param menuId 삭제할 메뉴 ID
*/
void deleteMenu(Long menuId);
/**
* 매장의 이용 가능한 메뉴 목록 조회
*
* @param storeId 매장 ID
* @return 이용 가능한 메뉴 목록
*/
List<Menu> findAvailableMenusByStoreId(Long storeId);
/**
* 카테고리별 메뉴 목록 조회
*
* @param storeId 매장 ID
* @param category 카테고리
* @return 카테고리별 메뉴 목록
*/
List<Menu> findMenusByStoreIdAndCategory(Long storeId, String category);
/**
* 메뉴 여러 저장
*
* @param menus 저장할 메뉴 목록
* @return 저장된 메뉴 목록
*/
List<Menu> saveMenus(List<Menu> menus);
/**
* 매장의 모든 메뉴 삭제
*
* @param storeId 매장 ID
*/
void deleteMenusByStoreId(Long storeId);
}

View File

@ -1,4 +1,4 @@
package com.ktds.hi.store.biz.domain;
package com.ktds.hi.store.domain;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

@ -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<String> tags;
private StoreStatus status;
private Double rating;
private Integer reviewCount;
private String imageUrl;
private Boolean isActive;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private List<String> tags;
private List<Menu> 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;
}
}

View File

@ -1,4 +1,4 @@
package com.ktds.hi.store.biz.domain;
package com.ktds.hi.store.domain;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

@ -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;
}
}

View File

@ -1,4 +1,4 @@
package com.ktds.hi.store.biz.domain;
package com.ktds.hi.store.domain;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

@ -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;
}
}
}
}

View File

@ -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<Menu> 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<Menu> 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<Menu> findAvailableMenusByStoreId(Long storeId) {
return menuJpaRepository.findByStoreIdAndIsAvailableTrue(storeId)
.stream()
.map(this::toDomain)
.collect(Collectors.toList());
}
@Override
public List<Menu> findMenusByStoreIdAndCategory(Long storeId, String category) {
return menuJpaRepository.findByStoreIdAndCategoryAndIsAvailableTrue(storeId, category)
.stream()
.map(this::toDomain)
.collect(Collectors.toList());
}
@Override
public List<Menu> saveMenus(List<Menu> menus) {
List<MenuEntity> entities = menus.stream()
.map(this::toEntity)
.collect(Collectors.toList());
List<MenuEntity> 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();
}
}
}