Merge pull request #57 from RecentRunner/staff-self-analytics
Staff analytics
This commit was merged in pull request #57.
This commit is contained in:
@@ -1,26 +1,40 @@
|
|||||||
package com.petshop.backend.controller;
|
package com.petshop.backend.controller;
|
||||||
|
|
||||||
import com.petshop.backend.dto.analytics.DashboardResponse;
|
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.service.AnalyticsService;
|
||||||
|
import com.petshop.backend.util.AuthenticationHelper;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/analytics")
|
@RequestMapping("/api/v1/analytics")
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
@PreAuthorize("hasAnyRole('ADMIN', 'STAFF')")
|
||||||
public class AnalyticsController {
|
public class AnalyticsController {
|
||||||
|
|
||||||
private final AnalyticsService analyticsService;
|
private final AnalyticsService analyticsService;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
public AnalyticsController(AnalyticsService analyticsService) {
|
public AnalyticsController(AnalyticsService analyticsService, UserRepository userRepository) {
|
||||||
this.analyticsService = analyticsService;
|
this.analyticsService = analyticsService;
|
||||||
|
this.userRepository = userRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/dashboard")
|
@GetMapping("/dashboard")
|
||||||
public ResponseEntity<DashboardResponse> getDashboard(
|
public ResponseEntity<DashboardResponse> getDashboard(
|
||||||
@RequestParam(defaultValue = "30") int days,
|
@RequestParam(defaultValue = "30") int days,
|
||||||
@RequestParam(defaultValue = "10") int top) {
|
@RequestParam(defaultValue = "10") int top) {
|
||||||
return ResponseEntity.ok(analyticsService.getDashboardData(days, top));
|
if (days < 1 || days > 365) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "days must be between 1 and 365");
|
||||||
|
}
|
||||||
|
if (top < 1 || top > 50) {
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,19 @@ public class DashboardResponse {
|
|||||||
private InventorySummary inventorySummary;
|
private InventorySummary inventorySummary;
|
||||||
private List<TopProduct> topProducts;
|
private List<TopProduct> topProducts;
|
||||||
private List<DailySales> dailySales;
|
private List<DailySales> dailySales;
|
||||||
|
private List<PaymentMethodData> paymentMethods;
|
||||||
|
private List<EmployeePerformanceData> employeePerformance;
|
||||||
|
|
||||||
public DashboardResponse() {
|
public DashboardResponse() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public DashboardResponse(SalesSummary salesSummary, InventorySummary inventorySummary, List<TopProduct> topProducts, List<DailySales> dailySales) {
|
public DashboardResponse(SalesSummary salesSummary, InventorySummary inventorySummary, List<TopProduct> topProducts, List<DailySales> dailySales, List<PaymentMethodData> paymentMethods, List<EmployeePerformanceData> employeePerformance) {
|
||||||
this.salesSummary = salesSummary;
|
this.salesSummary = salesSummary;
|
||||||
this.inventorySummary = inventorySummary;
|
this.inventorySummary = inventorySummary;
|
||||||
this.topProducts = topProducts;
|
this.topProducts = topProducts;
|
||||||
this.dailySales = dailySales;
|
this.dailySales = dailySales;
|
||||||
|
this.paymentMethods = paymentMethods;
|
||||||
|
this.employeePerformance = employeePerformance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SalesSummary getSalesSummary() {
|
public SalesSummary getSalesSummary() {
|
||||||
@@ -52,17 +56,33 @@ public class DashboardResponse {
|
|||||||
this.dailySales = dailySales;
|
this.dailySales = dailySales;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<PaymentMethodData> getPaymentMethods() {
|
||||||
|
return paymentMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPaymentMethods(List<PaymentMethodData> paymentMethods) {
|
||||||
|
this.paymentMethods = paymentMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EmployeePerformanceData> getEmployeePerformance() {
|
||||||
|
return employeePerformance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmployeePerformance(List<EmployeePerformanceData> employeePerformance) {
|
||||||
|
this.employeePerformance = employeePerformance;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
DashboardResponse that = (DashboardResponse) o;
|
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
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(salesSummary, inventorySummary, topProducts, dailySales);
|
return Objects.hash(salesSummary, inventorySummary, topProducts, dailySales, paymentMethods, employeePerformance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -72,6 +92,8 @@ public class DashboardResponse {
|
|||||||
", inventorySummary=" + inventorySummary +
|
", inventorySummary=" + inventorySummary +
|
||||||
", topProducts=" + topProducts +
|
", topProducts=" + topProducts +
|
||||||
", dailySales=" + dailySales +
|
", dailySales=" + dailySales +
|
||||||
|
", paymentMethods=" + paymentMethods +
|
||||||
|
", employeePerformance=" + employeePerformance +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,15 +102,17 @@ public class DashboardResponse {
|
|||||||
private Long totalSales;
|
private Long totalSales;
|
||||||
private BigDecimal totalRefunds;
|
private BigDecimal totalRefunds;
|
||||||
private Long totalRefundCount;
|
private Long totalRefundCount;
|
||||||
|
private Long totalItemsSold;
|
||||||
|
|
||||||
public SalesSummary() {
|
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.totalRevenue = totalRevenue;
|
||||||
this.totalSales = totalSales;
|
this.totalSales = totalSales;
|
||||||
this.totalRefunds = totalRefunds;
|
this.totalRefunds = totalRefunds;
|
||||||
this.totalRefundCount = totalRefundCount;
|
this.totalRefundCount = totalRefundCount;
|
||||||
|
this.totalItemsSold = totalItemsSold;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getTotalRevenue() {
|
public BigDecimal getTotalRevenue() {
|
||||||
@@ -123,17 +147,25 @@ public class DashboardResponse {
|
|||||||
this.totalRefundCount = totalRefundCount;
|
this.totalRefundCount = totalRefundCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getTotalItemsSold() {
|
||||||
|
return totalItemsSold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotalItemsSold(Long totalItemsSold) {
|
||||||
|
this.totalItemsSold = totalItemsSold;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
SalesSummary that = (SalesSummary) o;
|
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
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(totalRevenue, totalSales, totalRefunds, totalRefundCount);
|
return Objects.hash(totalRevenue, totalSales, totalRefunds, totalRefundCount, totalItemsSold);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -143,10 +175,69 @@ public class DashboardResponse {
|
|||||||
", totalSales=" + totalSales +
|
", totalSales=" + totalSales +
|
||||||
", totalRefunds=" + totalRefunds +
|
", totalRefunds=" + totalRefunds +
|
||||||
", totalRefundCount=" + totalRefundCount +
|
", 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 {
|
public static class InventorySummary {
|
||||||
private Long totalProducts;
|
private Long totalProducts;
|
||||||
private Long lowStockProducts;
|
private Long lowStockProducts;
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package com.petshop.backend.service;
|
package com.petshop.backend.service;
|
||||||
|
|
||||||
import com.petshop.backend.dto.analytics.DashboardResponse;
|
import com.petshop.backend.dto.analytics.DashboardResponse;
|
||||||
|
import com.petshop.backend.entity.Employee;
|
||||||
import com.petshop.backend.entity.Inventory;
|
import com.petshop.backend.entity.Inventory;
|
||||||
import com.petshop.backend.entity.Product;
|
import com.petshop.backend.entity.Product;
|
||||||
import com.petshop.backend.entity.Sale;
|
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.InventoryRepository;
|
||||||
import com.petshop.backend.repository.ProductRepository;
|
import com.petshop.backend.repository.ProductRepository;
|
||||||
import com.petshop.backend.repository.SaleRepository;
|
import com.petshop.backend.repository.SaleRepository;
|
||||||
@@ -23,28 +26,33 @@ public class AnalyticsService {
|
|||||||
private final SaleRepository saleRepository;
|
private final SaleRepository saleRepository;
|
||||||
private final InventoryRepository inventoryRepository;
|
private final InventoryRepository inventoryRepository;
|
||||||
private final ProductRepository productRepository;
|
private final ProductRepository productRepository;
|
||||||
|
private final EmployeeRepository employeeRepository;
|
||||||
|
|
||||||
public AnalyticsService(SaleRepository saleRepository,
|
public AnalyticsService(SaleRepository saleRepository,
|
||||||
InventoryRepository inventoryRepository, ProductRepository productRepository) {
|
InventoryRepository inventoryRepository, ProductRepository productRepository, EmployeeRepository employeeRepository) {
|
||||||
this.saleRepository = saleRepository;
|
this.saleRepository = saleRepository;
|
||||||
this.inventoryRepository = inventoryRepository;
|
this.inventoryRepository = inventoryRepository;
|
||||||
this.productRepository = productRepository;
|
this.productRepository = productRepository;
|
||||||
|
this.employeeRepository = employeeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@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);
|
LocalDateTime startDate = LocalDateTime.now().minusDays(days);
|
||||||
|
|
||||||
List<Sale> sales = saleRepository.findAll().stream()
|
List<Sale> sales = saleRepository.findAll().stream()
|
||||||
.filter(sale -> sale.getSaleDate().isAfter(startDate))
|
.filter(sale -> sale.getSaleDate().isAfter(startDate))
|
||||||
|
.filter(sale -> includeSaleForUser(sale, user))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
DashboardResponse.SalesSummary salesSummary = calculateSalesSummary(sales);
|
DashboardResponse.SalesSummary salesSummary = calculateSalesSummary(sales);
|
||||||
DashboardResponse.InventorySummary inventorySummary = calculateInventorySummary();
|
DashboardResponse.InventorySummary inventorySummary = user.getRole() == User.Role.ADMIN ? calculateInventorySummary() : null;
|
||||||
List<DashboardResponse.TopProduct> topProducts = calculateTopProducts(sales, top);
|
List<DashboardResponse.TopProduct> topProducts = calculateTopProducts(sales, top);
|
||||||
List<DashboardResponse.DailySales> dailySales = calculateDailySales(sales, days);
|
List<DashboardResponse.DailySales> dailySales = calculateDailySales(sales, days);
|
||||||
|
List<DashboardResponse.PaymentMethodData> paymentMethods = calculatePaymentMethods(sales);
|
||||||
|
List<DashboardResponse.EmployeePerformanceData> 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<Sale> sales) {
|
private DashboardResponse.SalesSummary calculateSalesSummary(List<Sale> sales) {
|
||||||
@@ -66,7 +74,13 @@ public class AnalyticsService {
|
|||||||
.filter(Sale::getIsRefund)
|
.filter(Sale::getIsRefund)
|
||||||
.count();
|
.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() {
|
private DashboardResponse.InventorySummary calculateInventorySummary() {
|
||||||
@@ -93,6 +107,9 @@ public class AnalyticsService {
|
|||||||
Map<Long, DashboardResponse.TopProduct> productSalesMap = new HashMap<>();
|
Map<Long, DashboardResponse.TopProduct> productSalesMap = new HashMap<>();
|
||||||
|
|
||||||
for (Sale sale : sales) {
|
for (Sale sale : sales) {
|
||||||
|
if (sale.getIsRefund()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for (var item : sale.getItems()) {
|
for (var item : sale.getItems()) {
|
||||||
Long productId = item.getProduct().getProdId();
|
Long productId = item.getProduct().getProdId();
|
||||||
String productName = item.getProduct().getProdName();
|
String productName = item.getProduct().getProdName();
|
||||||
@@ -128,6 +145,9 @@ public class AnalyticsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (Sale sale : sales) {
|
for (Sale sale : sales) {
|
||||||
|
if (sale.getIsRefund()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
LocalDate saleDate = sale.getSaleDate().toLocalDate();
|
LocalDate saleDate = sale.getSaleDate().toLocalDate();
|
||||||
if (dailySalesMap.containsKey(saleDate)) {
|
if (dailySalesMap.containsKey(saleDate)) {
|
||||||
DashboardResponse.DailySales dailySale = dailySalesMap.get(saleDate);
|
DashboardResponse.DailySales dailySale = dailySalesMap.get(saleDate);
|
||||||
@@ -138,4 +158,50 @@ public class AnalyticsService {
|
|||||||
|
|
||||||
return new ArrayList<>(dailySalesMap.values());
|
return new ArrayList<>(dailySalesMap.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<DashboardResponse.PaymentMethodData> calculatePaymentMethods(List<Sale> 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<DashboardResponse.EmployeePerformanceData> calculateEmployeePerformance(List<Sale> sales, User user) {
|
||||||
|
Map<String, BigDecimal> 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ public class DashboardResponse {
|
|||||||
private InventorySummary inventorySummary;
|
private InventorySummary inventorySummary;
|
||||||
private List<TopProduct> topProducts;
|
private List<TopProduct> topProducts;
|
||||||
private List<DailySales> dailySales;
|
private List<DailySales> dailySales;
|
||||||
|
private List<PaymentMethodData> paymentMethods;
|
||||||
|
private List<EmployeePerformanceData> employeePerformance;
|
||||||
|
|
||||||
public DashboardResponse() {
|
public DashboardResponse() {
|
||||||
}
|
}
|
||||||
@@ -44,11 +46,28 @@ public class DashboardResponse {
|
|||||||
this.dailySales = dailySales;
|
this.dailySales = dailySales;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<PaymentMethodData> getPaymentMethods() {
|
||||||
|
return paymentMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPaymentMethods(List<PaymentMethodData> paymentMethods) {
|
||||||
|
this.paymentMethods = paymentMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EmployeePerformanceData> getEmployeePerformance() {
|
||||||
|
return employeePerformance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmployeePerformance(List<EmployeePerformanceData> employeePerformance) {
|
||||||
|
this.employeePerformance = employeePerformance;
|
||||||
|
}
|
||||||
|
|
||||||
public static class SalesSummary {
|
public static class SalesSummary {
|
||||||
private BigDecimal totalRevenue;
|
private BigDecimal totalRevenue;
|
||||||
private Long totalSales;
|
private Long totalSales;
|
||||||
private BigDecimal totalRefunds;
|
private BigDecimal totalRefunds;
|
||||||
private Long totalRefundCount;
|
private Long totalRefundCount;
|
||||||
|
private Long totalItemsSold;
|
||||||
|
|
||||||
public SalesSummary() {
|
public SalesSummary() {
|
||||||
}
|
}
|
||||||
@@ -84,6 +103,14 @@ public class DashboardResponse {
|
|||||||
public void setTotalRefundCount(Long totalRefundCount) {
|
public void setTotalRefundCount(Long totalRefundCount) {
|
||||||
this.totalRefundCount = totalRefundCount;
|
this.totalRefundCount = totalRefundCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getTotalItemsSold() {
|
||||||
|
return totalItemsSold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotalItemsSold(Long totalItemsSold) {
|
||||||
|
this.totalItemsSold = totalItemsSold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class InventorySummary {
|
public static class InventorySummary {
|
||||||
@@ -118,4 +145,46 @@ public class DashboardResponse {
|
|||||||
this.outOfStockProducts = outOfStockProducts;
|
this.outOfStockProducts = outOfStockProducts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class PaymentMethodData {
|
||||||
|
private String paymentMethod;
|
||||||
|
private Long 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 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,4 +92,8 @@ public class UserSession {
|
|||||||
public boolean isAdmin() {
|
public boolean isAdmin() {
|
||||||
return Role.ADMIN.equals(role);
|
return Role.ADMIN.equals(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isStaff() {
|
||||||
|
return Role.STAFF.equals(role);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import javafx.scene.control.Label;
|
|||||||
import org.example.petshopdesktop.api.dto.analytics.DailySales;
|
import org.example.petshopdesktop.api.dto.analytics.DailySales;
|
||||||
import org.example.petshopdesktop.api.dto.analytics.DashboardResponse;
|
import org.example.petshopdesktop.api.dto.analytics.DashboardResponse;
|
||||||
import org.example.petshopdesktop.api.dto.analytics.TopProduct;
|
import org.example.petshopdesktop.api.dto.analytics.TopProduct;
|
||||||
|
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
|
||||||
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
|
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
|
||||||
import org.example.petshopdesktop.api.endpoints.AnalyticsApi;
|
import org.example.petshopdesktop.api.endpoints.AnalyticsApi;
|
||||||
import org.example.petshopdesktop.api.endpoints.SaleApi;
|
import org.example.petshopdesktop.api.endpoints.SaleApi;
|
||||||
|
import org.example.petshopdesktop.auth.UserSession;
|
||||||
import org.example.petshopdesktop.util.ActivityLogger;
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -127,16 +129,17 @@ public class AnalyticsController {
|
|||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
DashboardResponse dashboard = AnalyticsApi.getInstance().getDashboard(30, 10);
|
DashboardResponse dashboard = AnalyticsApi.getInstance().getDashboard(30, 10);
|
||||||
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, Integer.MAX_VALUE, null);
|
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
try {
|
try {
|
||||||
|
boolean isAdmin = UserSession.getInstance().isAdmin();
|
||||||
loadSummaryData(dashboard);
|
loadSummaryData(dashboard);
|
||||||
loadSalesOverTime(dashboard);
|
loadSalesOverTime(dashboard);
|
||||||
loadTopProductsByRevenue(dashboard);
|
loadTopProductsByRevenue(dashboard);
|
||||||
loadTopProductsByQuantity(dashboard);
|
loadTopProductsByQuantity(dashboard);
|
||||||
loadPaymentMethodDistribution(sales);
|
loadPaymentMethodDistribution(dashboard);
|
||||||
loadEmployeePerformance(sales);
|
loadEmployeePerformance(dashboard, isAdmin);
|
||||||
|
applyRoleVisibility(isAdmin);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
|
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
|
||||||
lblError.setText("Error loading analytics data. Please try again.");
|
lblError.setText("Error loading analytics data. Please try again.");
|
||||||
@@ -144,6 +147,31 @@ public class AnalyticsController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
if (UserSession.getInstance().isStaff()) {
|
||||||
|
try {
|
||||||
|
DashboardResponse fallback = buildStaffFallbackDashboard();
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
try {
|
||||||
|
loadSummaryData(fallback);
|
||||||
|
loadSalesOverTime(fallback);
|
||||||
|
loadTopProductsByRevenue(fallback);
|
||||||
|
loadTopProductsByQuantity(fallback);
|
||||||
|
loadPaymentMethodDistribution(fallback);
|
||||||
|
loadEmployeePerformance(fallback, false);
|
||||||
|
applyRoleVisibility(false);
|
||||||
|
lblError.setVisible(false);
|
||||||
|
} catch (Exception inner) {
|
||||||
|
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", inner, "Rendering fallback analytics data");
|
||||||
|
lblError.setText("Error loading analytics data. Please try again.");
|
||||||
|
lblError.setVisible(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} catch (Exception fallbackError) {
|
||||||
|
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", fallbackError, "Loading fallback analytics data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
|
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
|
||||||
lblError.setText("Error loading analytics data. Please try again.");
|
lblError.setText("Error loading analytics data. Please try again.");
|
||||||
@@ -157,15 +185,12 @@ public class AnalyticsController {
|
|||||||
if (dashboard != null) {
|
if (dashboard != null) {
|
||||||
BigDecimal totalRevenue = BigDecimal.ZERO;
|
BigDecimal totalRevenue = BigDecimal.ZERO;
|
||||||
Long totalSales = 0L;
|
Long totalSales = 0L;
|
||||||
Long totalProducts = 0L;
|
|
||||||
|
|
||||||
if (dashboard.getSalesSummary() != null) {
|
if (dashboard.getSalesSummary() != null) {
|
||||||
totalRevenue = dashboard.getSalesSummary().getTotalRevenue() != null ? dashboard.getSalesSummary().getTotalRevenue() : BigDecimal.ZERO;
|
totalRevenue = dashboard.getSalesSummary().getTotalRevenue() != null ? dashboard.getSalesSummary().getTotalRevenue() : BigDecimal.ZERO;
|
||||||
totalSales = dashboard.getSalesSummary().getTotalSales() != null ? dashboard.getSalesSummary().getTotalSales() : 0L;
|
totalSales = dashboard.getSalesSummary().getTotalSales() != null ? dashboard.getSalesSummary().getTotalSales() : 0L;
|
||||||
}
|
lblTotalItems.setText(wholeNumber.format(dashboard.getSalesSummary().getTotalItemsSold() != null ? dashboard.getSalesSummary().getTotalItemsSold() : 0L));
|
||||||
|
} else {
|
||||||
if (dashboard.getInventorySummary() != null) {
|
lblTotalItems.setText(wholeNumber.format(0));
|
||||||
totalProducts = dashboard.getInventorySummary().getTotalProducts() != null ? dashboard.getInventorySummary().getTotalProducts() : 0L;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lblTotalRevenue.setText(currency.format(totalRevenue));
|
lblTotalRevenue.setText(currency.format(totalRevenue));
|
||||||
@@ -176,7 +201,6 @@ public class AnalyticsController {
|
|||||||
avgTransaction = totalRevenue.divide(BigDecimal.valueOf(totalSales), 2, RoundingMode.HALF_UP);
|
avgTransaction = totalRevenue.divide(BigDecimal.valueOf(totalSales), 2, RoundingMode.HALF_UP);
|
||||||
}
|
}
|
||||||
lblAvgTransaction.setText(currency.format(avgTransaction));
|
lblAvgTransaction.setText(currency.format(avgTransaction));
|
||||||
lblTotalItems.setText(wholeNumber.format(totalProducts));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,24 +267,14 @@ public class AnalyticsController {
|
|||||||
applyBarChartColor(chartTopQuantity, QUANTITY_COLOR);
|
applyBarChartColor(chartTopQuantity, QUANTITY_COLOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadPaymentMethodDistribution(List<SaleResponse> sales) throws Exception {
|
private void loadPaymentMethodDistribution(DashboardResponse dashboard) throws Exception {
|
||||||
Map<String, Long> paymentMethodCount = sales.stream()
|
|
||||||
.filter(sale -> sale.getIsRefund() == null || !sale.getIsRefund())
|
|
||||||
.collect(Collectors.groupingBy(
|
|
||||||
sale -> sale.getPaymentMethod() != null ? sale.getPaymentMethod() : "Unknown",
|
|
||||||
Collectors.counting()
|
|
||||||
));
|
|
||||||
|
|
||||||
chartPaymentMethods.getData().clear();
|
chartPaymentMethods.getData().clear();
|
||||||
|
|
||||||
List<Map.Entry<String, Long>> paymentEntries = paymentMethodCount.entrySet().stream()
|
List<DashboardResponse.PaymentMethodData> paymentEntries = dashboard.getPaymentMethods() != null ? dashboard.getPaymentMethods() : List.of();
|
||||||
.sorted(Map.Entry.comparingByKey())
|
for (DashboardResponse.PaymentMethodData entry : paymentEntries) {
|
||||||
.toList();
|
|
||||||
|
|
||||||
for (Map.Entry<String, Long> entry : paymentEntries) {
|
|
||||||
PieChart.Data slice = new PieChart.Data(
|
PieChart.Data slice = new PieChart.Data(
|
||||||
entry.getKey() + " (" + entry.getValue() + ")",
|
entry.getPaymentMethod() + " (" + entry.getCount() + ")",
|
||||||
entry.getValue()
|
entry.getCount()
|
||||||
);
|
);
|
||||||
chartPaymentMethods.getData().add(slice);
|
chartPaymentMethods.getData().add(slice);
|
||||||
}
|
}
|
||||||
@@ -269,24 +283,14 @@ public class AnalyticsController {
|
|||||||
applyPieChartColors();
|
applyPieChartColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadEmployeePerformance(List<SaleResponse> sales) throws Exception {
|
private void loadEmployeePerformance(DashboardResponse dashboard, boolean isAdmin) throws Exception {
|
||||||
Map<String, Double> employeeRevenue = sales.stream()
|
|
||||||
.filter(sale -> sale.getIsRefund() == null || !sale.getIsRefund())
|
|
||||||
.filter(sale -> sale.getEmployeeName() != null)
|
|
||||||
.collect(Collectors.groupingBy(
|
|
||||||
SaleResponse::getEmployeeName,
|
|
||||||
Collectors.summingDouble(sale -> sale.getTotalAmount() != null ? sale.getTotalAmount().doubleValue() : 0.0)
|
|
||||||
));
|
|
||||||
|
|
||||||
XYChart.Series<String, Number> series = new XYChart.Series<>();
|
XYChart.Series<String, Number> series = new XYChart.Series<>();
|
||||||
series.setName("Revenue");
|
series.setName(isAdmin ? "Revenue" : "My Revenue");
|
||||||
|
|
||||||
List<Map.Entry<String, Double>> employeeEntries = employeeRevenue.entrySet().stream()
|
List<DashboardResponse.EmployeePerformanceData> employeeEntries = dashboard.getEmployeePerformance() != null ? dashboard.getEmployeePerformance() : List.of();
|
||||||
.sorted(Map.Entry.comparingByKey())
|
for (DashboardResponse.EmployeePerformanceData entry : employeeEntries) {
|
||||||
.toList();
|
BigDecimal revenue = entry.getRevenue() != null ? entry.getRevenue() : BigDecimal.ZERO;
|
||||||
|
series.getData().add(new XYChart.Data<>(entry.getEmployeeName(), revenue));
|
||||||
for (Map.Entry<String, Double> entry : employeeEntries) {
|
|
||||||
series.getData().add(new XYChart.Data<>(entry.getKey(), entry.getValue()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chartEmployeePerformance.getData().clear();
|
chartEmployeePerformance.getData().clear();
|
||||||
@@ -294,6 +298,139 @@ public class AnalyticsController {
|
|||||||
applyBarChartColor(chartEmployeePerformance, EMPLOYEE_COLOR);
|
applyBarChartColor(chartEmployeePerformance, EMPLOYEE_COLOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyRoleVisibility(boolean isAdmin) {
|
||||||
|
chartEmployeePerformance.setVisible(isAdmin);
|
||||||
|
chartEmployeePerformance.setManaged(isAdmin);
|
||||||
|
if (chartEmployeePerformance.getParent() != null) {
|
||||||
|
chartEmployeePerformance.getParent().setVisible(isAdmin);
|
||||||
|
chartEmployeePerformance.getParent().setManaged(isAdmin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DashboardResponse buildStaffFallbackDashboard() throws Exception {
|
||||||
|
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, Integer.MAX_VALUE, null);
|
||||||
|
String employeeName = UserSession.getInstance().getEmployeeName();
|
||||||
|
if (employeeName == null || employeeName.isBlank()) {
|
||||||
|
employeeName = UserSession.getInstance().getUsername();
|
||||||
|
}
|
||||||
|
final String employeeNameFilter = employeeName;
|
||||||
|
|
||||||
|
List<SaleResponse> personalSales = sales.stream()
|
||||||
|
.filter(sale -> sale.getEmployeeName() != null && sale.getEmployeeName().equalsIgnoreCase(employeeNameFilter))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
DashboardResponse dashboard = new DashboardResponse();
|
||||||
|
dashboard.setSalesSummary(buildSalesSummary(personalSales));
|
||||||
|
dashboard.setDailySales(buildDailySales(personalSales, 30));
|
||||||
|
dashboard.setTopProducts(buildTopProducts(personalSales, 10));
|
||||||
|
dashboard.setPaymentMethods(buildPaymentMethods(personalSales));
|
||||||
|
dashboard.setEmployeePerformance(List.of(new DashboardResponse.EmployeePerformanceData()));
|
||||||
|
return dashboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DashboardResponse.SalesSummary buildSalesSummary(List<SaleResponse> sales) {
|
||||||
|
DashboardResponse.SalesSummary summary = new DashboardResponse.SalesSummary();
|
||||||
|
BigDecimal totalRevenue = BigDecimal.ZERO;
|
||||||
|
long totalSales = 0L;
|
||||||
|
BigDecimal totalRefunds = BigDecimal.ZERO;
|
||||||
|
long totalRefundCount = 0L;
|
||||||
|
long totalItemsSold = 0L;
|
||||||
|
|
||||||
|
for (SaleResponse sale : sales) {
|
||||||
|
boolean refund = Boolean.TRUE.equals(sale.getIsRefund());
|
||||||
|
BigDecimal amount = sale.getTotalAmount() != null ? sale.getTotalAmount() : BigDecimal.ZERO;
|
||||||
|
if (refund) {
|
||||||
|
totalRefunds = totalRefunds.add(amount);
|
||||||
|
totalRefundCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
totalRevenue = totalRevenue.add(amount);
|
||||||
|
totalSales++;
|
||||||
|
if (sale.getItems() != null) {
|
||||||
|
totalItemsSold += sale.getItems().stream().mapToLong(item -> item.getQuantity() == null ? 0 : item.getQuantity()).sum();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.setTotalRevenue(totalRevenue);
|
||||||
|
summary.setTotalSales(totalSales);
|
||||||
|
summary.setTotalRefunds(totalRefunds);
|
||||||
|
summary.setTotalRefundCount(totalRefundCount);
|
||||||
|
summary.setTotalItemsSold(totalItemsSold);
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<DailySales> buildDailySales(List<SaleResponse> sales, int days) {
|
||||||
|
Map<LocalDate, DailySales> daily = new LinkedHashMap<>();
|
||||||
|
LocalDate start = LocalDate.now().minusDays(days - 1L);
|
||||||
|
for (int i = 0; i < days; i++) {
|
||||||
|
LocalDate date = start.plusDays(i);
|
||||||
|
DailySales row = new DailySales();
|
||||||
|
row.setDate(date.toString());
|
||||||
|
row.setRevenue(BigDecimal.ZERO);
|
||||||
|
row.setSalesCount(0L);
|
||||||
|
daily.put(date, row);
|
||||||
|
}
|
||||||
|
for (SaleResponse sale : sales) {
|
||||||
|
if (Boolean.TRUE.equals(sale.getIsRefund()) || sale.getSaleDate() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LocalDate date = sale.getSaleDate().toLocalDate();
|
||||||
|
DailySales row = daily.get(date);
|
||||||
|
if (row != null) {
|
||||||
|
row.setRevenue(row.getRevenue().add(sale.getTotalAmount() == null ? BigDecimal.ZERO : sale.getTotalAmount()));
|
||||||
|
row.setSalesCount(row.getSalesCount() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ArrayList<>(daily.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TopProduct> buildTopProducts(List<SaleResponse> sales, int top) {
|
||||||
|
Map<Long, TopProduct> totals = new HashMap<>();
|
||||||
|
for (SaleResponse sale : sales) {
|
||||||
|
if (Boolean.TRUE.equals(sale.getIsRefund()) || sale.getItems() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (SaleItemResponse item : sale.getItems()) {
|
||||||
|
if (item.getProdId() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
TopProduct product = totals.computeIfAbsent(item.getProdId(), id -> {
|
||||||
|
TopProduct p = new TopProduct();
|
||||||
|
p.setProductId(id);
|
||||||
|
p.setProductName(item.getProductName());
|
||||||
|
p.setQuantitySold(0L);
|
||||||
|
p.setRevenue(BigDecimal.ZERO);
|
||||||
|
return p;
|
||||||
|
});
|
||||||
|
long quantity = item.getQuantity() == null ? 0 : item.getQuantity();
|
||||||
|
BigDecimal unitPrice = item.getUnitPrice() == null ? BigDecimal.ZERO : item.getUnitPrice();
|
||||||
|
product.setQuantitySold(product.getQuantitySold() + quantity);
|
||||||
|
product.setRevenue(product.getRevenue().add(unitPrice.multiply(BigDecimal.valueOf(quantity))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return totals.values().stream()
|
||||||
|
.sorted((a, b) -> b.getRevenue().compareTo(a.getRevenue()))
|
||||||
|
.limit(top)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<DashboardResponse.PaymentMethodData> buildPaymentMethods(List<SaleResponse> sales) {
|
||||||
|
Map<String, Long> totals = new TreeMap<>();
|
||||||
|
for (SaleResponse sale : sales) {
|
||||||
|
if (Boolean.TRUE.equals(sale.getIsRefund())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String method = sale.getPaymentMethod() == null || sale.getPaymentMethod().isBlank() ? "Unknown" : sale.getPaymentMethod();
|
||||||
|
totals.merge(method, 1L, Long::sum);
|
||||||
|
}
|
||||||
|
return totals.entrySet().stream().map(entry -> {
|
||||||
|
DashboardResponse.PaymentMethodData data = new DashboardResponse.PaymentMethodData();
|
||||||
|
data.setPaymentMethod(entry.getKey());
|
||||||
|
data.setCount(entry.getValue());
|
||||||
|
return data;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
private void applyLineChartColor(LineChart<Number, Number> chart, String color) {
|
private void applyLineChartColor(LineChart<Number, Number> chart, String color) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
for (XYChart.Series<Number, Number> series : chart.getData()) {
|
for (XYChart.Series<Number, Number> series : chart.getData()) {
|
||||||
|
|||||||
@@ -358,6 +358,7 @@ public class MainLayoutController {
|
|||||||
lblRole.setText("Leon's Petstore");
|
lblRole.setText("Leon's Petstore");
|
||||||
|
|
||||||
boolean isAdmin = session.isAdmin();
|
boolean isAdmin = session.isAdmin();
|
||||||
|
boolean canViewAnalytics = isAdmin || session.isStaff();
|
||||||
btnInventory.setVisible(isAdmin);
|
btnInventory.setVisible(isAdmin);
|
||||||
btnInventory.setManaged(isAdmin);
|
btnInventory.setManaged(isAdmin);
|
||||||
btnSuppliers.setVisible(isAdmin);
|
btnSuppliers.setVisible(isAdmin);
|
||||||
@@ -384,8 +385,8 @@ public class MainLayoutController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (btnAnalytics != null) {
|
if (btnAnalytics != null) {
|
||||||
btnAnalytics.setVisible(isAdmin);
|
btnAnalytics.setVisible(canViewAnalytics);
|
||||||
btnAnalytics.setManaged(isAdmin);
|
btnAnalytics.setManaged(canViewAnalytics);
|
||||||
}
|
}
|
||||||
|
|
||||||
btnSalesHistory.setText(isAdmin ? "Sales History" : "Sales");
|
btnSalesHistory.setText(isAdmin ? "Sales History" : "Sales");
|
||||||
|
|||||||
Reference in New Issue
Block a user