prepare(" SELECT s.*, sp.name_ar as plan_name, sp.ai_features, sp.jofotara_enabled FROM subscriptions s LEFT JOIN subscription_plans sp ON s.plan_id = sp.id WHERE s.tenant_id = ? "); $stmt->execute([$tenantId]); $sub = $stmt->fetch(); if ($sub) { Cache::set($cacheKey, $sub, 300); // Cache for 5 minutes } } if (!$sub) { json_error('لا يوجد اشتراك فعّال لهذا المكتب. يرجى التواصل مع الإدارة.', 403); } // Check subscription status if ($sub['status'] === 'cancelled') { json_error('تم إلغاء اشتراكك. يرجى تجديد الاشتراك للمتابعة.', 403); } if ($sub['status'] === 'past_due') { json_error('اشتراكك متأخر الدفع. يرجى تسوية المبلغ المستحق للمتابعة.', 403); } // Auto-reset monthly counter if billing period has ended if (!empty($sub['current_period_end']) && strtotime($sub['current_period_end']) < time()) { $newStart = date('Y-m-d H:i:s'); $newEnd = date('Y-m-d H:i:s', strtotime('+30 days')); $resetStmt = $db->prepare(" UPDATE subscriptions SET invoices_used_this_month = 0, current_period_start = ?, current_period_end = ?, updated_at = NOW() WHERE tenant_id = ? "); $resetStmt->execute([$newStart, $newEnd, $tenantId]); $sub['invoices_used_this_month'] = 0; $sub['current_period_start'] = $newStart; $sub['current_period_end'] = $newEnd; error_log("QuotaMiddleware: Auto-reset monthly counter for tenant {$tenantId}"); } // Check invoice quota $used = (int)$sub['invoices_used_this_month']; $limit = (int)$sub['max_invoices_per_month']; if ($used >= $limit) { json_error('لقد وصلت للحد الأقصى من الفواتير المسموحة هذا الشهر (' . $limit . ' فاتورة). يرجى ترقية باقتك.', 429, [ 'quota_type' => 'invoices', 'used' => $used, 'limit' => $limit, 'plan' => $sub['plan_id'] ?? 'free', 'plan_name' => $sub['plan_name'] ?? 'مجانية', 'period_end' => $sub['current_period_end'], ]); } return $sub; } /** * Increment the monthly invoice counter after a successful upload. */ public static function incrementInvoiceUsage(string $tenantId): void { $db = Database::getInstance(); $stmt = $db->prepare(" UPDATE subscriptions SET invoices_used_this_month = invoices_used_this_month + 1, updated_at = NOW() WHERE tenant_id = ? "); $stmt->execute([$tenantId]); // Invalidate cache Cache::delete("quota_sub_{$tenantId}"); } /** * Check if the tenant can add more companies. */ public static function checkCompanyQuota(string $tenantId): array { $db = Database::getInstance(); // Get subscription $stmt = $db->prepare(" SELECT s.*, sp.name_ar as plan_name FROM subscriptions s LEFT JOIN subscription_plans sp ON s.plan_id = sp.id WHERE s.tenant_id = ? "); $stmt->execute([$tenantId]); $sub = $stmt->fetch(); if (!$sub) { json_error('لا يوجد اشتراك فعّال لهذا المكتب.', 403); } // Count current active companies $countStmt = $db->prepare(" SELECT COUNT(*) FROM companies WHERE tenant_id = ? AND (deleted_at IS NULL) "); $countStmt->execute([$tenantId]); $currentCount = (int)$countStmt->fetchColumn(); $limit = (int)$sub['max_companies']; if ($currentCount >= $limit) { json_error('لقد وصلت للحد الأقصى من الشركات المسموحة (' . $limit . ' شركة). يرجى ترقية باقتك.', 429, [ 'quota_type' => 'companies', 'used' => $currentCount, 'limit' => $limit, 'plan' => $sub['plan_id'] ?? 'free', 'plan_name' => $sub['plan_name'] ?? 'مجانية', ]); } return $sub; } /** * Check if the tenant can add more users. */ public static function checkUserQuota(string $tenantId): array { $db = Database::getInstance(); // Get subscription $stmt = $db->prepare(" SELECT s.*, sp.name_ar as plan_name FROM subscriptions s LEFT JOIN subscription_plans sp ON s.plan_id = sp.id WHERE s.tenant_id = ? "); $stmt->execute([$tenantId]); $sub = $stmt->fetch(); if (!$sub) { json_error('لا يوجد اشتراك فعّال لهذا المكتب.', 403); } // Count current active users in this tenant $countStmt = $db->prepare(" SELECT COUNT(*) FROM users WHERE tenant_id = ? AND (deleted_at IS NULL) AND is_active = 1 "); $countStmt->execute([$tenantId]); $currentCount = (int)$countStmt->fetchColumn(); $maxUsers = (int)($sub['max_users'] ?? 999); if ($currentCount >= $maxUsers) { json_error('لقد وصلت للحد الأقصى من المستخدمين المسموحين (' . $maxUsers . ' مستخدم). يرجى ترقية باقتك.', 429, [ 'quota_type' => 'users', 'used' => $currentCount, 'limit' => $maxUsers, 'plan' => $sub['plan_id'] ?? 'free', 'plan_name' => $sub['plan_name'] ?? 'مجانية', ]); } return $sub; } /** * Get usage summary for a tenant (for dashboard display). */ public static function getUsageSummary(string $tenantId): array { $db = Database::getInstance(); // Get subscription $stmt = $db->prepare(" SELECT s.*, sp.name_ar as plan_name, sp.name_en as plan_name_en, sp.ai_features, sp.jofotara_enabled, sp.price_jod as plan_price FROM subscriptions s LEFT JOIN subscription_plans sp ON s.plan_id = sp.id WHERE s.tenant_id = ? "); $stmt->execute([$tenantId]); $sub = $stmt->fetch(); if (!$sub) { return [ 'has_subscription' => false, 'plan' => 'none', ]; } // Count companies $compStmt = $db->prepare("SELECT COUNT(*) FROM companies WHERE tenant_id = ? AND deleted_at IS NULL"); $compStmt->execute([$tenantId]); $companiesUsed = (int)$compStmt->fetchColumn(); // Count users $userStmt = $db->prepare("SELECT COUNT(*) FROM users WHERE tenant_id = ? AND (deleted_at IS NULL) AND is_active = 1"); $userStmt->execute([$tenantId]); $usersUsed = (int)$userStmt->fetchColumn(); $invoicesUsed = (int)$sub['invoices_used_this_month']; $invoicesLimit = (int)$sub['max_invoices_per_month']; $companiesLimit = (int)$sub['max_companies']; $usersLimit = (int)($sub['max_users'] ?? 999); // Check for pending payment request $stmt = $db->prepare("SELECT id, plan_id, internal_reference FROM payment_requests WHERE tenant_id = ? AND status = 'pending' LIMIT 1"); $stmt->execute([$tenantId]); $pendingPayment = $stmt->fetch(); return [ 'has_subscription' => true, 'plan_id' => $sub['plan_id'] ?? 'free', 'plan_name' => $sub['plan_name'] ?? 'مجانية', 'plan_name_en' => $sub['plan_name_en'] ?? 'Free', 'plan_price' => (float)($sub['plan_price'] ?? 0), 'status' => $sub['status'], 'ai_features' => (bool)($sub['ai_features'] ?? false), 'jofotara_enabled' => (bool)($sub['jofotara_enabled'] ?? false), 'pending_payment' => $pendingPayment ? [ 'id' => $pendingPayment['id'], 'plan_id' => $pendingPayment['plan_id'], 'reference' => $pendingPayment['internal_reference'] ] : null, 'invoices' => [ 'used' => $invoicesUsed, 'limit' => $invoicesLimit, 'percent' => $invoicesLimit > 0 ? round(($invoicesUsed / $invoicesLimit) * 100) : 0, 'warning' => $invoicesLimit > 0 && ($invoicesUsed / $invoicesLimit) >= 0.9, ], 'companies' => [ 'used' => $companiesUsed, 'limit' => $companiesLimit, 'percent' => $companiesLimit > 0 ? round(($companiesUsed / $companiesLimit) * 100) : 0, 'warning' => $companiesLimit > 0 && ($companiesUsed / $companiesLimit) >= 0.9, ], 'users' => [ 'used' => $usersUsed, 'limit' => $usersLimit, 'percent' => $usersLimit > 0 ? round(($usersUsed / $usersLimit) * 100) : 0, 'warning' => $usersLimit > 0 && ($usersUsed / $usersLimit) >= 0.9, ], 'period_start' => $sub['current_period_start'], 'period_end' => $sub['current_period_end'], 'trial_ends_at' => $sub['trial_ends_at'], 'days_remaining' => !empty($sub['current_period_end']) ? max(0, (int)ceil((strtotime($sub['current_period_end']) - time()) / 86400)) : null, ]; } }