startOfYear(); $dateTo = $filters['date_to'] ?? Carbon::now()->endOfYear(); return [ 'from' => Carbon::parse($dateFrom)->startOfDay(), 'to' => Carbon::parse($dateTo)->endOfDay() ]; } /** * Apply common filters to query */ protected function applyCommonFilters($query, array $filters = []): void { $dateRange = $this->getDateRange($filters); $query->whereBetween('created_at', [$dateRange['from'], $dateRange['to']]); // Government filter: Filter by government (get all units under that government) if (isset($filters['government_id']) && $filters['government_id']) { $unitIds = $this->getUnitsByGovernment($filters['government_id']); if (!empty($unitIds)) { $query->whereIn('unit_id', $unitIds); } else { // No units found for this government $query->whereNull('id'); } } if (isset($filters['unit_id']) && $filters['unit_id']) { $query->where('unit_id', $filters['unit_id']); } if (isset($filters['camp_id']) && $filters['camp_id']) { $query->whereHas('unit', function ($q) use ($filters) { $q->where('camp_id', $filters['camp_id']); }); } if (isset($filters['formation_id']) && $filters['formation_id']) { $query->whereHas('unit', function ($q) use ($filters) { $q->where('formation_id', $filters['formation_id']); }); } } /** * Apply visibility scoping to query based on user permissions */ protected function applyVisibilityScoping($query, $user): void { if (!$user) { return; } // Check if the model has the HasVisibility trait if (method_exists($query->getModel(), 'scopeVisibleTo')) { $query->visibleTo($user); } } /** * Apply visibility scoping to raw database queries * Uses VisibilityService for consistent logic */ protected function applyRawVisibilityScoping($query, $user, string $tableName): void { if (!$user) { return; } $userUnit = Unit::find($user->unit_id); if (!$userUnit) { $query->whereRaw('1 = 0'); return; } $unitIds = $this->visibilityService->getVisibleUnitIds($user); if (empty($unitIds)) { $query->whereRaw('1 = 0'); } else { $query->whereIn('unit_id', $unitIds); } } /** * Get all units by government ID * Uses VisibilityService for consistent logic */ protected function getUnitsByGovernment(int $governmentId): array { $governmentUnit = Unit::where('government_id', $governmentId)->first(); if (!$governmentUnit) { // If no direct unit found, try to get from formation $formation = Formation::where('government_id', $governmentId)->first(); if ($formation) { $governmentUnit = Unit::where('formation_id', $formation->id)->first(); } } if (!$governmentUnit) { return []; } return $this->visibilityService->getUnitsUnderGovernment($governmentUnit); } /** * Get total units count */ public function getTotalUnits(array $filters = []): int { $query = Unit::query(); if (isset($filters['camp_id']) && $filters['camp_id']) { $query->where('camp_id', $filters['camp_id']); } if (isset($filters['formation_id']) && $filters['formation_id']) { $query->where('formation_id', $filters['formation_id']); } return $query->count(); } /** * Get total camps count */ public function getTotalCamps(array $filters = []): int { return Camp::count(); } /** * Get total formations count */ public function getTotalFormations(array $filters = []): int { return Formation::count(); } /** * Get active repairs count (to be implemented by child classes) */ abstract public function getActiveRepairs(array $filters = []): int; /** * Get pending reports count (to be implemented by child classes) */ abstract public function getPendingReports(array $filters = []): int; /** * Get total assets count (to be implemented by child classes) */ abstract public function getTotalAssets(array $filters = []): int; /** * Get operational assets count (to be implemented by child classes) */ abstract public function getOperationalAssets(array $filters = []): int; /** * Get under repair assets count (to be implemented by child classes) */ abstract public function getUnderRepairAssets(array $filters = []): int; /** * Get maintenance due assets count (to be implemented by child classes) */ abstract public function getMaintenanceDueAssets(array $filters = []): int; /** * Format chart data with common structure */ protected function formatChartData( string $chartType, string $title, array $series, array $categories = [], array $drillDown = [] ): array { return [ 'chart_type' => $chartType, 'title' => $title, 'series' => $series, 'categories' => $categories, 'drill_down' => $drillDown ]; } /** * Format pie chart data */ protected function formatPieChartData( string $title, array $data, string $drillDownEndpoint = '', array $drillDownParameters = [] ): array { $total = array_sum(array_column($data, 'value')); $series = []; foreach ($data as $item) { $percentage = $total > 0 ? round(($item['value'] / $total) * 100, 1) : 0; $seriesItem = [ 'name' => $item['name'], 'value' => $item['value'], 'percentage' => $percentage ]; // Preserve status_code if it exists (for drill-down functionality) if (isset($item['status_code'])) { $seriesItem['status_code'] = $item['status_code']; } $series[] = $seriesItem; } return $this->formatChartData( 'pie', $title, $series, [], [ 'enabled' => !empty($drillDownEndpoint), 'endpoint' => $drillDownEndpoint, 'parameters' => $drillDownParameters ] ); } /** * Format bar chart data */ protected function formatBarChartData( string $title, array $series, array $categories, string $drillDownEndpoint = '', array $drillDownParameters = [] ): array { return $this->formatChartData( 'bar', $title, $series, $categories, [ 'enabled' => !empty($drillDownEndpoint), 'endpoint' => $drillDownEndpoint, 'parameters' => $drillDownParameters ] ); } /** * Format line chart data */ protected function formatLineChartData( string $title, array $series, array $categories, string $drillDownEndpoint = '', array $drillDownParameters = [] ): array { return $this->formatChartData( 'line', $title, $series, $categories, [ 'enabled' => !empty($drillDownEndpoint), 'endpoint' => $drillDownEndpoint, 'parameters' => $drillDownParameters ] ); } /** * Get monthly categories for date range */ protected function getMonthlyCategories(array $filters = []): array { $dateRange = $this->getDateRange($filters); $categories = []; $current = $dateRange['from']->copy()->startOfMonth(); $end = $dateRange['to']->copy()->endOfMonth(); while ($current->lte($end)) { $categories[] = $current->format('M Y'); $current->addMonth(); } return $categories; } /** * Get weekly categories for date range */ protected function getWeeklyCategories(array $filters = []): array { $dateRange = $this->getDateRange($filters); $categories = []; $current = $dateRange['from']->copy()->startOfWeek(); $end = $dateRange['to']->copy()->endOfWeek(); while ($current->lte($end)) { $categories[] = 'Week ' . $current->weekOfYear . ' ' . $current->year; $current->addWeek(); } return $categories; } /** * Get daily categories for date range */ protected function getDailyCategories(array $filters = []): array { $dateRange = $this->getDateRange($filters); $categories = []; $current = $dateRange['from']->copy(); $end = $dateRange['to']->copy(); while ($current->lte($end)) { $categories[] = $current->format('M d'); $current->addDay(); } return $categories; } /** * Calculate percentage change */ protected function calculatePercentageChange(int $current, int $previous): float { if ($previous === 0) { return $current > 0 ? 100.0 : 0.0; } return round((($current - $previous) / $previous) * 100, 1); } /** * Get status color mapping */ protected function getStatusColors(): array { return [ 'operational' => '#4caf50', 'under_repair' => '#ff9800', 'maintenance_due' => '#f44336', 'out_of_service' => '#9e9e9e', 'completed' => '#4caf50', 'pending' => '#ff9800', 'in_progress' => '#2196f3', 'cancelled' => '#f44336' ]; } /** * Get priority color mapping */ protected function getPriorityColors(): array { return [ 'high' => '#f44336', 'medium' => '#ff9800', 'low' => '#4caf50' ]; } /** * Format currency */ protected function formatCurrency(float $amount): string { return 'RM ' . number_format($amount, 2); } /** * Format duration in hours */ protected function formatDuration(float $hours): string { if ($hours < 24) { return round($hours, 1) . ' hours'; } $days = floor($hours / 24); $remainingHours = $hours % 24; if ($remainingHours > 0) { return $days . ' days ' . round($remainingHours, 1) . ' hours'; } return $days . ' days'; } /** * Get common drill-down parameters */ protected function getCommonDrillDownParameters(): array { return ['date_from', 'date_to', 'unit_id', 'camp_id', 'formation_id']; } /** * Build drill-down endpoint */ protected function buildDrillDownEndpoint(string $baseEndpoint, array $parameters = []): string { $endpoint = $baseEndpoint; if (!empty($parameters)) { $endpoint .= '?' . http_build_query($parameters); } return $endpoint; } /** * Get summary statistics for drill-down */ protected function getSummaryStatistics($query, array $filters = []): array { $total = $query->count(); // Get breakdown by unit $byUnit = $query->clone() ->join('units', function ($join) { $join->on('units.id', '=', $this->getUnitIdColumn()); }) ->selectRaw('units.name as unit_name, COUNT(*) as count') ->groupBy('units.name') ->get() ->toArray(); return [ 'total' => $total, 'by_unit' => $byUnit ]; } /** * Get unit ID column name (to be implemented by child classes) */ abstract protected function getUnitIdColumn(): string; /** * Get asset model class (to be implemented by child classes) */ abstract protected function getAssetModelClass(): string; /** * Get repair model class (to be implemented by child classes) */ abstract protected function getRepairModelClass(): string; }