prepare("SELECT max_invoices_per_month, invoices_used_this_month FROM subscriptions WHERE tenant_id = ?"); $stmt->execute([$tenantId]); $sub = $stmt->fetch(); if (!$sub) return; $usage = ($sub['max_invoices_per_month'] > 0) ? ($sub['invoices_used_this_month'] / $sub['max_invoices_per_month']) * 100 : 0; if ($usage >= 80 && $usage < 100) { // Find admin user $adminStmt = $db->prepare("SELECT id FROM users WHERE tenant_id = ? AND role = 'admin' LIMIT 1"); $adminStmt->execute([$tenantId]); $adminId = $adminStmt->fetchColumn(); if ($adminId) { self::send($tenantId, $adminId, 'quota_warning', '⚠️ اقتربت من حد الباقة', 'استخدمت ' . round($usage) . '% من حصة الفواتير الشهرية. فكّر بالترقية لتجنب التوقف.', ['usage_percent' => round($usage)] ); } } } catch (\Throwable $e) { error_log("[SmartNotifications] Quota warning failed: " . $e->getMessage()); } } /** * Notify user when invoice is approved */ public static function invoiceApproved(string $tenantId, string $uploaderId, string $invoiceId, string $invoiceNumber): void { self::send($tenantId, $uploaderId, 'invoice_approved', '✅ تم اعتماد الفاتورة', "الفاتورة رقم {$invoiceNumber} تم اعتمادها وهي جاهزة للإرسال لجوفوترا.", ['invoice_id' => $invoiceId] ); } /** * Notify when JoFotara submission succeeds */ public static function jofotaraSuccess(string $tenantId, string $userId, string $invoiceId, string $uuid): void { self::send($tenantId, $userId, 'jofotara_success', '🎉 تم إرسال الفاتورة لجوفوترا', "الفاتورة أُرسلت بنجاح. UUID: {$uuid}", ['invoice_id' => $invoiceId, 'jofotara_uuid' => $uuid] ); } /** * Notify when JoFotara submission fails */ public static function jofotaraRejected(string $tenantId, string $userId, string $invoiceId, string $error): void { self::send($tenantId, $userId, 'jofotara_rejected', '❌ رُفضت الفاتورة من جوفوترا', "الفاتورة لم تُقبل: {$error}", ['invoice_id' => $invoiceId] ); } /** * Notify admin about pending invoices (daily digest) */ public static function pendingInvoicesDigest(string $tenantId): void { try { $db = Database::getInstance(); $stmt = $db->prepare("SELECT COUNT(*) FROM invoices WHERE tenant_id = ? AND status = 'extracted'"); $stmt->execute([$tenantId]); $count = (int)$stmt->fetchColumn(); if ($count === 0) return; $adminStmt = $db->prepare("SELECT id FROM users WHERE tenant_id = ? AND role = 'admin' LIMIT 1"); $adminStmt->execute([$tenantId]); $adminId = $adminStmt->fetchColumn(); if ($adminId) { self::send($tenantId, $adminId, 'pending_digest', "📋 لديك {$count} فاتورة بانتظار المراجعة", "هناك {$count} فاتورة مستخرجة لم تُراجع بعد. راجعها واعتمدها لإرسالها لجوفوترا.", ['pending_count' => $count] ); } } catch (\Throwable $e) { error_log("[SmartNotifications] Pending digest failed: " . $e->getMessage()); } } /** * Welcome notification for new users */ public static function welcomeUser(string $tenantId, string $userId, string $name): void { self::send($tenantId, $userId, 'welcome', "مرحباً بك في مُصادَق، {$name}! 🎉", 'ابدأ برفع أول فاتورة — صوّرها أو ارفع PDF والذكاء الاصطناعي يكمل الباقي.', [] ); } /** * Core send method — writes to DB (push handled by NotificationService) */ private static function send(string $tenantId, string $userId, string $type, string $title, string $body, array $data): void { try { $db = Database::getInstance(); // Deduplicate: don't send same type within 1 hour $dedup = $db->prepare(" SELECT id FROM notifications WHERE user_id = ? AND type = ? AND created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR) LIMIT 1 "); $dedup->execute([$userId, $type]); if ($dedup->fetch()) return; $db->prepare(" INSERT INTO notifications (id, tenant_id, user_id, type, title, body, data, created_at) VALUES (UUID(), ?, ?, ?, ?, ?, ?, NOW()) ")->execute([$tenantId, $userId, $type, $title, $body, json_encode($data, JSON_UNESCAPED_UNICODE)]); // Try push notification (non-blocking) try { $notifService = new NotificationService(); $notifService->sendNotification($userId, $title, $body, $data); } catch (\Throwable $e) { // Push failure is non-critical } } catch (\Throwable $e) { error_log("[SmartNotifications] Send failed: " . $e->getMessage()); } } }