5, 'invoice_approved' => 10, 'jofotara_submitted' => 15, 'company_created' => 20, 'referral_registered' => 50, 'first_login' => 10, 'streak_7_days' => 30, 'streak_30_days' => 100, ]; // Badge definitions private const BADGES = [ 'starter' => ['name' => 'بداية موفقة', 'icon' => '🌟', 'desc' => 'رفعت أول فاتورة', 'condition' => 'invoices >= 1'], 'active_10' => ['name' => 'نشيط', 'icon' => '🔥', 'desc' => '10 فواتير مرفوعة', 'condition' => 'invoices >= 10'], 'pro_50' => ['name' => 'محترف', 'icon' => '💎', 'desc' => '50 فاتورة مرفوعة', 'condition' => 'invoices >= 50'], 'master_200' => ['name' => 'خبير فوترة', 'icon' => '👑', 'desc' => '200 فاتورة مرفوعة', 'condition' => 'invoices >= 200'], 'jofotara_first' => ['name' => 'رسمي', 'icon' => '🏛️', 'desc' => 'أول إرسال لجوفوترا', 'condition' => 'submitted >= 1'], 'jofotara_50' => ['name' => 'فوترة ذهبية', 'icon' => '🏆', 'desc' => '50 فاتورة مرسلة لجوفوترا', 'condition' => 'submitted >= 50'], 'multi_company' => ['name' => 'مدير شركات', 'icon' => '🏢', 'desc' => 'تدير 3 شركات أو أكثر', 'condition' => 'companies >= 3'], 'referrer' => ['name' => 'سفير مُصادَق', 'icon' => '🤝', 'desc' => 'أحلت مستخدم جديد', 'condition' => 'referrals >= 1'], 'streak_week' => ['name' => 'مثابر', 'icon' => '📅', 'desc' => 'دخلت 7 أيام متتالية', 'condition' => 'streak >= 7'], ]; /** * Award points for an action */ public static function award(string $userId, string $tenantId, string $action): void { try { $points = self::POINTS[$action] ?? 0; if ($points === 0) return; $db = Database::getInstance(); // Add points $db->prepare(" INSERT INTO user_points (id, user_id, tenant_id, action, points, created_at) VALUES (UUID(), ?, ?, ?, ?, NOW()) ")->execute([$userId, $tenantId, $action, $points]); // Check for new badges self::checkBadges($userId, $tenantId); } catch (\Throwable $e) { error_log("[Gamification] Award failed: " . $e->getMessage()); } } /** * Check and award any earned badges */ private static function checkBadges(string $userId, string $tenantId): void { $db = Database::getInstance(); // Get user stats $invoices = (int)$db->prepare("SELECT COUNT(*) FROM invoices WHERE tenant_id = ?")->execute([$tenantId])?->fetchColumn() ?: 0; $submitted = (int)$db->prepare("SELECT COUNT(*) FROM invoices WHERE tenant_id = ? AND status = 'submitted'")->execute([$tenantId])?->fetchColumn() ?: 0; $companies = (int)$db->prepare("SELECT COUNT(*) FROM companies WHERE tenant_id = ? AND deleted_at IS NULL")->execute([$tenantId])?->fetchColumn() ?: 0; $referrals = (int)$db->prepare("SELECT COUNT(*) FROM referrals WHERE referrer_id = ?")->execute([$userId])?->fetchColumn() ?: 0; // Get existing badges $existingStmt = $db->prepare("SELECT badge_key FROM user_badges WHERE user_id = ?"); $existingStmt->execute([$userId]); $existing = $existingStmt->fetchAll(\PDO::FETCH_COLUMN); $stats = compact('invoices', 'submitted', 'companies', 'referrals'); foreach (self::BADGES as $key => $badge) { if (in_array($key, $existing)) continue; if (self::evaluateCondition($badge['condition'], $stats)) { $db->prepare(" INSERT INTO user_badges (id, user_id, tenant_id, badge_key, badge_name, badge_icon, earned_at) VALUES (UUID(), ?, ?, ?, ?, ?, NOW()) ")->execute([$userId, $tenantId, $key, $badge['name'], $badge['icon']]); // Notify user SmartNotifications::send($tenantId, $userId, 'badge_earned', "{$badge['icon']} شارة جديدة: {$badge['name']}!", $badge['desc'], ['badge_key' => $key] ); } } } /** * Simple condition evaluator */ private static function evaluateCondition(string $condition, array $stats): bool { if (preg_match('/(\w+)\s*>=\s*(\d+)/', $condition, $m)) { $field = $m[1]; $value = (int)$m[2]; return ($stats[$field] ?? 0) >= $value; } return false; } /** * Get user's gamification profile */ public static function getProfile(string $userId, string $tenantId): array { $db = Database::getInstance(); // Total points $pointsStmt = $db->prepare("SELECT COALESCE(SUM(points), 0) FROM user_points WHERE user_id = ?"); $pointsStmt->execute([$userId]); $totalPoints = (int)$pointsStmt->fetchColumn(); // Badges $badgesStmt = $db->prepare("SELECT badge_key, badge_name, badge_icon, earned_at FROM user_badges WHERE user_id = ? ORDER BY earned_at DESC"); $badgesStmt->execute([$userId]); $badges = $badgesStmt->fetchAll(); // Level (every 100 points = 1 level) $level = max(1, (int)floor($totalPoints / 100) + 1); $levelNames = ['', 'مبتدئ', 'ناشط', 'متقدم', 'خبير', 'أسطورة', 'سيد الفوترة']; $levelName = $levelNames[min($level, count($levelNames) - 1)] ?? 'أسطورة'; // Progress to next level $pointsInLevel = $totalPoints % 100; $progressPercent = $pointsInLevel; return [ 'total_points' => $totalPoints, 'level' => $level, 'level_name' => $levelName, 'progress_percent' => $progressPercent, 'badges' => $badges, 'badges_count' => count($badges), 'available_badges' => count(self::BADGES), ]; } }