243 lines
10 KiB
JavaScript
243 lines
10 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.createLeadsRoutes = createLeadsRoutes;
|
|
const express_1 = require("express");
|
|
let authenticate;
|
|
let authorize;
|
|
if (process.env.NODE_ENV === 'production') {
|
|
authenticate = require('../../../dist/middleware/auth').authMiddleware;
|
|
authorize = require('../../../dist/middleware/rbac').adminMiddleware;
|
|
}
|
|
else {
|
|
authenticate = require('../../../backend/src/middleware/auth').authMiddleware;
|
|
authorize = require('../../../backend/src/middleware/rbac').adminMiddleware;
|
|
}
|
|
const STATUS_VALUES = ['new', 'contacted', 'qualified', 'converted', 'lost'];
|
|
const LEAD_TYPE_VALUES = ['cold', 'member'];
|
|
function translateLead(lead) {
|
|
if (!lead)
|
|
return lead;
|
|
return {
|
|
...lead,
|
|
source: lead.source || 'manual',
|
|
partner_id: lead.partner_id || null,
|
|
benefit_id: lead.benefit_id || null,
|
|
user_id: lead.user_id || null,
|
|
lead_type: lead.lead_type || 'cold',
|
|
};
|
|
}
|
|
function createLeadsRoutes(ctx) {
|
|
const router = (0, express_1.Router)();
|
|
const { db } = ctx;
|
|
const protectedRoutes = [authenticate, authorize(['super_admin', 'admin'])];
|
|
// GET /api/leads/metrics — Global metrics for super admin
|
|
router.get('/metrics', ...protectedRoutes, async (req, res) => {
|
|
try {
|
|
const [totalRow] = await db('leads').count('id as total');
|
|
const total = Number(totalRow.total);
|
|
// Today
|
|
const [todayRow] = await db('leads')
|
|
.count('id as total')
|
|
.whereRaw('DATE(created_at) = CURDATE()');
|
|
const today = Number(todayRow.total);
|
|
// By status
|
|
const byStatus = await db('leads')
|
|
.select('status')
|
|
.count('id as total')
|
|
.groupBy('status');
|
|
// By lead type
|
|
const byType = await db('leads')
|
|
.select('lead_type')
|
|
.count('id as total')
|
|
.groupBy('lead_type');
|
|
// Converted count for conversion rate
|
|
const converted = byStatus.find((r) => r.status === 'converted');
|
|
const conversionRate = total > 0 ? ((Number(converted?.total || 0) / total) * 100).toFixed(1) : '0.0';
|
|
// By city (via users join)
|
|
const byCity = await db('leads')
|
|
.join('users', 'leads.user_id', 'users.id')
|
|
.select('users.city', 'users.state')
|
|
.count('leads.id as total')
|
|
.whereNotNull('users.city')
|
|
.groupBy('users.city', 'users.state')
|
|
.orderBy('total', 'desc')
|
|
.limit(10);
|
|
// By neighborhood (via users join)
|
|
const byNeighborhood = await db('leads')
|
|
.join('users', 'leads.user_id', 'users.id')
|
|
.select('users.neighborhood', 'users.city', 'users.state')
|
|
.count('leads.id as total')
|
|
.whereNotNull('users.neighborhood')
|
|
.groupBy('users.neighborhood', 'users.city', 'users.state')
|
|
.orderBy('total', 'desc')
|
|
.limit(10);
|
|
// By partner
|
|
const byPartner = await db('leads')
|
|
.join('partners', 'leads.partner_id', 'partners.id')
|
|
.select('partners.id as partner_id', 'partners.company_name')
|
|
.count('leads.id as total')
|
|
.groupBy('partners.id', 'partners.company_name')
|
|
.orderBy('total', 'desc')
|
|
.limit(10);
|
|
// Recent leads
|
|
const recent = await db('leads')
|
|
.leftJoin('users', 'leads.user_id', 'users.id')
|
|
.leftJoin('partners', 'leads.partner_id', 'partners.id')
|
|
.select('leads.id', 'leads.name', 'leads.email', 'leads.status', 'leads.lead_type', 'leads.source', 'leads.created_at', 'partners.company_name as partner_name', 'users.city', 'users.neighborhood')
|
|
.orderBy('leads.created_at', 'desc')
|
|
.limit(15);
|
|
res.json({
|
|
total,
|
|
today,
|
|
conversion_rate: conversionRate,
|
|
by_status: byStatus,
|
|
by_type: byType,
|
|
by_city: byCity,
|
|
by_neighborhood: byNeighborhood,
|
|
by_partner: byPartner,
|
|
recent,
|
|
});
|
|
}
|
|
catch (err) {
|
|
console.error('Error fetching lead metrics:', err);
|
|
res.status(500).json({ error: 'Erro ao buscar métricas' });
|
|
}
|
|
});
|
|
router.get('/', authenticate, async (req, res) => {
|
|
try {
|
|
const { partner_id, status, type, page = '1', limit = '20' } = req.query;
|
|
const offset = (parseInt(page) - 1) * parseInt(limit);
|
|
const userRole = req.user?.role;
|
|
const userPartner = req.user?.partner_id;
|
|
let query = db('leads').orderBy('created_at', 'desc');
|
|
if (partner_id)
|
|
query = query.where({ partner_id });
|
|
if (userRole === 'partner' && userPartner) {
|
|
query = query.where({ partner_id: userPartner });
|
|
}
|
|
if (type && LEAD_TYPE_VALUES.includes(type)) {
|
|
query = query.where({ lead_type: type });
|
|
}
|
|
if (status && STATUS_VALUES.includes(status)) {
|
|
query = query.where({ status: status });
|
|
}
|
|
else {
|
|
query = query.where({ status: 'new' });
|
|
}
|
|
const countResult = await query.clone().count('id as count');
|
|
const leads = await query.limit(parseInt(limit)).offset(offset);
|
|
res.json({ leads: leads.map(translateLead), total: Number(countResult[0].count), page: parseInt(page) });
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: 'Erro ao listar leads' });
|
|
}
|
|
});
|
|
router.get('/:id', authenticate, async (req, res) => {
|
|
try {
|
|
const id = Number(req.params.id);
|
|
const lead = await db('leads').where({ id }).first();
|
|
if (!lead)
|
|
return res.status(404).json({ error: 'Lead não encontrado' });
|
|
if (req.user?.role === 'partner' && req.user.partner_id && lead.partner_id !== req.user.partner_id) {
|
|
return res.status(403).json({ error: 'Acesso negado' });
|
|
}
|
|
const history = await db('lead_history').where({ lead_id: id }).orderBy('created_at', 'desc');
|
|
res.json({ lead: translateLead(lead), history });
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: 'Erro ao buscar lead' });
|
|
}
|
|
});
|
|
router.post('/', ...protectedRoutes, async (req, res) => {
|
|
try {
|
|
const { name, email, phone, partner_id, source, notes, benefit_id, assigned_to, user_id } = req.body;
|
|
const numericUserId = user_id ? Number(user_id) : null;
|
|
const parsedUserId = Number.isInteger(numericUserId) ? numericUserId : null;
|
|
const leadType = parsedUserId ? 'member' : 'cold';
|
|
const [id] = await db('leads').insert({
|
|
name,
|
|
email,
|
|
phone,
|
|
partner_id: partner_id || null,
|
|
benefit_id: benefit_id || null,
|
|
user_id: parsedUserId,
|
|
lead_type: leadType,
|
|
source: source || 'manual',
|
|
notes: notes || null,
|
|
assigned_to: assigned_to || null,
|
|
status: 'new',
|
|
owner: 'clube67',
|
|
});
|
|
await db('lead_history').insert({
|
|
lead_id: id,
|
|
action: 'created',
|
|
new_value: 'new',
|
|
created_by: req.user?.id || null,
|
|
});
|
|
await ctx.hooks.emit('lead:created', { id, email, partner_id });
|
|
const lead = await db('leads').where({ id }).first();
|
|
res.status(201).json(translateLead(lead));
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: 'Erro ao criar lead' });
|
|
}
|
|
});
|
|
router.patch('/:id/status', ...protectedRoutes, async (req, res) => {
|
|
try {
|
|
const id = Number(req.params.id);
|
|
const { status } = req.body;
|
|
if (!STATUS_VALUES.includes(status))
|
|
return res.status(400).json({ error: 'Status inválido' });
|
|
const lead = await db('leads').where({ id }).first();
|
|
if (!lead)
|
|
return res.status(404).json({ error: 'Lead não encontrado' });
|
|
await db('leads').where({ id }).update({ status, updated_at: db.fn.now() });
|
|
await db('lead_history').insert({
|
|
lead_id: id,
|
|
action: 'status',
|
|
old_value: lead.status,
|
|
new_value: status,
|
|
created_by: req.user?.id || null,
|
|
});
|
|
if (status === 'converted')
|
|
await ctx.hooks.emit('lead:converted', { id });
|
|
if (status === 'lost')
|
|
await ctx.hooks.emit('lead:lost', { id });
|
|
const updated = await db('leads').where({ id }).first();
|
|
res.json(translateLead(updated));
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: 'Erro ao atualizar status' });
|
|
}
|
|
});
|
|
router.post('/:id/notes', ...protectedRoutes, async (req, res) => {
|
|
try {
|
|
const id = Number(req.params.id);
|
|
const { note } = req.body;
|
|
if (!note)
|
|
return res.status(400).json({ error: 'Nota obrigatória' });
|
|
const lead = await db('leads').where({ id }).first();
|
|
if (!lead)
|
|
return res.status(404).json({ error: 'Lead não encontrado' });
|
|
await db('lead_history').insert({
|
|
lead_id: id,
|
|
action: 'note',
|
|
old_value: lead.notes,
|
|
new_value: lead.notes ? `${lead.notes}\n${note}` : note,
|
|
note,
|
|
created_by: req.user?.id || null,
|
|
});
|
|
await db('leads').where({ id }).update({
|
|
notes: db.raw('CONCAT(COALESCE(notes,""), "\n", ?)', [note]),
|
|
updated_at: db.fn.now(),
|
|
});
|
|
const updated = await db('leads').where({ id }).first();
|
|
res.json(translateLead(updated));
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: 'Erro ao salvar nota' });
|
|
}
|
|
});
|
|
return router;
|
|
}
|
|
//# sourceMappingURL=routes.js.map
|