AnalyticsController.java
package com.smartsupplypro.inventory.controller;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.smartsupplypro.inventory.controller.analytics.AnalyticsControllerValidationHelper;
import com.smartsupplypro.inventory.controller.analytics.AnalyticsDashboardHelper;
import com.smartsupplypro.inventory.dto.DashboardSummaryDTO;
import com.smartsupplypro.inventory.dto.FinancialSummaryDTO;
import com.smartsupplypro.inventory.dto.ItemUpdateFrequencyDTO;
import com.smartsupplypro.inventory.dto.LowStockItemDTO;
import com.smartsupplypro.inventory.dto.MonthlyStockMovementDTO;
import com.smartsupplypro.inventory.dto.PriceTrendDTO;
import com.smartsupplypro.inventory.dto.StockPerSupplierDTO;
import com.smartsupplypro.inventory.dto.StockUpdateFilterDTO;
import com.smartsupplypro.inventory.dto.StockUpdateResultDTO;
import com.smartsupplypro.inventory.dto.StockValueOverTimeDTO;
import com.smartsupplypro.inventory.service.impl.analytics.FinancialAnalyticsService;
import com.smartsupplypro.inventory.service.impl.analytics.StockAnalyticsService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
/**
* Analytics REST controller for inventory reporting and dashboard data.
*
* <p>Delegates to specialized helpers for validation and dashboard aggregation
* while maintaining the original REST API contract.
*
* <p><strong>Delegation Strategy</strong>:
* <ul>
* <li>{@link AnalyticsControllerValidationHelper} - Parameter validation</li>
* <li>{@link AnalyticsDashboardHelper} - Dashboard data aggregation</li>
* <li>{@link StockAnalyticsService} - Stock metrics and trends</li>
* <li>{@link FinancialAnalyticsService} - Financial summaries</li>
* </ul>
*
* @author Smart Supply Pro Development Team
* @version 2.0.0
* @since 1.0.0
* @see StockAnalyticsService
* @see FinancialAnalyticsService
*/
@RestController
@RequestMapping(value = "/api/analytics", produces = MediaType.APPLICATION_JSON_VALUE)
@RequiredArgsConstructor
@Validated
public class AnalyticsController {
private final StockAnalyticsService stockAnalyticsService;
private final FinancialAnalyticsService financialAnalyticsService;
private final AnalyticsControllerValidationHelper validationHelper;
private final AnalyticsDashboardHelper dashboardHelper;
/**
* Gets time series of total stock value between dates.
*
* @param start inclusive start date (ISO yyyy-MM-dd)
* @param end inclusive end date (ISO yyyy-MM-dd)
* @param supplierId optional supplier filter
* @return list of stock value points over time
*/
@PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
@GetMapping("/stock-value")
public ResponseEntity<List<StockValueOverTimeDTO>> getStockValueOverTime(
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate start,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate end,
@RequestParam(required = false) String supplierId) {
validationHelper.validateDateRange(start, end, "start", "end");
return ResponseEntity.ok(stockAnalyticsService.getTotalStockValueOverTime(start, end, supplierId));
}
/**
* Gets current total stock per supplier for charts.
*
* @return list of stock quantities per supplier
*/
@PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
@GetMapping("/stock-per-supplier")
public ResponseEntity<List<StockPerSupplierDTO>> getStockPerSupplier() {
return ResponseEntity.ok(stockAnalyticsService.getTotalStockPerSupplier());
}
/**
* Gets count of items below minimum stock threshold.
*
* @return number of low-stock items
*/
@PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
@GetMapping("/low-stock/count")
public long getLowStockCount() {
return stockAnalyticsService.lowStockCount();
}
/**
* Gets item update frequency for a supplier.
*
* @param supplierId required supplier identifier
* @return list of item update frequencies
*/
@PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
@GetMapping("/item-update-frequency")
public ResponseEntity<List<ItemUpdateFrequencyDTO>> getItemUpdateFrequency(
@RequestParam(name = "supplierId") String supplierId) {
validationHelper.requireNonBlank(supplierId, "supplierId");
return ResponseEntity.ok(stockAnalyticsService.getItemUpdateFrequency(supplierId));
}
/**
* Gets items below minimum stock threshold for a supplier.
*
* @param supplierId required supplier identifier
* @return list of low-stock items with details
*/
@PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
@GetMapping("/low-stock-items")
public ResponseEntity<List<LowStockItemDTO>> getLowStockItems(
@RequestParam(name = "supplierId") String supplierId) {
validationHelper.requireNonBlank(supplierId, "supplierId");
return ResponseEntity.ok(stockAnalyticsService.getItemsBelowMinimumStock(supplierId));
}
/**
* Gets monthly stock movement within date range.
* @param start inclusive start date (ISO yyyy-MM-dd)
* @param end inclusive end date (ISO yyyy-MM-dd)
* @param supplierId optional supplier filter
*/
@PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
@GetMapping("/monthly-stock-movement")
public ResponseEntity<List<MonthlyStockMovementDTO>> getMonthlyStockMovement(
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate start,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate end,
@RequestParam(required = false) String supplierId) {
validationHelper.validateDateRange(start, end, "start", "end");
return ResponseEntity.ok(stockAnalyticsService.getMonthlyStockMovement(start, end, supplierId));
}
/**
* Gets filtered stock updates via query parameters (defaults to last 30 days).
*
* @param startDate optional inclusive start date-time (ISO yyyy-MM-dd'T'HH:mm:ss)
* @param endDate optional inclusive end date-time (ISO yyyy-MM-dd'T'HH:mm:ss)
* @param itemName optional item name filter
* @param supplierId optional supplier identifier filter
* @param createdBy optional creator username filter
* @param minChange optional minimum quantity change filter
* @param maxChange optional maximum quantity change filter
*/
@PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
@GetMapping("/stock-updates")
public ResponseEntity<List<StockUpdateResultDTO>> getFilteredStockUpdatesFromParams(
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate,
@RequestParam(required = false) String itemName,
@RequestParam(required = false) String supplierId,
@RequestParam(required = false) String createdBy,
@RequestParam(required = false) Integer minChange,
@RequestParam(required = false) Integer maxChange) {
// Apply default date window (last 30 days)
LocalDateTime[] dateWindow = validationHelper.applyDefaultDateWindow(startDate, endDate);
startDate = dateWindow[0];
endDate = dateWindow[1];
// Validate date & numeric params
validationHelper.validateDateTimeRange(startDate, endDate, "startDate", "endDate");
validationHelper.validateNumericRange(minChange, maxChange, "minChange", "maxChange");
StockUpdateFilterDTO filter = new StockUpdateFilterDTO();
filter.setStartDate(startDate);
filter.setEndDate(endDate);
filter.setItemName(itemName);
filter.setSupplierId(supplierId);
filter.setCreatedBy(createdBy);
filter.setMinChange(minChange);
filter.setMaxChange(maxChange);
return ResponseEntity.ok(stockAnalyticsService.getFilteredStockUpdates(filter));
}
/**
* Gets filtered stock updates via JSON payload.
*
* @param filter stock update filter criteria
* @return list of filtered stock updates
* @throws IllegalArgumentException if validation fails
*/
@PreAuthorize("isAuthenticated()")
@PostMapping(value = "/stock-updates/query", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<StockUpdateResultDTO>> getFilteredStockUpdatesPost(
@RequestBody @Valid StockUpdateFilterDTO filter) {
validationHelper.validateStockUpdateFilter(filter);
return ResponseEntity.ok(stockAnalyticsService.getFilteredStockUpdates(filter));
}
/**
* Gets dashboard summary with multiple analytics (defaults to last 30 days).
* @param supplierId optional supplier filter
* @param startDate optional inclusive start date-time (ISO yyyy-MM-dd'T'HH:mm:ss)
* @param endDate optional inclusive end date-time (ISO yyyy-MM-dd'T'HH:mm:ss)
* @return dashboard summary DTO
*/
@PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
@GetMapping("/summary")
public ResponseEntity<DashboardSummaryDTO> getDashboardSummary(
@RequestParam(required = false) String supplierId,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate) {
// Apply default date window
LocalDateTime[] dateWindow = validationHelper.applyDefaultDateWindow(startDate, endDate);
startDate = dateWindow[0];
endDate = dateWindow[1];
validationHelper.validateDateTimeRange(startDate, endDate, "startDate", "endDate");
return ResponseEntity.ok(dashboardHelper.buildDashboardSummary(supplierId, startDate, endDate));
}
/**
* Gets historical price changes for an item.
* @param itemId required item identifier
* @param supplierId optional supplier filter
* @param start inclusive start date (ISO yyyy-MM-dd)
* @param end inclusive end date (ISO yyyy-MM-dd)
* @return list of price trend DTOs
*/
@PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
@GetMapping("/price-trend")
public ResponseEntity<List<PriceTrendDTO>> getPriceTrend(
@RequestParam String itemId,
@RequestParam(required = false) String supplierId,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate start,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate end) {
validationHelper.requireNonBlank(itemId, "itemId");
validationHelper.validateDateRange(start, end, "start", "end");
return ResponseEntity.ok(stockAnalyticsService.getPriceTrend(itemId, supplierId, start, end));
}
/**
* Gets financial summary with WAC calculations.
* @param from inclusive start date (ISO yyyy-MM-dd)
* @param to inclusive end date (ISO yyyy-MM-dd)
* @param supplierId optional supplier filter
* @return financial summary DTO
*/
@PreAuthorize("isAuthenticated() or @appProperties.demoReadonly")
@GetMapping("/financial/summary")
public ResponseEntity<FinancialSummaryDTO> getFinancialSummary(
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to,
@RequestParam(required = false) String supplierId) {
validationHelper.validateDateRange(from, to, "from", "to");
return ResponseEntity.ok(financialAnalyticsService.getFinancialSummaryWAC(from, to, supplierId));
}
}