where('created_at', '>=', now()->startOfMonth()) ->whereNotNull('cost_eur') ->sum('cost_eur'); } public function remainingEur(): float { return max(0.0, $this->capEur() - $this->spentEurThisMonth()); } public function warnThresholdReached(): bool { $cap = $this->capEur(); if ($cap <= 0) { return false; } return ($this->spentEurThisMonth() / $cap) >= (float) config('runware.warn_threshold'); } /** * Throws BudgetExceededException if spending $estimatedUsd would push * this month's total (plus a safety margin) past the cap. Guards the * check with an atomic lock so concurrent requests can't both slip * through right at the boundary. */ public function ensureCanSpend(float $estimatedUsd): void { Cache::lock(self::LOCK_KEY, self::LOCK_TIMEOUT_SECONDS)->block(5, function () use ($estimatedUsd) { $rate = $this->rates->usdToEur(); $estimatedEur = $estimatedUsd * $rate; $spent = $this->spentEurThisMonth(); $cap = $this->capEur(); $margin = $cap * (float) config('runware.safety_margin'); if ($spent + $estimatedEur + $margin > $cap) { throw new BudgetExceededException($spent, $cap, $estimatedEur); } }); } }