Spaces:
Running
Running
| const express = require('express'); | |
| const router = express.Router(); | |
| const { getTenantDb, requireAuth } = require('./auth'); | |
| const eventBus = require('./eventBus'); | |
| // 1. GET /api/leads - Fetch all leads scoped by tenant organization via RLS | |
| router.get('/', requireAuth, async (req, res) => { | |
| try { | |
| const db = getTenantDb(req); | |
| // RLS in Supabase PostgreSQL automatically handles tenant scoping & role boundaries | |
| const { data: leads, error } = await db | |
| .from('leads') | |
| .select(` | |
| *, | |
| counselors ( | |
| name | |
| ), | |
| admissions ( | |
| course, | |
| fees, | |
| payment_status | |
| ) | |
| `) | |
| .order('created_at', { ascending: false }); | |
| if (error) throw error; | |
| const formattedLeads = (leads || []).map(lead => { | |
| const hasAdmission = lead.admissions && lead.admissions.length > 0; | |
| const admissionRecord = hasAdmission ? lead.admissions[0] : null; | |
| let followupTime = 'One Day'; | |
| if (lead.notes) { | |
| try { | |
| const parsed = JSON.parse(lead.notes); | |
| if (parsed.followup_time) { | |
| followupTime = parsed.followup_time; | |
| } | |
| } catch (e) {} | |
| } | |
| return { | |
| id: lead.id, | |
| student_name: lead.name, | |
| phone_number: lead.phone, | |
| email: lead.email || '', | |
| interested_course: lead.course_interested, | |
| lead_source: lead.source, | |
| counselor_name: lead.counselors ? lead.counselors.name : '', | |
| followup_status: lead.status || 'Pending', | |
| followup_time: followupTime, | |
| admission_status: hasAdmission ? 'Admitted' : 'Not Admitted', | |
| fees: admissionRecord ? parseFloat(admissionRecord.fees || 0) : 0, | |
| lead_score: lead.lead_score || 50, | |
| created_date: lead.created_at | |
| }; | |
| }); | |
| res.json(formattedLeads); | |
| } catch (error) { | |
| console.error('Error fetching leads:', error); | |
| res.status(500).json({ error: 'Internal Server Error' }); | |
| } | |
| }); | |
| // 2. PUT /api/update-lead - Update a lead's follow-up or admission status | |
| router.put('/update-lead', requireAuth, async (req, res) => { | |
| const { phone_number, followup_status, admission_status, followup_time } = req.body; | |
| if (!phone_number) { | |
| return res.status(400).json({ error: 'Phone number is required' }); | |
| } | |
| try { | |
| const db = getTenantDb(req); | |
| // Find the lead first | |
| const { data: leads, error: findError } = await db | |
| .from('leads') | |
| .select('id, name, course_interested, status, counselor_id, notes, organization_id, branch_id') | |
| .eq('phone', phone_number); | |
| if (findError) throw findError; | |
| if (!leads || leads.length === 0) { | |
| return res.status(404).json({ error: 'Lead not found' }); | |
| } | |
| const lead = leads[0]; | |
| // Parse metadata notes JSON | |
| let metadata = {}; | |
| try { | |
| if (lead.notes && lead.notes.startsWith('{')) { | |
| metadata = JSON.parse(lead.notes); | |
| } else if (lead.notes) { | |
| metadata = { text_notes: lead.notes }; | |
| } | |
| } catch (e) {} | |
| let notesUpdated = false; | |
| if (followup_time) { | |
| metadata.followup_time = followup_time; | |
| notesUpdated = true; | |
| } | |
| // Prepare lead updates | |
| const leadUpdates = {}; | |
| const oldStatus = lead.status; | |
| if (followup_status && followup_status !== lead.status) { | |
| leadUpdates.status = followup_status; | |
| } | |
| if (notesUpdated) { | |
| leadUpdates.notes = JSON.stringify(metadata); | |
| } | |
| if (Object.keys(leadUpdates).length > 0) { | |
| const { error: updateLeadError } = await db | |
| .from('leads') | |
| .update(leadUpdates) | |
| .eq('id', lead.id); | |
| if (updateLeadError) throw updateLeadError; | |
| // Log followup details if status changed | |
| if (followup_status && followup_status !== oldStatus) { | |
| await db.from('follow_ups').insert([{ | |
| lead_id: lead.id, | |
| organization_id: lead.organization_id, | |
| branch_id: lead.branch_id, | |
| followup_date: new Date().toISOString(), | |
| followup_type: 'Call', | |
| status: followup_status === 'Pending' ? 'Pending' : 'Completed', | |
| remarks: `Status updated via CRM to ${followup_status}`, | |
| created_by: lead.counselor_id | |
| }]); | |
| // Publish event to decoupled Event Bus | |
| eventBus.publish('lead.status_changed', { | |
| lead_id: lead.id, | |
| student_name: lead.name, | |
| phone: phone_number, | |
| old_status: oldStatus, | |
| new_status: followup_status, | |
| followup_time: followup_time || 'One Day', | |
| organization_id: lead.organization_id, | |
| branch_id: lead.branch_id | |
| }); | |
| } | |
| } | |
| // Update admissions status | |
| if (admission_status) { | |
| const { data: existingAdmissions, error: admSelectError } = await db | |
| .from('admissions') | |
| .select('id') | |
| .eq('lead_id', lead.id); | |
| if (admSelectError) throw admSelectError; | |
| const hasAdmissionRecord = existingAdmissions && existingAdmissions.length > 0; | |
| if (admission_status === 'Admitted' && !hasAdmissionRecord) { | |
| // Trigger Lead Converted Event Bus sequence | |
| eventBus.publish('lead.converted', { | |
| lead_id: lead.id, | |
| student_name: lead.name, | |
| phone: phone_number, | |
| course: lead.course_interested || 'Default Course', | |
| counselor_id: lead.counselor_id, | |
| organization_id: lead.organization_id, | |
| branch_id: lead.branch_id | |
| }); | |
| } else if (admission_status === 'Not Admitted' && hasAdmissionRecord) { | |
| // Remove admission details | |
| const { error: deleteAdmError } = await db | |
| .from('admissions') | |
| .delete() | |
| .eq('lead_id', lead.id); | |
| if (deleteAdmError) throw deleteAdmError; | |
| } | |
| } | |
| res.json({ message: 'Lead updated successfully' }); | |
| } catch (error) { | |
| console.error('Error updating lead:', error); | |
| res.status(500).json({ error: 'Internal Server Error' }); | |
| } | |
| }); | |
| module.exports = router; | |