$companyId, 'billing_start' => $billingStart, 'billing_end' => $billingEnd, 'request_count' => 0, 'voice_count' => 0, 'ocr_count' => 0 ]); $usage = [ 'id' => $id, 'company_id' => $companyId, 'billing_start' => $billingStart, 'billing_end' => $billingEnd, 'request_count' => 0, 'voice_count' => 0, 'ocr_count' => 0 ]; } catch (\Exception $e) { // Handle concurrent insertions gracefully $usage = Database::selectOne( "SELECT * FROM " . static::$table . " WHERE company_id = ? AND billing_start = ? AND billing_end = ? LIMIT 1", [$companyId, $billingStart, $billingEnd] ); if (!$usage) { throw $e; } } } // Cache usage data for 60 seconds (short TTL since usage changes frequently) Cache::set($cacheKey, $usage, 60); return $usage; } /** * Increment usage counts for the current billing cycle */ public static function incrementUsage(int $companyId, string $type = 'request', int $amount = 1): bool { $activeSub = CompanySubscription::findActiveByCompany($companyId); if (!$activeSub) { return false; } $currentUsage = self::getOrCreateCurrentUsage($companyId, $activeSub); $column = 'request_count'; if ($type === 'voice') { $column = 'voice_count'; } elseif ($type === 'ocr') { $column = 'ocr_count'; } $success = Database::execute( "UPDATE " . static::$table . " SET {$column} = {$column} + ? WHERE id = ?", [$amount, $currentUsage['id']] ) > 0; if ($success) { // Sync Redis cache with incremented values $cacheKey = "company_usage_{$companyId}_{$currentUsage['billing_start']}_{$currentUsage['billing_end']}"; $cached = Cache::get($cacheKey); if ($cached) { $cached[$column] += $amount; Cache::set($cacheKey, $cached, 60); } } return $success; } /** * Check if a company has exceeded its plan limits for a certain action */ public static function hasRemainingLimit(int $companyId, string $type = 'request'): bool { $activeSub = CompanySubscription::findActiveByCompany($companyId); if (!$activeSub) { return false; // No active subscription means no requests allowed } $usage = self::getOrCreateCurrentUsage($companyId, $activeSub); if ($type === 'request') { return $usage['request_count'] < $activeSub['max_requests']; } elseif ($type === 'voice') { return $usage['voice_count'] < $activeSub['max_voice_requests']; } elseif ($type === 'ocr') { return $usage['ocr_count'] < $activeSub['max_ocr_requests']; } return false; } }