- audit.controller.js: Remove unused fs/path imports, add AuditLog import, fix indentation, use const for userCostFactors, use property shorthand - crm.controller.js: Remove unused Contact, MediaInquiry, CaseSubmission imports - cases.controller.js: Remove unused GovernanceLog, BoundaryEnforcer imports - DiskMetrics.model.js: Use template literals instead of string concatenation - framework-content-analysis.controller.js: Use template literals, prefix unused destructured vars with underscore - feedback.controller.js: Use template literal for string concat - DeliberationSession.model.js: Fix line length by moving comments to own lines Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
284 lines
6.4 KiB
JavaScript
284 lines
6.4 KiB
JavaScript
/**
|
|
* CRM Controller
|
|
* Multi-project CRM system for contacts, organizations, activities, and SLAs
|
|
*/
|
|
|
|
const UnifiedContact = require('../models/UnifiedContact.model');
|
|
const Organization = require('../models/Organization.model');
|
|
const ActivityTimeline = require('../models/ActivityTimeline.model');
|
|
const ResponseTemplate = require('../models/ResponseTemplate.model');
|
|
const SLATracking = require('../models/SLATracking.model');
|
|
|
|
/**
|
|
* Get CRM dashboard statistics
|
|
*/
|
|
async function getDashboardStats(req, res) {
|
|
try {
|
|
const [contactStats, orgStats, slaStats, templateStats] = await Promise.all([
|
|
UnifiedContact.getStats(),
|
|
Organization.getStats(),
|
|
SLATracking.getStats({ project: 'tractatus' }),
|
|
ResponseTemplate.getStats()
|
|
]);
|
|
|
|
res.json({
|
|
success: true,
|
|
stats: {
|
|
contacts: contactStats,
|
|
organizations: orgStats,
|
|
sla: slaStats,
|
|
templates: templateStats
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('CRM dashboard stats error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to load dashboard statistics'
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List unified contacts
|
|
*/
|
|
async function listContacts(req, res) {
|
|
try {
|
|
const { project, status, tag, organization_id, limit = 50, skip = 0 } = req.query;
|
|
|
|
const filters = {};
|
|
if (project) filters.project = project;
|
|
if (status) filters.status = status;
|
|
if (tag) filters.tag = tag;
|
|
if (organization_id) filters.organization_id = organization_id;
|
|
|
|
const contacts = await UnifiedContact.list(filters, {
|
|
limit: parseInt(limit),
|
|
skip: parseInt(skip)
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
total: contacts.length,
|
|
contacts
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('List contacts error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to list contacts'
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get single contact with full details
|
|
*/
|
|
async function getContact(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const contact = await UnifiedContact.findById(id);
|
|
if (!contact) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Contact not found'
|
|
});
|
|
}
|
|
|
|
// Get organization if linked
|
|
let organization = null;
|
|
if (contact.organization_id) {
|
|
organization = await Organization.findById(contact.organization_id);
|
|
}
|
|
|
|
// Get activity timeline
|
|
const activities = await ActivityTimeline.getByContact(id, { limit: 50 });
|
|
|
|
res.json({
|
|
success: true,
|
|
contact,
|
|
organization,
|
|
activities
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Get contact error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to load contact'
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List organizations
|
|
*/
|
|
async function listOrganizations(req, res) {
|
|
try {
|
|
const { type, country, project, status, tier, limit = 50, skip = 0 } = req.query;
|
|
|
|
const filters = {};
|
|
if (type) filters.type = type;
|
|
if (country) filters.country = country;
|
|
if (project) filters.project = project;
|
|
if (status) filters.status = status;
|
|
if (tier) filters.tier = tier;
|
|
|
|
const organizations = await Organization.list(filters, {
|
|
limit: parseInt(limit),
|
|
skip: parseInt(skip)
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
total: organizations.length,
|
|
organizations
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('List organizations error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to list organizations'
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get single organization with contacts and activity
|
|
*/
|
|
async function getOrganization(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const organization = await Organization.findById(id);
|
|
if (!organization) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Organization not found'
|
|
});
|
|
}
|
|
|
|
// Get contacts at this organization
|
|
const contacts = await UnifiedContact.findByOrganization(id, { limit: 100 });
|
|
|
|
// Get activity timeline
|
|
const activities = await ActivityTimeline.getByOrganization(id, { limit: 50 });
|
|
|
|
res.json({
|
|
success: true,
|
|
organization,
|
|
contacts,
|
|
activities
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Get organization error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to load organization'
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get SLA dashboard
|
|
*/
|
|
async function getSLADashboard(req, res) {
|
|
try {
|
|
const { project = 'tractatus' } = req.query;
|
|
|
|
const [stats, pending, approaching, breached] = await Promise.all([
|
|
SLATracking.getStats({ project }),
|
|
SLATracking.getPending({ project, limit: 20 }),
|
|
SLATracking.getApproachingBreach({ project }),
|
|
SLATracking.getBreached({ project, limit: 20 })
|
|
]);
|
|
|
|
res.json({
|
|
success: true,
|
|
stats,
|
|
pending,
|
|
approaching_breach: approaching,
|
|
breached
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('SLA dashboard error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to load SLA dashboard'
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List response templates
|
|
*/
|
|
async function listTemplates(req, res) {
|
|
try {
|
|
const { category, language, project, tag, limit = 50, skip = 0 } = req.query;
|
|
|
|
const filters = {};
|
|
if (category) filters.category = category;
|
|
if (language) filters.language = language;
|
|
if (project) filters.project = project;
|
|
if (tag) filters.tag = tag;
|
|
|
|
const templates = await ResponseTemplate.list(filters, {
|
|
limit: parseInt(limit),
|
|
skip: parseInt(skip)
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
total: templates.length,
|
|
templates
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('List templates error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to list templates'
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render template with variables
|
|
*/
|
|
async function renderTemplate(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
const { variables } = req.body;
|
|
|
|
const rendered = await ResponseTemplate.render(id, variables || {});
|
|
|
|
res.json({
|
|
success: true,
|
|
rendered
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Render template error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to render template'
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
getDashboardStats,
|
|
listContacts,
|
|
getContact,
|
|
listOrganizations,
|
|
getOrganization,
|
|
getSLADashboard,
|
|
listTemplates,
|
|
renderTemplate
|
|
};
|