// 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 { 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 { 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 { 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 { 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 { 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 { 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 }