- 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.
312 lines
8.8 KiB
TypeScript
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 } |