Fix staff analytics
This commit is contained in:
@@ -5,9 +5,11 @@ 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.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;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/analytics")
|
||||
@@ -26,6 +28,12 @@ public class AnalyticsController {
|
||||
public ResponseEntity<DashboardResponse> getDashboard(
|
||||
@RequestParam(defaultValue = "30") int days,
|
||||
@RequestParam(defaultValue = "10") int 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));
|
||||
}
|
||||
|
||||
@@ -107,6 +107,9 @@ public class AnalyticsService {
|
||||
Map<Long, DashboardResponse.TopProduct> productSalesMap = new HashMap<>();
|
||||
|
||||
for (Sale sale : sales) {
|
||||
if (sale.getIsRefund()) {
|
||||
continue;
|
||||
}
|
||||
for (var item : sale.getItems()) {
|
||||
Long productId = item.getProduct().getProdId();
|
||||
String productName = item.getProduct().getProdName();
|
||||
@@ -142,6 +145,9 @@ public class AnalyticsService {
|
||||
}
|
||||
|
||||
for (Sale sale : sales) {
|
||||
if (sale.getIsRefund()) {
|
||||
continue;
|
||||
}
|
||||
LocalDate saleDate = sale.getSaleDate().toLocalDate();
|
||||
if (dailySalesMap.containsKey(saleDate)) {
|
||||
DashboardResponse.DailySales dailySale = dailySalesMap.get(saleDate);
|
||||
|
||||
@@ -9,7 +9,10 @@ import javafx.scene.control.Label;
|
||||
import org.example.petshopdesktop.api.dto.analytics.DailySales;
|
||||
import org.example.petshopdesktop.api.dto.analytics.DashboardResponse;
|
||||
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.endpoints.AnalyticsApi;
|
||||
import org.example.petshopdesktop.api.endpoints.SaleApi;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
@@ -144,6 +147,31 @@ public class AnalyticsController {
|
||||
}
|
||||
});
|
||||
} 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(() -> {
|
||||
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
|
||||
lblError.setText("Error loading analytics data. Please try again.");
|
||||
@@ -279,6 +307,130 @@ public class AnalyticsController {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
Platform.runLater(() -> {
|
||||
for (XYChart.Series<Number, Number> series : chart.getData()) {
|
||||
|
||||
Reference in New Issue
Block a user