approved_estimate; // If no approved estimate, consider it UNDER if ($approved <= 0) { return 'UNDER'; } $planSum = $this->calculatePlanSum($project, $plans); // Use decimal-safe comparison if ($this->isGreaterThan($planSum, $approved)) { return 'OVER'; } if ($this->isLessThan($planSum, $approved)) { return 'UNDER'; } return 'MATCH'; } /** * Calculate the sum of planned hours for a project. * Only sums non-null planned_hours values. */ public function calculatePlanSum(Project $project, ?Collection $plans = null): float { if ($plans === null) { $plans = ProjectMonthPlan::where('project_id', $project->id)->get(); } return $plans ->filter(fn ($plan) => $plan->planned_hours !== null) ->sum('planned_hours'); } /** * Calculate plan sum and status for multiple projects. * Returns array with project_id => ['plan_sum' => float, 'status' => string]. */ public function calculateForProjects(Collection $projects, int $year): array { $startDate = "{$year}-01-01"; $endDate = "{$year}-12-01"; // Get all plans for the year, grouped by project_id $allPlans = ProjectMonthPlan::whereBetween('month', [$startDate, $endDate]) ->get() ->groupBy('project_id'); $results = []; foreach ($projects as $project) { $projectPlans = $allPlans->get($project->id, collect()); $planSum = $this->calculatePlanSum($project, $projectPlans); $status = $this->calculateStatus($project, $projectPlans); $results[$project->id] = [ 'plan_sum' => $planSum, 'status' => $status, 'approved_estimate' => $project->approved_estimate, ]; } return $results; } /** * Compare two floats using epsilon for decimal-safe comparison. */ private function isGreaterThan(float $a, float $b): bool { $epsilon = 0.0001; return ($a - $b) > $epsilon; } /** * Compare two floats using epsilon for decimal-safe comparison. */ private function isLessThan(float $a, float $b): bool { $epsilon = 0.0001; return ($b - $a) > $epsilon; } }