Files
miracles_in_motion/src/crm/SalesforceConnector.ts
defiQUG 972669d325 feat: Implement Salesforce Nonprofit Cloud CRM integration and Real-Time Data Processing system
- Added SalesforceConnector for handling Salesforce authentication, case creation, and updates.
- Integrated real-time processing capabilities with StudentAssistanceAI and Salesforce.
- Implemented WebSocket support for real-time updates and request handling.
- Enhanced metrics tracking for processing performance and sync status.
- Added error handling and retry logic for processing requests.
- Created factory function for easy initialization of RealTimeProcessor with default configurations.
2025-10-05 05:29:36 -07:00

312 lines
8.8 KiB
TypeScript

// Phase 3B: Salesforce Nonprofit Cloud CRM Integration
import type { StudentRequest, MatchResult } from '../ai/types'
export interface SalesforceConfig {
instanceUrl: string
clientId: string
clientSecret: string
username: string
password: string
securityToken: string
apiVersion: string
}
export interface SalesforceContact {
Id: string
Name: string
Email: string
Phone: string
Account: {
Id: string
Name: string
}
}
export interface SalesforceCase {
Id: string
Subject: string
Description: string
Status: 'New' | 'In Progress' | 'Closed' | 'Escalated'
Priority: 'Low' | 'Medium' | 'High' | 'Critical'
ContactId: string
CaseNumber: string
CreatedDate: string
LastModifiedDate: string
}
export interface NPSPAllocation {
Id: string
Amount: number
GAU__c: string // General Accounting Unit
Opportunity__c: string
Percent: number
}
class SalesforceConnector {
private config: SalesforceConfig
private accessToken: string | null = null
private instanceUrl: string = ''
constructor(config: SalesforceConfig) {
this.config = config
this.instanceUrl = config.instanceUrl
}
// Authentication with Salesforce
async authenticate(): Promise<boolean> {
try {
const response = await fetch(`${this.config.instanceUrl}/services/oauth2/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'password',
client_id: this.config.clientId,
client_secret: this.config.clientSecret,
username: this.config.username,
password: this.config.password + this.config.securityToken
})
})
if (!response.ok) {
throw new Error(`Authentication failed: ${response.statusText}`)
}
const data = await response.json()
this.accessToken = data.access_token
this.instanceUrl = data.instance_url
return true
} catch (error) {
console.error('Salesforce authentication error:', error)
return false
}
}
// Create assistance request case in Salesforce
async createAssistanceCase(request: StudentRequest): Promise<string | null> {
if (!this.accessToken) {
await this.authenticate()
}
try {
const caseData = {
Subject: `Student Assistance Request - ${request.category}`,
Description: this.formatRequestDescription(request),
Status: 'New',
Priority: this.determinePriority(request),
Origin: 'AI Portal',
Type: 'Student Assistance',
// Custom fields for nonprofit
Student_Name__c: request.studentName,
Student_ID__c: request.studentId,
Need_Category__c: request.category,
Urgency_Level__c: request.urgency,
Location_City__c: request.location.city,
Location_State__c: request.location.state,
Location_Zip__c: request.location.zipCode
}
const response = await fetch(`${this.instanceUrl}/services/data/v${this.config.apiVersion}/sobjects/Case`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(caseData)
})
if (!response.ok) {
throw new Error(`Failed to create case: ${response.statusText}`)
}
const result = await response.json()
return result.id
} catch (error) {
console.error('Error creating Salesforce case:', error)
return null
}
}
// Update case with AI matching results
async updateCaseWithMatching(caseId: string, matchResult: MatchResult): Promise<boolean> {
if (!this.accessToken) {
await this.authenticate()
}
try {
const updateData = {
AI_Match_Confidence__c: matchResult.confidenceScore,
Recommended_Resource_Id__c: matchResult.resourceId,
Recommended_Resource_Name__c: matchResult.resourceName,
Resource_Type__c: matchResult.resourceType,
Estimated_Impact__c: matchResult.estimatedImpact,
Estimated_Cost__c: matchResult.estimatedCost,
Fulfillment_Timeline__c: matchResult.fulfillmentTimeline,
Last_AI_Update__c: new Date().toISOString()
}
const response = await fetch(`${this.instanceUrl}/services/data/v${this.config.apiVersion}/sobjects/Case/${caseId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(updateData)
})
return response.ok
} catch (error) {
console.error('Error updating Salesforce case:', error)
return false
}
}
// Get nonprofit contacts (volunteers, donors, partners)
async getContacts(recordType?: string): Promise<SalesforceContact[]> {
if (!this.accessToken) {
await this.authenticate()
}
try {
let query = `SELECT Id, Name, Email, Phone, Account.Id, Account.Name FROM Contact`
if (recordType) {
query += ` WHERE RecordType.Name = '${recordType}'`
}
const response = await fetch(
`${this.instanceUrl}/services/data/v${this.config.apiVersion}/query?q=${encodeURIComponent(query)}`,
{
headers: {
'Authorization': `Bearer ${this.accessToken}`
}
}
)
if (!response.ok) {
throw new Error(`Failed to fetch contacts: ${response.statusText}`)
}
const data = await response.json()
return data.records
} catch (error) {
console.error('Error fetching Salesforce contacts:', error)
return []
}
}
// Create NPSP allocation for resource tracking
async createResourceAllocation(opportunityId: string, amount: number, gauId: string): Promise<string | null> {
if (!this.accessToken) {
await this.authenticate()
}
try {
const allocationData = {
Amount__c: amount,
GAU__c: gauId,
Opportunity__c: opportunityId,
Percent__c: 100
}
const response = await fetch(`${this.instanceUrl}/services/data/v${this.config.apiVersion}/sobjects/Allocation__c`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(allocationData)
})
if (!response.ok) {
throw new Error(`Failed to create allocation: ${response.statusText}`)
}
const result = await response.json()
return result.id
} catch (error) {
console.error('Error creating resource allocation:', error)
return null
}
}
// Get donation opportunities for matching
async getDonationOpportunities(category?: string): Promise<any[]> {
if (!this.accessToken) {
await this.authenticate()
}
try {
let query = `SELECT Id, Name, Amount, StageName, CloseDate, Account.Name
FROM Opportunity
WHERE StageName IN ('Pledged', 'Posted')
AND CloseDate >= TODAY`
if (category) {
query += ` AND Category__c = '${category}'`
}
const response = await fetch(
`${this.instanceUrl}/services/data/v${this.config.apiVersion}/query?q=${encodeURIComponent(query)}`,
{
headers: {
'Authorization': `Bearer ${this.accessToken}`
}
}
)
if (!response.ok) {
throw new Error(`Failed to fetch opportunities: ${response.statusText}`)
}
const data = await response.json()
return data.records
} catch (error) {
console.error('Error fetching donation opportunities:', error)
return []
}
}
private formatRequestDescription(request: StudentRequest): string {
return `
AI-Generated Student Assistance Request
Student Information:
- Name: ${request.studentName}
- ID: ${request.studentId}
- Location: ${request.location.city}, ${request.location.state} ${request.location.zipCode}
Request Details:
- Category: ${request.category}
- Urgency: ${request.urgency}
- Description: ${request.description}
Additional Information:
- Estimated Cost: $${request.estimatedCost || 0}
- Required Skills: ${request.requiredSkills?.join(', ') || 'None specified'}
- Deadline: ${request.deadline ? new Date(request.deadline).toLocaleDateString() : 'Not specified'}
Submission Details:
- Submitted: ${new Date(request.submittedAt).toLocaleString()}
- Request ID: ${request.id}
`.trim()
}
private determinePriority(request: StudentRequest): 'Low' | 'Medium' | 'High' | 'Critical' {
switch (request.urgency) {
case 'emergency':
return 'Critical'
case 'high':
return 'High'
case 'medium':
return 'Medium'
case 'low':
default:
return 'Low'
}
}
}
export { SalesforceConnector }