diff --git a/backend/src/main/java/com/petshop/backend/controller/AnalyticsController.java b/backend/src/main/java/com/petshop/backend/controller/AnalyticsController.java index 9d44f2d4..fedf5a16 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AnalyticsController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AnalyticsController.java @@ -5,12 +5,15 @@ import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.service.AnalyticsService; import com.petshop.backend.util.AuthenticationHelper; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; +import java.time.LocalDate; + @RestController @RequestMapping("/api/v1/analytics") @PreAuthorize("hasAnyRole('ADMIN', 'STAFF')") @@ -27,7 +30,11 @@ public class AnalyticsController { @GetMapping("/dashboard") public ResponseEntity getDashboard( @RequestParam(defaultValue = "30") int days, - @RequestParam(defaultValue = "10") int top) { + @RequestParam(defaultValue = "10") int top, + @RequestParam(required = false) String paymentMethod, + @RequestParam(required = false) Long storeId, + @RequestParam(required = false) String channel, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) { if (days < 1 || days > 365) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "days must be between 1 and 365"); } @@ -35,6 +42,7 @@ public class AnalyticsController { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "top must be between 1 and 50"); } User user = AuthenticationHelper.getAuthenticatedUser(userRepository); - return ResponseEntity.ok(analyticsService.getDashboardData(days, top, user)); + java.time.LocalDateTime endDateTime = endDate != null ? endDate.plusDays(1).atStartOfDay() : null; + return ResponseEntity.ok(analyticsService.getDashboardData(days, top, user, paymentMethod, storeId, channel, endDateTime)); } } diff --git a/backend/src/main/java/com/petshop/backend/repository/SaleRepository.java b/backend/src/main/java/com/petshop/backend/repository/SaleRepository.java index bf8c050e..4313ba51 100644 --- a/backend/src/main/java/com/petshop/backend/repository/SaleRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/SaleRepository.java @@ -3,6 +3,7 @@ package com.petshop.backend.repository; import com.petshop.backend.entity.Sale; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import java.time.LocalDateTime; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -26,6 +27,19 @@ public interface SaleRepository extends JpaRepository { "(:customerId IS NULL OR s.customer.id = :customerId)") Page searchSales(@Param("q") String query, @Param("paymentMethod") String paymentMethod, @Param("storeId") Long storeId, @Param("isRefund") Boolean isRefund, @Param("customerId") Long customerId, Pageable pageable); + long countByCoupon_CouponId(Long couponId); + + @Query("SELECT s FROM Sale s WHERE s.saleDate > :startDate " + + "AND (:endDate IS NULL OR s.saleDate <= :endDate) " + + "AND (:paymentMethod IS NULL OR LOWER(s.paymentMethod) = LOWER(:paymentMethod)) " + + "AND (:storeId IS NULL OR s.store.storeId = :storeId) " + + "AND (:channel IS NULL OR s.channel = :channel)") + List findForAnalytics(@Param("startDate") LocalDateTime startDate, + @Param("endDate") LocalDateTime endDate, + @Param("paymentMethod") String paymentMethod, + @Param("storeId") Long storeId, + @Param("channel") String channel); + List findByOriginalSaleSaleId(Long originalSaleId); Optional findByCartCartId(Long cartId); diff --git a/backend/src/main/java/com/petshop/backend/service/AnalyticsService.java b/backend/src/main/java/com/petshop/backend/service/AnalyticsService.java index f4841228..7a69357b 100644 --- a/backend/src/main/java/com/petshop/backend/service/AnalyticsService.java +++ b/backend/src/main/java/com/petshop/backend/service/AnalyticsService.java @@ -34,10 +34,17 @@ public class AnalyticsService { @Transactional(readOnly = true) public DashboardResponse getDashboardData(int days, int top, User user) { + return getDashboardData(days, top, user, null, null, null, null); + } + + @Transactional(readOnly = true) + public DashboardResponse getDashboardData(int days, int top, User user, + String paymentMethod, Long storeId, + String channel, LocalDateTime endDate) { LocalDateTime startDate = LocalDateTime.now().minusDays(days); - List sales = saleRepository.findAll().stream() - .filter(sale -> sale.getSaleDate().isAfter(startDate)) + List sales = saleRepository.findForAnalytics(startDate, endDate, paymentMethod, storeId, channel) + .stream() .filter(sale -> includeSaleForUser(sale, user)) .collect(Collectors.toList()); diff --git a/backend/src/main/java/com/petshop/backend/service/CartService.java b/backend/src/main/java/com/petshop/backend/service/CartService.java index 8fd8c053..f2e304d2 100644 --- a/backend/src/main/java/com/petshop/backend/service/CartService.java +++ b/backend/src/main/java/com/petshop/backend/service/CartService.java @@ -206,6 +206,13 @@ public class CartService { throw new BusinessException("Minimum order amount of $" + coupon.getMinOrderAmount() + " required"); } + if (coupon.getUsageLimit() != null) { + long used = saleRepository.countByCoupon_CouponId(coupon.getCouponId()); + if (used >= coupon.getUsageLimit()) { + throw new BusinessException("Coupon usage limit has been reached"); + } + } + cart.setCoupon(coupon); recalculate(cart);