cre-sync/lib/ghl/services/opportunities.ts
BusyBee3333 4e6467ffb0 Add CRESync CRM application with Setup page
- Build complete Next.js CRM for commercial real estate
- Add authentication with JWT sessions and role-based access
- Add GoHighLevel API integration for contacts, conversations, opportunities
- Add AI-powered Control Center with tool calling
- Add Setup page with onboarding checklist (/setup)
- Add sidebar navigation with Setup menu item
- Fix type errors in onboarding API, GHL services, and control center tools
- Add Prisma schema with SQLite for local development
- Add UI components with clay morphism design system

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 17:30:55 -05:00

220 lines
7.0 KiB
TypeScript

import { GHLClient } from '../client';
import {
GHLOpportunity,
GHLPipeline,
GHLPipelineStage,
GHLPaginatedResponse
} from '@/types/ghl';
export interface CreateOpportunityDTO {
name: string;
pipelineId: string;
pipelineStageId: string;
contactId: string;
monetaryValue?: number;
assignedTo?: string;
status?: 'open' | 'won' | 'lost' | 'abandoned';
customFields?: { key: string; value: any }[];
}
export interface UpdateOpportunityDTO {
name?: string;
pipelineStageId?: string;
monetaryValue?: number;
status?: 'open' | 'won' | 'lost' | 'abandoned';
assignedTo?: string;
customFields?: { key: string; value: any }[];
}
export interface OpportunitySearchParams {
locationId?: string;
pipelineId?: string;
pipelineStageId?: string;
contactId?: string;
status?: 'open' | 'won' | 'lost' | 'abandoned' | 'all';
assignedTo?: string;
limit?: number;
startAfterId?: string;
query?: string;
}
export class OpportunitiesService {
constructor(private client: GHLClient) {}
// Get all opportunities
async getAll(params?: OpportunitySearchParams): Promise<GHLPaginatedResponse<GHLOpportunity>> {
const searchParams: Record<string, string> = {
locationId: params?.locationId || this.client.locationID,
};
if (params?.pipelineId) searchParams.pipelineId = params.pipelineId;
if (params?.pipelineStageId) searchParams.pipelineStageId = params.pipelineStageId;
if (params?.contactId) searchParams.contactId = params.contactId;
if (params?.status) searchParams.status = params.status;
if (params?.assignedTo) searchParams.assignedTo = params.assignedTo;
if (params?.limit) searchParams.limit = String(params.limit);
if (params?.startAfterId) searchParams.startAfterId = params.startAfterId;
if (params?.query) searchParams.query = params.query;
return this.client.get('/opportunities/search', searchParams);
}
// Get a single opportunity by ID
async getById(opportunityId: string): Promise<GHLOpportunity> {
return this.client.get(`/opportunities/${opportunityId}`);
}
// Create a new opportunity
async create(data: CreateOpportunityDTO): Promise<GHLOpportunity> {
return this.client.post('/opportunities/', {
...data,
locationId: this.client.locationID,
});
}
// Update an existing opportunity
async update(opportunityId: string, data: UpdateOpportunityDTO): Promise<GHLOpportunity> {
return this.client.put(`/opportunities/${opportunityId}`, data);
}
// Delete an opportunity
async delete(opportunityId: string): Promise<void> {
await this.client.delete(`/opportunities/${opportunityId}`);
}
// Move opportunity to a different stage
async moveToStage(opportunityId: string, stageId: string): Promise<GHLOpportunity> {
return this.update(opportunityId, { pipelineStageId: stageId });
}
// Mark opportunity as won
async markAsWon(opportunityId: string): Promise<GHLOpportunity> {
return this.update(opportunityId, { status: 'won' });
}
// Mark opportunity as lost
async markAsLost(opportunityId: string): Promise<GHLOpportunity> {
return this.update(opportunityId, { status: 'lost' });
}
// Mark opportunity as abandoned
async markAsAbandoned(opportunityId: string): Promise<GHLOpportunity> {
return this.update(opportunityId, { status: 'abandoned' });
}
// Reopen an opportunity
async reopen(opportunityId: string): Promise<GHLOpportunity> {
return this.update(opportunityId, { status: 'open' });
}
// Get opportunities by contact
async getByContact(contactId: string): Promise<GHLOpportunity[]> {
const result = await this.getAll({ contactId });
return result.data || [];
}
// Get opportunities by pipeline
async getByPipeline(pipelineId: string): Promise<GHLOpportunity[]> {
const result = await this.getAll({ pipelineId });
return result.data || [];
}
// Get opportunities by stage
async getByStage(pipelineStageId: string): Promise<GHLOpportunity[]> {
const result = await this.getAll({ pipelineStageId });
return result.data || [];
}
// Update monetary value
async updateValue(opportunityId: string, value: number): Promise<GHLOpportunity> {
return this.update(opportunityId, { monetaryValue: value });
}
// Assign opportunity to a user
async assign(opportunityId: string, userId: string): Promise<GHLOpportunity> {
return this.update(opportunityId, { assignedTo: userId });
}
}
export class PipelinesService {
constructor(private client: GHLClient) {}
// Get all pipelines
async getAll(): Promise<GHLPipeline[]> {
const response = await this.client.get('/opportunities/pipelines', {
locationId: this.client.locationID,
});
return (response as any).pipelines || [];
}
// Get a single pipeline by ID
async getById(pipelineId: string): Promise<GHLPipeline> {
return this.client.get(`/opportunities/pipelines/${pipelineId}`);
}
// Get stages for a pipeline
async getStages(pipelineId: string): Promise<GHLPipelineStage[]> {
const pipeline = await this.getById(pipelineId);
return pipeline.stages || [];
}
// Create a new pipeline
async create(data: { name: string; stages: { name: string }[] }): Promise<GHLPipeline> {
return this.client.post('/opportunities/pipelines/', {
...data,
locationId: this.client.locationID,
});
}
// Update a pipeline
async update(pipelineId: string, data: { name?: string }): Promise<GHLPipeline> {
return this.client.put(`/opportunities/pipelines/${pipelineId}`, data);
}
// Delete a pipeline
async delete(pipelineId: string): Promise<void> {
await this.client.delete(`/opportunities/pipelines/${pipelineId}`);
}
// Add a stage to a pipeline
async addStage(pipelineId: string, stageName: string, position?: number): Promise<GHLPipelineStage> {
return this.client.post(`/opportunities/pipelines/${pipelineId}/stages`, {
name: stageName,
...(position !== undefined && { position }),
});
}
// Update a stage
async updateStage(pipelineId: string, stageId: string, data: { name?: string; position?: number }): Promise<GHLPipelineStage> {
return this.client.put(`/opportunities/pipelines/${pipelineId}/stages/${stageId}`, data);
}
// Delete a stage
async deleteStage(pipelineId: string, stageId: string): Promise<void> {
await this.client.delete(`/opportunities/pipelines/${pipelineId}/stages/${stageId}`);
}
// Get pipeline statistics
async getStats(pipelineId: string): Promise<{
totalOpportunities: number;
totalValue: number;
byStage: { stageId: string; count: number; value: number }[];
}> {
// This would need to aggregate from opportunities
// Implementing a basic version
const pipeline = await this.getById(pipelineId);
const stages = pipeline.stages || [];
// This is a simplified version - actual implementation would aggregate from opportunities
return {
totalOpportunities: 0,
totalValue: 0,
byStage: stages.map(stage => ({
stageId: stage.id,
count: 0,
value: 0,
})),
};
}
}