File size: 6,255 Bytes
0d3ef02
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
const express = require('express');
const router = express.Router();
const { getTenantDb, requireAuth } = require('./auth');
const eventBus = require('./eventBus');
const supabase = require('../db');

// Helper to save notifications in background events
const addNotification = async (notif) => {
  const orgId = notif.organization_id || '00000000-0000-0000-0000-000000000001';
  const newNotif = {
    organization_id: orgId,
    branch_id: notif.branch_id || '00000000-0000-0000-0000-000000000002',
    title: notif.title,
    message: notif.message,
    type: notif.type || 'SYSTEM_ALERT',
    priority: notif.priority || 'Low',
    is_read: false,
    is_resolved: false,
    action_url: notif.action_url || ''
  };

  try {
    const { data, error } = await supabase
      .from('notifications')
      .insert([newNotif])
      .select();
    if (error) throw error;
    console.log(`[NotificationService] Database notification logged: ${notif.title}`);
    return data[0];
  } catch (err) {
    console.error('[NotificationService] Failed to save notification to database:', err);
    return null;
  }
};

/**

 * Centrally registers and logs notifications in PostgreSQL database

 */
const initNotificationService = (io) => {
  // ===================================================================
  // πŸ“₯ EVENT BUS SUBSCRIBERS (Decoupled Automated System Alerts)
  // ===================================================================

  // 1. Auto Alert for Manual Enrollments
  eventBus.subscribe('manual_admission.completed', async (payload) => {
    const { student_name, student_id, course, pending_amount, installment_option, organization_id } = payload;
    
    await addNotification({
      organization_id,
      title: "New admission completed πŸŽ“",
      message: `${student_name} enrolled in ${course} (ID: ${student_id}).`,
      type: "ADMISSION_ALERT",
      priority: "Medium",
      action_url: "/admissions"
    });

    if (parseFloat(pending_amount || 0) > 0) {
      await addNotification({
        organization_id,
        title: "Installment generated πŸ’°",
        message: `Pending balance of β‚Ή${parseFloat(pending_amount).toLocaleString()} for ${student_name} (${installment_option || 'EMI'}).`,
        type: "PAYMENT_ALERT",
        priority: "High",
        action_url: "/admissions"
      });
    }

    // AI trends trigger alert
    await addNotification({
      organization_id,
      title: `${course} conversion boost πŸ“ˆ`,
      message: `AI Insight: ${course} enrollments showed a surge after campaign adjustments.`,
      type: "AI_INSIGHT",
      priority: "Medium",
      action_url: "/ai-insights"
    });
  });

  // 2. Alert for Payment Completions
  eventBus.subscribe('payment.completed', async (payload) => {
    const { student_name, course, organization_id } = payload;
    await addNotification({
      organization_id,
      title: "Fee payment completed βœ…",
      message: `Received final fee payment installment from ${student_name} for course: ${course}.`,
      type: "PAYMENT_ALERT",
      priority: "Medium",
      action_url: "/admissions"
    });
  });

  // 3. Alert for Real-time counselor lead updates
  eventBus.subscribe('lead.status_changed', async (payload) => {
    const { student_name, new_status, organization_id } = payload;
    
    if (new_status === 'Pending') {
      await addNotification({
        organization_id,
        title: "Follow-up Overdue Alert πŸ”΄",
        message: `Lead ${student_name} followup has expired. Action required.`,
        type: "SYSTEM_ALERT",
        priority: "High",
        action_url: "/leads"
      });
    }
  });

  return { addNotification };
};

// ===================================================================
// REST API Routes
// ===================================================================

// GET /api/notifications - Fetch all active notifications scoped by active tenant
router.get('/', requireAuth, async (req, res) => {
  try {
    const db = getTenantDb(req);
    const { data, error } = await db
      .from('active_notifications')
      .select('*')
      .order('created_at', { ascending: false });

    if (error) throw error;
    res.json(data || []);
  } catch (error) {
    console.error('Failed to fetch notifications:', error);
    res.status(500).json({ error: 'Failed to fetch notifications' });
  }
});

// PUT /api/notifications/:id/read - Mark alert as read
router.put('/:id/read', requireAuth, async (req, res) => {
  const { id } = req.params;
  try {
    const db = getTenantDb(req);
    const { error } = await db
      .from('notifications')
      .update({ is_read: true })
      .eq('id', id);

    if (error) throw error;
    res.json({ success: true });
  } catch (error) {
    console.error('Failed to mark notification read:', error);
    res.status(500).json({ error: 'Failed to update alert log status' });
  }
});

// PUT /api/notifications/:id/resolve - Resolve active notification checklist item
router.put('/:id/resolve', requireAuth, async (req, res) => {
  const { id } = req.params;
  try {
    const db = getTenantDb(req);
    const { error } = await db
      .from('notifications')
      .update({ is_resolved: true, is_read: true })
      .eq('id', id);

    if (error) throw error;
    res.json({ success: true });
  } catch (error) {
    console.error('Failed to resolve notification:', error);
    res.status(500).json({ error: 'Failed to resolve notification logs' });
  }
});

// POST /api/notifications/mark-all-read - Clear all unread notifications
router.post('/mark-all-read', requireAuth, async (req, res) => {
  try {
    const db = getTenantDb(req);
    const { error } = await db
      .from('notifications')
      .update({ is_read: true })
      .eq('is_read', false);

    if (error) throw error;
    res.json({ success: true });
  } catch (error) {
    console.error('Failed to mark all notifications read:', error);
    res.status(500).json({ error: 'Failed to mark notifications read' });
  }
});

module.exports = {
  router,
  initNotificationService
};