Add: 주문 데이터 API 로직 개발

This commit is contained in:
UNGGU0704 2025-06-16 17:17:32 +09:00
parent 2373a07772
commit 6591ff7a1c
11 changed files with 412 additions and 91 deletions

View File

@ -1,91 +0,0 @@
package com.ktds.hi.member.service;
import com.ktds.hi.member.dto.PreferenceRequest;
import com.ktds.hi.member.dto.TasteTagResponse;
import com.ktds.hi.member.domain.TagType;
import com.ktds.hi.member.repository.entity.PreferenceEntity;
import com.ktds.hi.member.repository.entity.TasteTagEntity;
import com.ktds.hi.member.repository.jpa.PreferenceRepository;
import com.ktds.hi.member.repository.jpa.TasteTagRepository;
import com.ktds.hi.common.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
/**
* 취향 관리 서비스 구현체
* 취향 정보 등록/수정 태그 관리 기능을 구현
*/
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class PreferenceServiceImpl implements PreferenceService {
private final PreferenceRepository preferenceRepository;
private final TasteTagRepository tasteTagRepository;
@Override
public void savePreference(Long memberId, PreferenceRequest request) {
// 태그 유효성 검증
List<TasteTagEntity> existingTags = tasteTagRepository.findByTagNameIn(request.getTags());
if (existingTags.size() != request.getTags().size()) {
throw new BusinessException("유효하지 않은 태그가 포함되어 있습니다");
}
// 기존 취향 정보 조회
PreferenceEntity preference = preferenceRepository.findByMemberId(memberId)
.orElse(null);
if (preference != null) {
// 기존 정보 업데이트
preference.updatePreference(request.getTags(), request.getHealthInfo(), request.getSpicyLevel());
} else {
// 새로운 취향 정보 생성
preference = PreferenceEntity.builder()
.memberId(memberId)
.tags(request.getTags())
.healthInfo(request.getHealthInfo())
.spicyLevel(request.getSpicyLevel())
.build();
}
preferenceRepository.save(preference);
log.info("취향 정보 저장 완료: memberId={}, tags={}", memberId, request.getTags());
}
@Override
@Transactional(readOnly = true)
public List<TasteTagResponse> getAvailableTags() {
List<TasteTagEntity> tags = tasteTagRepository.findByIsActiveTrue();
return tags.stream()
.map(tag -> TasteTagResponse.builder()
.id(tag.getId())
.tagName(tag.getTagName())
.tagType(tag.getTagType())
.description(tag.getDescription())
.build())
.collect(Collectors.toList());
}
@Override
@Transactional(readOnly = true)
public List<TasteTagResponse> getTagsByType(TagType tagType) {
List<TasteTagEntity> tags = tasteTagRepository.findByTagTypeAndIsActiveTrue(tagType);
return tags.stream()
.map(tag -> TasteTagResponse.builder()
.id(tag.getId())
.tagName(tag.getTagName())
.tagType(tag.getTagType())
.description(tag.getDescription())
.build())
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,88 @@
package com.ktds.hi.store.biz.service;
import com.ktds.hi.store.biz.usecase.in.OrderUseCase;
import com.ktds.hi.store.biz.usecase.out.OrderRepositoryPort;
import com.ktds.hi.store.domain.Order;
import com.ktds.hi.store.infra.dto.OrderListResponse;
import com.ktds.hi.store.infra.dto.OrderResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class OrderService implements OrderUseCase {
private final OrderRepositoryPort orderRepositoryPort;
@Override
public OrderListResponse getOrdersByStore(Long storeId) {
log.info("가게 {} 의 주문 목록 조회", storeId);
List<Order> orders = orderRepositoryPort.findOrdersByStoreId(storeId);
List<OrderResponse> orderResponses = orders.stream()
.map(this::toResponse)
.collect(Collectors.toList());
return OrderListResponse.builder()
.storeId(storeId)
.totalCount(orderResponses.size())
.orders(orderResponses)
.build();
}
@Override
public OrderListResponse getOrdersByStoreAndPeriod(Long storeId, LocalDateTime startDate, LocalDateTime endDate) {
log.info("가게 {} 의 기간별 주문 목록 조회: {} ~ {}", storeId, startDate, endDate);
List<Order> orders = orderRepositoryPort.findOrdersByStoreIdAndPeriod(storeId, startDate, endDate);
List<OrderResponse> orderResponses = orders.stream()
.map(this::toResponse)
.collect(Collectors.toList());
return OrderListResponse.builder()
.storeId(storeId)
.totalCount(orderResponses.size())
.orders(orderResponses)
.build();
}
@Override
public OrderResponse getOrder(Long orderId) {
log.info("주문 {} 조회", orderId);
Order order = orderRepositoryPort.findOrderById(orderId)
.orElseThrow(() -> new IllegalArgumentException("주문을 찾을 수 없습니다: " + orderId));
return toResponse(order);
}
@Override
public List<OrderResponse> getAllOrders() {
log.info("전체 주문 목록 조회");
return orderRepositoryPort.findAllOrders().stream()
.map(this::toResponse)
.collect(Collectors.toList());
}
private OrderResponse toResponse(Order order) {
return OrderResponse.builder()
.id(order.getId())
.storeId(order.getStoreId())
.menuId(order.getMenuId())
.customerAge(order.getCustomerAge())
.customerGender(order.getCustomerGender())
.orderAmount(order.getOrderAmount())
.orderDate(order.getOrderDate())
.createdAt(order.getCreatedAt())
.build();
}
}

View File

@ -0,0 +1,18 @@
package com.ktds.hi.store.biz.usecase.in;
import com.ktds.hi.store.infra.dto.OrderListResponse;
import com.ktds.hi.store.infra.dto.OrderResponse;
import java.time.LocalDateTime;
import java.util.List;
public interface OrderUseCase {
OrderListResponse getOrdersByStore(Long storeId);
OrderListResponse getOrdersByStoreAndPeriod(Long storeId, LocalDateTime startDate, LocalDateTime endDate);
OrderResponse getOrder(Long orderId);
List<OrderResponse> getAllOrders();
}

View File

@ -0,0 +1,20 @@
package com.ktds.hi.store.biz.usecase.out;
import com.ktds.hi.store.domain.Order;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
public interface OrderRepositoryPort {
List<Order> findOrdersByStoreId(Long storeId);
List<Order> findOrdersByStoreIdAndPeriod(Long storeId, LocalDateTime startDate, LocalDateTime endDate);
Optional<Order> findOrderById(Long orderId);
List<Order> findAllOrders();
Order saveOrder(Order order);
}

View File

@ -0,0 +1,22 @@
package com.ktds.hi.store.domain;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Order {
private Long id;
private Long storeId;
private Long menuId;
private Integer customerAge;
private String customerGender;
private BigDecimal orderAmount;
private LocalDateTime orderDate;
private LocalDateTime createdAt;
}

View File

@ -0,0 +1,60 @@
package com.ktds.hi.store.infra.controller;
import com.ktds.hi.common.dto.SuccessResponse;
import com.ktds.hi.store.biz.usecase.in.OrderUseCase;
import com.ktds.hi.store.infra.dto.OrderListResponse;
import com.ktds.hi.store.infra.dto.OrderResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.List;
@RestController
@RequestMapping("/api/stores/orders")
@RequiredArgsConstructor
@Tag(name = "주문 데이터", description = "주문 데이터 조회 API")
public class OrderController {
private final OrderUseCase orderUseCase;
@GetMapping("/store/{storeId}")
@Operation(summary = "가게별 주문 조회", description = "특정 가게의 모든 주문을 조회합니다")
public ResponseEntity<SuccessResponse<OrderListResponse>> getOrdersByStore(
@PathVariable Long storeId) {
OrderListResponse response = orderUseCase.getOrdersByStore(storeId);
return ResponseEntity.ok(SuccessResponse.of(response));
}
@GetMapping("/store/{storeId}/period")
@Operation(summary = "가게별 기간 주문 조회", description = "특정 가게의 기간별 주문을 조회합니다")
public ResponseEntity<SuccessResponse<OrderListResponse>> getOrdersByStoreAndPeriod(
@PathVariable Long storeId,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate) {
OrderListResponse response = orderUseCase.getOrdersByStoreAndPeriod(storeId, startDate, endDate);
return ResponseEntity.ok(SuccessResponse.of(response));
}
@GetMapping("/{orderId}")
@Operation(summary = "단일 주문 조회", description = "특정 주문 하나를 조회합니다 (테스트용)")
public ResponseEntity<SuccessResponse<OrderResponse>> getOrder(@PathVariable Long orderId) {
OrderResponse response = orderUseCase.getOrder(orderId);
return ResponseEntity.ok(SuccessResponse.of(response));
}
@GetMapping("/all")
@Operation(summary = "전체 주문 조회", description = "모든 주문을 조회합니다 (Analytics 서비스용)")
public ResponseEntity<SuccessResponse<List<OrderResponse>>> getAllOrders() {
List<OrderResponse> response = orderUseCase.getAllOrders();
return ResponseEntity.ok(SuccessResponse.of(response));
}
}

View File

@ -0,0 +1,18 @@
package com.ktds.hi.store.infra.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderListResponse {
private Long storeId;
private Integer totalCount;
private List<OrderResponse> orders;
}

View File

@ -0,0 +1,38 @@
package com.ktds.hi.store.infra.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderResponse {
private Long id;
private Long storeId;
private Long menuId;
private Integer customerAge;
private String customerGender;
private BigDecimal orderAmount;
private LocalDateTime orderDate;
private LocalDateTime createdAt;
// Entity를 Response로 변환
public static OrderResponse from(com.ktds.hi.store.infra.gateway.entity.OrderEntity entity) {
return OrderResponse.builder()
.id(entity.getId())
.storeId(entity.getStoreId())
.menuId(entity.getMenuId())
.customerAge(entity.getCustomerAge())
.customerGender(entity.getCustomerGender())
.orderAmount(entity.getOrderAmount())
.orderDate(entity.getOrderDate())
.createdAt(entity.getCreatedAt())
.build();
}
}

View File

@ -0,0 +1,82 @@
package com.ktds.hi.store.infra.gateway;
import com.ktds.hi.store.biz.usecase.out.OrderRepositoryPort;
import com.ktds.hi.store.domain.Order;
import com.ktds.hi.store.infra.gateway.entity.OrderEntity;
import com.ktds.hi.store.infra.gateway.repository.OrderJpaRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Component
@RequiredArgsConstructor
public class OrderRepositoryAdapter implements OrderRepositoryPort {
private final OrderJpaRepository orderJpaRepository;
@Override
public List<Order> findOrdersByStoreId(Long storeId) {
return orderJpaRepository.findByStoreId(storeId)
.stream()
.map(this::toDomain)
.collect(Collectors.toList());
}
@Override
public List<Order> findOrdersByStoreIdAndPeriod(Long storeId, LocalDateTime startDate, LocalDateTime endDate) {
return orderJpaRepository.findByStoreIdAndOrderDateBetween(storeId, startDate, endDate)
.stream()
.map(this::toDomain)
.collect(Collectors.toList());
}
@Override
public Optional<Order> findOrderById(Long orderId) {
return orderJpaRepository.findById(orderId)
.map(this::toDomain);
}
@Override
public List<Order> findAllOrders() {
return orderJpaRepository.findAll()
.stream()
.map(this::toDomain)
.collect(Collectors.toList());
}
@Override
public Order saveOrder(Order order) {
OrderEntity entity = toEntity(order);
OrderEntity saved = orderJpaRepository.save(entity);
return toDomain(saved);
}
private Order toDomain(OrderEntity entity) {
return Order.builder()
.id(entity.getId())
.storeId(entity.getStoreId())
.menuId(entity.getMenuId())
.customerAge(entity.getCustomerAge())
.customerGender(entity.getCustomerGender())
.orderAmount(entity.getOrderAmount())
.orderDate(entity.getOrderDate())
.createdAt(entity.getCreatedAt())
.build();
}
private OrderEntity toEntity(Order domain) {
return OrderEntity.builder()
.id(domain.getId())
.storeId(domain.getStoreId())
.menuId(domain.getMenuId())
.customerAge(domain.getCustomerAge())
.customerGender(domain.getCustomerGender())
.orderAmount(domain.getOrderAmount())
.orderDate(domain.getOrderDate())
.build();
}
}

View File

@ -0,0 +1,46 @@
package com.ktds.hi.store.infra.gateway.entity;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity
@Table(name = "orders")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public class OrderEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "store_id", nullable = false)
private Long storeId;
@Column(name = "menu_id", nullable = false)
private Long menuId;
@Column(name = "customer_age", nullable = false)
private Integer customerAge;
@Column(name = "customer_gender", nullable = false)
private String customerGender;
@Column(name = "order_amount", nullable = false, precision = 10, scale = 2)
private BigDecimal orderAmount;
@Column(name = "order_date", nullable = false)
private LocalDateTime orderDate;
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
}

View File

@ -0,0 +1,20 @@
package com.ktds.hi.store.infra.gateway.repository;
import com.ktds.hi.store.infra.gateway.entity.OrderEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
@Repository
public interface OrderJpaRepository extends JpaRepository<OrderEntity, Long> {
List<OrderEntity> findByStoreId(Long storeId);
List<OrderEntity> findByStoreIdAndOrderDateBetween(
Long storeId,
LocalDateTime startDate,
LocalDateTime endDate
);
}