acadflow / deploy-backend /services /leadService.js
Vijayadhith7's picture
Upload 29 files
57a1132 verified
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;