diff --git a/src/main/java/poomasi/domain/admin/statistics/controller/StatisticsController.java b/src/main/java/poomasi/domain/admin/statistics/controller/StatisticsController.java new file mode 100644 index 00000000..e221f69c --- /dev/null +++ b/src/main/java/poomasi/domain/admin/statistics/controller/StatisticsController.java @@ -0,0 +1,42 @@ +package poomasi.domain.admin.statistics.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import poomasi.domain.admin.statistics.dto.response.CategoryMonthlySalesResponse; +import poomasi.domain.admin.statistics.dto.response.StoreMonthlySalesResponse; +import poomasi.domain.admin.statistics.service.StatisticsService; +import poomasi.domain.order.service.OrderService; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/admin/statistics") +public class StatisticsController { + + private final StatisticsService statisticsService; + private final OrderService orderService; + + @GetMapping("/stores/{storeId}/monthly-sales") + public ResponseEntity> getMonthlyStoreSales( + @PathVariable Long storeId, + @RequestParam String startMonth, + @RequestParam String endMonth, + Pageable pageable) { + + Page salesResponses = statisticsService.getMonthlyStoreSales(storeId, startMonth, endMonth, pageable); + return ResponseEntity.ok(salesResponses); + } + + @GetMapping("/categories/monthly-sales") + public ResponseEntity> getCategoryMonthlySales( + @RequestParam Long categoryId, + @RequestParam String startMonth, + @RequestParam String endMonth, + Pageable pageable + ) { + Page sales = statisticsService.getCategoryMonthlySales(categoryId, startMonth, endMonth, pageable); + return ResponseEntity.ok(sales); + } +} diff --git a/src/main/java/poomasi/domain/admin/statistics/dto/response/CategoryMonthlySalesResponse.java b/src/main/java/poomasi/domain/admin/statistics/dto/response/CategoryMonthlySalesResponse.java new file mode 100644 index 00000000..3a2649b5 --- /dev/null +++ b/src/main/java/poomasi/domain/admin/statistics/dto/response/CategoryMonthlySalesResponse.java @@ -0,0 +1,9 @@ +package poomasi.domain.admin.statistics.dto.response; + +import java.math.BigDecimal; + +public record CategoryMonthlySalesResponse( + String categoryName, + int count, + BigDecimal totalSales +) { } diff --git a/src/main/java/poomasi/domain/admin/statistics/dto/response/StoreMonthlySalesResponse.java b/src/main/java/poomasi/domain/admin/statistics/dto/response/StoreMonthlySalesResponse.java new file mode 100644 index 00000000..b1b40165 --- /dev/null +++ b/src/main/java/poomasi/domain/admin/statistics/dto/response/StoreMonthlySalesResponse.java @@ -0,0 +1,10 @@ +package poomasi.domain.admin.statistics.dto.response; + +import poomasi.domain.store.entity.Store; + +import java.math.BigDecimal; + +public record StoreMonthlySalesResponse( + Store store, + BigDecimal totalSales +) {} diff --git a/src/main/java/poomasi/domain/admin/statistics/service/StatisticsService.java b/src/main/java/poomasi/domain/admin/statistics/service/StatisticsService.java new file mode 100644 index 00000000..f63767c6 --- /dev/null +++ b/src/main/java/poomasi/domain/admin/statistics/service/StatisticsService.java @@ -0,0 +1,60 @@ +package poomasi.domain.admin.statistics.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import poomasi.domain.admin.statistics.dto.response.StoreMonthlySalesResponse; +import poomasi.domain.order.entity.Order; +import poomasi.domain.order.service.OrderService; +import poomasi.domain.admin.statistics.dto.response.CategoryMonthlySalesResponse; + + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class StatisticsService { + private final OrderService orderService; + + @Transactional(readOnly = true) + public Page getMonthlyStoreSales(Long storeId, String startMonth, String endMonth, Pageable pageable) { + LocalDate start = LocalDate.parse(startMonth + "-01"); + LocalDate end = LocalDate.parse(endMonth + "-01"); + end = end.withDayOfMonth(end.lengthOfMonth()); + + LocalDateTime startDate = start.atStartOfDay(); + LocalDateTime endDate = end.atTime(23, 59, 59); + + List orders = orderService.getOrdersByUpdateAtBetween(startDate, endDate); + + // 주문 목록에서 각 OrderedProduct를 순회하여 storeId와 일치하는 주문 품목의 매출을 계산 + List salesResponses = orders.stream() + .flatMap(order -> order.getOrderedProducts().stream()) + .filter(orderedProduct -> orderedProduct.getProduct().getStore().getId().equals(storeId)) + .map(orderedProduct -> { + BigDecimal totalSales = orderedProduct.getPrice().multiply(BigDecimal.valueOf(orderedProduct.getCount())); + return new StoreMonthlySalesResponse(orderedProduct.getProduct().getStore(), totalSales); + }) + .collect(Collectors.toList()); + + return new PageImpl<>(salesResponses, pageable, salesResponses.size()); + } + + public Page getCategoryMonthlySales(Long categoryId, String startMonth, String endMonth, Pageable pageable) { + LocalDate start = LocalDate.parse(startMonth + "-01"); + LocalDate end = LocalDate.parse(endMonth + "-01"); + end = end.withDayOfMonth(end.lengthOfMonth()); + + return orderService.getCategoryMonthlySales(categoryId, start.atStartOfDay(), end.atTime(LocalTime.MAX), pageable); + } +} + diff --git a/src/main/java/poomasi/domain/image/controller/ImageController.java b/src/main/java/poomasi/domain/image/controller/ImageController.java index 08c52a29..cfa5dc2d 100644 --- a/src/main/java/poomasi/domain/image/controller/ImageController.java +++ b/src/main/java/poomasi/domain/image/controller/ImageController.java @@ -17,7 +17,7 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api/image") +@RequestMapping("/api/images") public class ImageController { private final ImageService imageService; diff --git a/src/main/java/poomasi/domain/order/repository/OrderRepository.java b/src/main/java/poomasi/domain/order/repository/OrderRepository.java index 77b84fe7..e6977c45 100644 --- a/src/main/java/poomasi/domain/order/repository/OrderRepository.java +++ b/src/main/java/poomasi/domain/order/repository/OrderRepository.java @@ -3,9 +3,13 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import poomasi.domain.admin.statistics.dto.response.CategoryMonthlySalesResponse; import poomasi.domain.order.entity.Order; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -14,4 +18,29 @@ public interface OrderRepository extends JpaRepository { Optional findByMerchantUid(String merchantUid); Page findById(Long id, Pageable pageable); Page findByMemberId(Long memberId, Pageable pageable); + + @Query("SELECT o FROM Order o WHERE o.updateAt BETWEEN :startDate AND :endDate") + List findAllByUpdateAtBetween(@Param("startDate") LocalDateTime startDate, + @Param("endDate") LocalDateTime endDate); + + @Query(""" + SELECT new poomasi.domain.admin.statistics.dto.response.CategoryMonthlySalesResponse( + p.category, + COUNT(op.id), + SUM(op.price * op.quantity) + ) + FROM Order o + JOIN o.orderedProducts op + JOIN op.product p + WHERE p.category.id = :categoryId + AND o.createdAt BETWEEN :startDate AND :endDate + GROUP BY p.category + """) + Page findCategoryMonthlySales( + @Param("categoryId") Long categoryId, + @Param("startDate") LocalDateTime startDate, + @Param("endDate") LocalDateTime endDate, + Pageable pageable + ); + } diff --git a/src/main/java/poomasi/domain/order/service/OrderService.java b/src/main/java/poomasi/domain/order/service/OrderService.java index 7fec2874..276b98a3 100644 --- a/src/main/java/poomasi/domain/order/service/OrderService.java +++ b/src/main/java/poomasi/domain/order/service/OrderService.java @@ -5,11 +5,13 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import poomasi.domain.admin.statistics.dto.response.CategoryMonthlySalesResponse; import poomasi.domain.auth.security.userdetail.UserDetailsImpl; import poomasi.domain.member.entity.Member; import poomasi.domain.order.dto.request.PreOrderRequest; @@ -33,6 +35,7 @@ import poomasi.payment.util.PaymentUtil; import java.math.BigDecimal; +import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @@ -212,6 +215,13 @@ private Member getMember() { return member; } + public List getOrdersByUpdateAtBetween(LocalDateTime startDate, LocalDateTime endDate) { + return orderRepository.findAllByUpdateAtBetween(startDate, endDate); + } + + public Page getCategoryMonthlySales(Long categoryId, LocalDateTime startDate, LocalDateTime endDate, Pageable pageable){ + return orderRepository.findCategoryMonthlySales(categoryId, startDate, endDate, pageable); + } }