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 6c7b9b9e..002733fa 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AnalyticsController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AnalyticsController.java @@ -1,26 +1,32 @@ package com.petshop.backend.controller; import com.petshop.backend.dto.analytics.DashboardResponse; +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.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/analytics") -@PreAuthorize("hasRole('ADMIN')") +@PreAuthorize("hasAnyRole('ADMIN', 'STAFF')") public class AnalyticsController { private final AnalyticsService analyticsService; + private final UserRepository userRepository; - public AnalyticsController(AnalyticsService analyticsService) { + public AnalyticsController(AnalyticsService analyticsService, UserRepository userRepository) { this.analyticsService = analyticsService; + this.userRepository = userRepository; } @GetMapping("/dashboard") public ResponseEntity getDashboard( @RequestParam(defaultValue = "30") int days, @RequestParam(defaultValue = "10") int top) { - return ResponseEntity.ok(analyticsService.getDashboardData(days, top)); + User user = AuthenticationHelper.getAuthenticatedUser(userRepository); + return ResponseEntity.ok(analyticsService.getDashboardData(days, top, user)); } } diff --git a/backend/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java b/backend/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java index c884d24c..56788f77 100644 --- a/backend/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java @@ -9,15 +9,19 @@ public class DashboardResponse { private InventorySummary inventorySummary; private List topProducts; private List dailySales; + private List paymentMethods; + private List employeePerformance; public DashboardResponse() { } - public DashboardResponse(SalesSummary salesSummary, InventorySummary inventorySummary, List topProducts, List dailySales) { + public DashboardResponse(SalesSummary salesSummary, InventorySummary inventorySummary, List topProducts, List dailySales, List paymentMethods, List employeePerformance) { this.salesSummary = salesSummary; this.inventorySummary = inventorySummary; this.topProducts = topProducts; this.dailySales = dailySales; + this.paymentMethods = paymentMethods; + this.employeePerformance = employeePerformance; } public SalesSummary getSalesSummary() { @@ -52,17 +56,33 @@ public class DashboardResponse { this.dailySales = dailySales; } + public List getPaymentMethods() { + return paymentMethods; + } + + public void setPaymentMethods(List paymentMethods) { + this.paymentMethods = paymentMethods; + } + + public List getEmployeePerformance() { + return employeePerformance; + } + + public void setEmployeePerformance(List employeePerformance) { + this.employeePerformance = employeePerformance; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DashboardResponse that = (DashboardResponse) o; - return Objects.equals(salesSummary, that.salesSummary) && Objects.equals(inventorySummary, that.inventorySummary) && Objects.equals(topProducts, that.topProducts) && Objects.equals(dailySales, that.dailySales); + return Objects.equals(salesSummary, that.salesSummary) && Objects.equals(inventorySummary, that.inventorySummary) && Objects.equals(topProducts, that.topProducts) && Objects.equals(dailySales, that.dailySales) && Objects.equals(paymentMethods, that.paymentMethods) && Objects.equals(employeePerformance, that.employeePerformance); } @Override public int hashCode() { - return Objects.hash(salesSummary, inventorySummary, topProducts, dailySales); + return Objects.hash(salesSummary, inventorySummary, topProducts, dailySales, paymentMethods, employeePerformance); } @Override @@ -72,6 +92,8 @@ public class DashboardResponse { ", inventorySummary=" + inventorySummary + ", topProducts=" + topProducts + ", dailySales=" + dailySales + + ", paymentMethods=" + paymentMethods + + ", employeePerformance=" + employeePerformance + '}'; } @@ -80,15 +102,17 @@ public class DashboardResponse { private Long totalSales; private BigDecimal totalRefunds; private Long totalRefundCount; + private Long totalItemsSold; public SalesSummary() { } - public SalesSummary(BigDecimal totalRevenue, Long totalSales, BigDecimal totalRefunds, Long totalRefundCount) { + public SalesSummary(BigDecimal totalRevenue, Long totalSales, BigDecimal totalRefunds, Long totalRefundCount, Long totalItemsSold) { this.totalRevenue = totalRevenue; this.totalSales = totalSales; this.totalRefunds = totalRefunds; this.totalRefundCount = totalRefundCount; + this.totalItemsSold = totalItemsSold; } public BigDecimal getTotalRevenue() { @@ -123,17 +147,25 @@ public class DashboardResponse { this.totalRefundCount = totalRefundCount; } + public Long getTotalItemsSold() { + return totalItemsSold; + } + + public void setTotalItemsSold(Long totalItemsSold) { + this.totalItemsSold = totalItemsSold; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SalesSummary that = (SalesSummary) o; - return Objects.equals(totalRevenue, that.totalRevenue) && Objects.equals(totalSales, that.totalSales) && Objects.equals(totalRefunds, that.totalRefunds) && Objects.equals(totalRefundCount, that.totalRefundCount); + return Objects.equals(totalRevenue, that.totalRevenue) && Objects.equals(totalSales, that.totalSales) && Objects.equals(totalRefunds, that.totalRefunds) && Objects.equals(totalRefundCount, that.totalRefundCount) && Objects.equals(totalItemsSold, that.totalItemsSold); } @Override public int hashCode() { - return Objects.hash(totalRevenue, totalSales, totalRefunds, totalRefundCount); + return Objects.hash(totalRevenue, totalSales, totalRefunds, totalRefundCount, totalItemsSold); } @Override @@ -143,10 +175,69 @@ public class DashboardResponse { ", totalSales=" + totalSales + ", totalRefunds=" + totalRefunds + ", totalRefundCount=" + totalRefundCount + + ", totalItemsSold=" + totalItemsSold + '}'; } } + public static class PaymentMethodData { + private String paymentMethod; + private Long count; + + public PaymentMethodData() { + } + + public PaymentMethodData(String paymentMethod, Long count) { + this.paymentMethod = paymentMethod; + this.count = count; + } + + public String getPaymentMethod() { + return paymentMethod; + } + + public void setPaymentMethod(String paymentMethod) { + this.paymentMethod = paymentMethod; + } + + public Long getCount() { + return count; + } + + public void setCount(Long count) { + this.count = count; + } +} + + public static class EmployeePerformanceData { + private String employeeName; + private BigDecimal revenue; + + public EmployeePerformanceData() { + } + + public EmployeePerformanceData(String employeeName, BigDecimal revenue) { + this.employeeName = employeeName; + this.revenue = revenue; + } + + public String getEmployeeName() { + return employeeName; + } + + public void setEmployeeName(String employeeName) { + this.employeeName = employeeName; + } + + public BigDecimal getRevenue() { + return revenue; + } + + public void setRevenue(BigDecimal revenue) { + this.revenue = revenue; + } +} + public static class InventorySummary { private Long totalProducts; private Long lowStockProducts; 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 32e6a5a7..cef0b620 100644 --- a/backend/src/main/java/com/petshop/backend/service/AnalyticsService.java +++ b/backend/src/main/java/com/petshop/backend/service/AnalyticsService.java @@ -1,9 +1,12 @@ package com.petshop.backend.service; import com.petshop.backend.dto.analytics.DashboardResponse; +import com.petshop.backend.entity.Employee; import com.petshop.backend.entity.Inventory; import com.petshop.backend.entity.Product; import com.petshop.backend.entity.Sale; +import com.petshop.backend.entity.User; +import com.petshop.backend.repository.EmployeeRepository; import com.petshop.backend.repository.InventoryRepository; import com.petshop.backend.repository.ProductRepository; import com.petshop.backend.repository.SaleRepository; @@ -23,28 +26,33 @@ public class AnalyticsService { private final SaleRepository saleRepository; private final InventoryRepository inventoryRepository; private final ProductRepository productRepository; + private final EmployeeRepository employeeRepository; public AnalyticsService(SaleRepository saleRepository, - InventoryRepository inventoryRepository, ProductRepository productRepository) { + InventoryRepository inventoryRepository, ProductRepository productRepository, EmployeeRepository employeeRepository) { this.saleRepository = saleRepository; this.inventoryRepository = inventoryRepository; this.productRepository = productRepository; + this.employeeRepository = employeeRepository; } @Transactional(readOnly = true) - public DashboardResponse getDashboardData(int days, int top) { + public DashboardResponse getDashboardData(int days, int top, User user) { LocalDateTime startDate = LocalDateTime.now().minusDays(days); List sales = saleRepository.findAll().stream() .filter(sale -> sale.getSaleDate().isAfter(startDate)) + .filter(sale -> includeSaleForUser(sale, user)) .collect(Collectors.toList()); DashboardResponse.SalesSummary salesSummary = calculateSalesSummary(sales); - DashboardResponse.InventorySummary inventorySummary = calculateInventorySummary(); + DashboardResponse.InventorySummary inventorySummary = user.getRole() == User.Role.ADMIN ? calculateInventorySummary() : null; List topProducts = calculateTopProducts(sales, top); List dailySales = calculateDailySales(sales, days); + List paymentMethods = calculatePaymentMethods(sales); + List employeePerformance = calculateEmployeePerformance(sales, user); - return new DashboardResponse(salesSummary, inventorySummary, topProducts, dailySales); + return new DashboardResponse(salesSummary, inventorySummary, topProducts, dailySales, paymentMethods, employeePerformance); } private DashboardResponse.SalesSummary calculateSalesSummary(List sales) { @@ -66,7 +74,13 @@ public class AnalyticsService { .filter(Sale::getIsRefund) .count(); - return new DashboardResponse.SalesSummary(totalRevenue, totalSales, totalRefunds, totalRefundCount); + Long totalItemsSold = sales.stream() + .filter(sale -> !sale.getIsRefund()) + .flatMap(sale -> sale.getItems().stream()) + .mapToLong(item -> item.getQuantity()) + .sum(); + + return new DashboardResponse.SalesSummary(totalRevenue, totalSales, totalRefunds, totalRefundCount, totalItemsSold); } private DashboardResponse.InventorySummary calculateInventorySummary() { @@ -138,4 +152,50 @@ public class AnalyticsService { return new ArrayList<>(dailySalesMap.values()); } + + private List calculatePaymentMethods(List sales) { + return sales.stream() + .filter(sale -> !sale.getIsRefund()) + .collect(Collectors.groupingBy( + sale -> sale.getPaymentMethod() == null ? "Unknown" : sale.getPaymentMethod(), + TreeMap::new, + Collectors.counting())) + .entrySet().stream() + .map(entry -> new DashboardResponse.PaymentMethodData(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + } + + private List calculateEmployeePerformance(List sales, User user) { + Map employeeRevenue = new TreeMap<>(); + + for (Sale sale : sales) { + if (sale.getIsRefund()) { + continue; + } + String employeeName = sale.getEmployee().getFirstName() + " " + sale.getEmployee().getLastName(); + employeeRevenue.merge(employeeName, sale.getTotalAmount(), BigDecimal::add); + } + + if (user.getRole() == User.Role.STAFF && employeeRevenue.isEmpty()) { + Employee employee = employeeRepository.findByUserId(user.getId()).orElse(null); + if (employee != null) { + String employeeName = employee.getFirstName() + " " + employee.getLastName(); + employeeRevenue.put(employeeName, BigDecimal.ZERO); + } + } + + return employeeRevenue.entrySet().stream() + .map(entry -> new DashboardResponse.EmployeePerformanceData(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + } + + private boolean includeSaleForUser(Sale sale, User user) { + if (user.getRole() == User.Role.ADMIN) { + return true; + } + if (user.getRole() == User.Role.STAFF) { + return sale.getEmployee() != null && sale.getEmployee().getUserId() != null && sale.getEmployee().getUserId().equals(user.getId()); + } + return false; + } }