Add comprehensive admin features and multi-system integration
- Add admin dashboard with sync controls and data browser - Implement RMM, Auvik, and Addigy organization mappings - Add chunked ticket sync with progress tracking - Implement entity sync service with rate limiting - Add analytics engine and performance optimizer - Create data browser for all PSA entities - Add navigation components and UI improvements - Implement background processing and sync services - Add comprehensive documentation and migration scripts - Update configuration items with multi-system support - Enhance contact management and purchase history - Add issue type assignment and LLM analyzer - Improve error handling and logging utilities
This commit is contained in:
parent
e8462ef301
commit
6eee14f8af
171 changed files with 32671 additions and 621 deletions
|
|
@ -211,3 +211,14 @@ export enum AddigyDeviceType {
|
|||
iPad = 'ipad',
|
||||
AppleTV = 'appletv',
|
||||
}
|
||||
|
||||
// Mapping types for Addigy organizations to Autotask companies
|
||||
export interface AddigyOrgMapping {
|
||||
id: number;
|
||||
addigyOrgId: string;
|
||||
addigyOrgName: string;
|
||||
autotaskCompanyId: number;
|
||||
autotaskCompanyName: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
|
|
|||
276
lib/types/analytics.ts
Normal file
276
lib/types/analytics.ts
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
/**
|
||||
* Analytics Types
|
||||
* TypeScript definitions for analytics, scoring, and insights
|
||||
*/
|
||||
|
||||
// Score interfaces
|
||||
export interface ActivityScore {
|
||||
score: number; // 0 to 1
|
||||
factors: string[];
|
||||
breakdown: {
|
||||
completeness: number;
|
||||
consistency: number;
|
||||
duration: number;
|
||||
categorization: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ContentScore {
|
||||
score: number; // 0 to 1
|
||||
factors: string[];
|
||||
breakdown: {
|
||||
notesQuality: number;
|
||||
titleClarity: number;
|
||||
internalNotes: number;
|
||||
technicalDetail: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TimelinessScore {
|
||||
score: number; // 0 to 1
|
||||
factors: string[];
|
||||
breakdown: {
|
||||
entryDelay: number;
|
||||
businessHours: number;
|
||||
regularity: number;
|
||||
approvalTimeliness: number;
|
||||
};
|
||||
}
|
||||
|
||||
// Insight interfaces
|
||||
export interface AnalyticsInsight {
|
||||
type: 'success' | 'warning' | 'error' | 'info';
|
||||
category: 'activity' | 'content' | 'timeliness' | 'overall' | 'billing' | 'performance';
|
||||
title: string;
|
||||
description: string;
|
||||
recommendation: string;
|
||||
severity?: 'low' | 'medium' | 'high';
|
||||
actionable?: boolean;
|
||||
}
|
||||
|
||||
// Analysis interfaces
|
||||
export interface TimeEntryAnalysis {
|
||||
timeEntryId: number;
|
||||
activityScore: ActivityScore;
|
||||
contentScore: ContentScore;
|
||||
timelinessScore: TimelinessScore;
|
||||
overallScore: number; // 0 to 1
|
||||
insights: AnalyticsInsight[];
|
||||
analyzedAt: Date;
|
||||
}
|
||||
|
||||
export interface AggregateAnalysis {
|
||||
totalEntries: number;
|
||||
totalHours: number;
|
||||
averageHoursPerEntry: number;
|
||||
dateRange: {
|
||||
earliest: Date;
|
||||
latest: Date;
|
||||
};
|
||||
scores: {
|
||||
activity: number;
|
||||
content: number;
|
||||
timeliness: number;
|
||||
overall: number;
|
||||
};
|
||||
insights: AnalyticsInsight[];
|
||||
patterns: {
|
||||
dayOfWeek: number[]; // 7 values, Sunday = 0
|
||||
hourly: number[]; // 24 values
|
||||
};
|
||||
trends: {
|
||||
weekly: Array<{
|
||||
week: Date;
|
||||
hours: number;
|
||||
entries: number;
|
||||
}>;
|
||||
};
|
||||
analyzedAt: Date;
|
||||
}
|
||||
|
||||
// Timeline interfaces
|
||||
export interface TimelineEvent {
|
||||
id: string;
|
||||
type: 'time_entry' | 'key_moment' | 'milestone';
|
||||
timestamp: Date;
|
||||
title: string;
|
||||
description?: string;
|
||||
duration?: number; // in hours
|
||||
metadata?: Record<string, any>;
|
||||
score?: number; // 0 to 1
|
||||
isHumanActivity: boolean;
|
||||
importance: 'low' | 'medium' | 'high' | 'critical';
|
||||
}
|
||||
|
||||
export interface TimelineView {
|
||||
events: TimelineEvent[];
|
||||
dateRange: {
|
||||
start: Date;
|
||||
end: Date;
|
||||
};
|
||||
timeRange: 'hour' | 'day' | 'week' | 'month';
|
||||
filters: {
|
||||
resourceIds?: number[];
|
||||
ticketIds?: number[];
|
||||
projectIds?: number[];
|
||||
activityTypes?: string[];
|
||||
minScore?: number;
|
||||
};
|
||||
summary: {
|
||||
totalEvents: number;
|
||||
humanActivities: number;
|
||||
systemActivities: number;
|
||||
totalHours: number;
|
||||
averageScore: number;
|
||||
};
|
||||
}
|
||||
|
||||
// LLM Analysis interfaces
|
||||
export interface LLMAnalysisRequest {
|
||||
timeEntries: Array<{
|
||||
id: number;
|
||||
notes?: string;
|
||||
title?: string;
|
||||
hours_worked: number;
|
||||
entry_date: string;
|
||||
resource_name?: string;
|
||||
ticket_title?: string;
|
||||
}>;
|
||||
analysisType: 'productivity' | 'quality' | 'patterns' | 'anomalies' | 'comprehensive';
|
||||
context?: {
|
||||
timeRange: string;
|
||||
resourceIds?: number[];
|
||||
projectIds?: number[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface LLMAnalysisResponse {
|
||||
insights: string[];
|
||||
patterns: Array<{
|
||||
type: string;
|
||||
description: string;
|
||||
frequency: number;
|
||||
impact: 'low' | 'medium' | 'high';
|
||||
}>;
|
||||
recommendations: Array<{
|
||||
category: string;
|
||||
priority: 'low' | 'medium' | 'high';
|
||||
action: string;
|
||||
expectedImpact: string;
|
||||
}>;
|
||||
summary: {
|
||||
overallQuality: number; // 0 to 1
|
||||
productivityLevel: number; // 0 to 1
|
||||
keyFindings: string[];
|
||||
};
|
||||
processingTime: number; // milliseconds
|
||||
tokensUsed: number;
|
||||
}
|
||||
|
||||
// Scoring algorithm interfaces
|
||||
export interface ScoringWeights {
|
||||
activity: number;
|
||||
content: number;
|
||||
timeliness: number;
|
||||
}
|
||||
|
||||
export interface ScoringConfiguration {
|
||||
weights: ScoringWeights;
|
||||
thresholds: {
|
||||
excellent: number;
|
||||
good: number;
|
||||
average: number;
|
||||
poor: number;
|
||||
};
|
||||
factors: {
|
||||
activity: {
|
||||
completeness: number;
|
||||
consistency: number;
|
||||
duration: number;
|
||||
categorization: number;
|
||||
};
|
||||
content: {
|
||||
notesQuality: number;
|
||||
titleClarity: number;
|
||||
internalNotes: number;
|
||||
technicalDetail: number;
|
||||
};
|
||||
timeliness: {
|
||||
entryDelay: number;
|
||||
businessHours: number;
|
||||
regularity: number;
|
||||
approvalTimeliness: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Analytics query interfaces
|
||||
export interface AnalyticsQuery {
|
||||
timeRange: {
|
||||
start: Date;
|
||||
end: Date;
|
||||
};
|
||||
filters: {
|
||||
resourceIds?: number[];
|
||||
ticketIds?: number[];
|
||||
taskIds?: number[];
|
||||
projectIds?: number[];
|
||||
companyIds?: number[];
|
||||
minHours?: number;
|
||||
maxHours?: number;
|
||||
billable?: boolean;
|
||||
approved?: boolean;
|
||||
activityTypes?: string[];
|
||||
};
|
||||
groupBy?: 'resource' | 'ticket' | 'project' | 'company' | 'day' | 'week' | 'month';
|
||||
includeScores?: boolean;
|
||||
includeInsights?: boolean;
|
||||
includePatterns?: boolean;
|
||||
includeTrends?: boolean;
|
||||
}
|
||||
|
||||
export interface AnalyticsQueryResult {
|
||||
data: Array<{
|
||||
group: string;
|
||||
totalEntries: number;
|
||||
totalHours: number;
|
||||
averageHoursPerEntry: number;
|
||||
scores?: {
|
||||
activity: number;
|
||||
content: number;
|
||||
timeliness: number;
|
||||
overall: number;
|
||||
};
|
||||
insights?: AnalyticsInsight[];
|
||||
}>;
|
||||
summary: AggregateAnalysis;
|
||||
query: AnalyticsQuery;
|
||||
processedAt: Date;
|
||||
}
|
||||
|
||||
// Export and reporting interfaces
|
||||
export interface AnalyticsExport {
|
||||
format: 'csv' | 'excel' | 'pdf' | 'json';
|
||||
data: {
|
||||
timeEntries: any[];
|
||||
analyses: TimeEntryAnalysis[];
|
||||
summary: AggregateAnalysis;
|
||||
insights: AnalyticsInsight[];
|
||||
};
|
||||
metadata: {
|
||||
exportedAt: Date;
|
||||
timeRange: string;
|
||||
filters: string;
|
||||
recordCount: number;
|
||||
};
|
||||
}
|
||||
|
||||
// Performance metrics
|
||||
export interface AnalyticsPerformanceMetrics {
|
||||
processingTime: number; // milliseconds
|
||||
recordsProcessed: number;
|
||||
recordsPerSecond: number;
|
||||
memoryUsage: number; // MB
|
||||
cacheHitRate: number; // percentage
|
||||
errors: string[];
|
||||
}
|
||||
|
|
@ -183,6 +183,8 @@ export interface ConfigurationItem {
|
|||
setupFee?: number;
|
||||
sourceProductID?: number;
|
||||
type?: number;
|
||||
configurationItemType?: number; // API field name (camelCase)
|
||||
configuration_item_type?: number; // Database field name (snake_case)
|
||||
vendorID?: number;
|
||||
vendorName?: string;
|
||||
warrantyExpirationDate?: string;
|
||||
|
|
@ -207,6 +209,7 @@ export interface PicklistValue {
|
|||
sortOrder?: number;
|
||||
isActive?: boolean;
|
||||
isSystem?: boolean;
|
||||
parentValue?: number | string;
|
||||
}
|
||||
|
||||
export interface EntityField {
|
||||
|
|
@ -221,6 +224,53 @@ export interface EntityField {
|
|||
picklistValues?: PicklistValue[];
|
||||
}
|
||||
|
||||
export interface AutotaskTimeEntry {
|
||||
id: number;
|
||||
resourceID: number;
|
||||
ticketID?: number;
|
||||
taskID?: number;
|
||||
projectID?: number;
|
||||
companyID?: number;
|
||||
dateWorked: string; // ISO date string - Autotask uses dateWorked
|
||||
hoursWorked: number; // Hours worked on this entry
|
||||
summaryNotes?: string; // Autotask uses summaryNotes not notes
|
||||
internalNotes?: string;
|
||||
title?: string;
|
||||
type?: number;
|
||||
startDateTime?: string; // ISO datetime string
|
||||
endDateTime?: string; // ISO datetime string
|
||||
billable?: boolean;
|
||||
billingRate?: number;
|
||||
billingRateCurrencyID?: number;
|
||||
costRate?: number;
|
||||
costRateCurrencyID?: number;
|
||||
cost?: number;
|
||||
costCurrencyID?: number;
|
||||
revenue?: number;
|
||||
revenueCurrencyID?: number;
|
||||
margin?: number;
|
||||
marginCurrencyID?: number;
|
||||
approved?: boolean;
|
||||
approvedByResourceID?: number;
|
||||
approvedDateTime?: string; // ISO datetime string
|
||||
nonBillable?: boolean;
|
||||
contractServiceID?: number;
|
||||
contractServiceBundleID?: number;
|
||||
roleID?: number;
|
||||
departmentID?: number;
|
||||
locationID?: number;
|
||||
allocationCodeID?: number;
|
||||
impProjectScheduleID?: number;
|
||||
impProjectScheduleTaskID?: number;
|
||||
apiVendorID?: number;
|
||||
createDate: string; // ISO datetime string
|
||||
lastModifiedDate?: string; // ISO datetime string
|
||||
userDefinedFields?: Array<{
|
||||
name: string;
|
||||
value: any;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
item?: T;
|
||||
items?: T[];
|
||||
|
|
|
|||
111
lib/types/auvik.ts
Normal file
111
lib/types/auvik.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// Auvik API Type Definitions
|
||||
|
||||
export interface AuvikNetworkInterface {
|
||||
interfaceName: string;
|
||||
status: string;
|
||||
speed?: number;
|
||||
macAddress?: string;
|
||||
ipAddress?: string;
|
||||
vlan?: string;
|
||||
}
|
||||
|
||||
export interface AuvikDevice {
|
||||
id: string;
|
||||
deviceName: string;
|
||||
serialNumber?: string;
|
||||
macAddresses?: string[];
|
||||
ipAddresses: string[];
|
||||
deviceType: string;
|
||||
manufacturer?: string;
|
||||
model?: string;
|
||||
makeModel?: string;
|
||||
vendorName?: string;
|
||||
firmwareVersion?: string;
|
||||
softwareVersion?: string;
|
||||
onlineStatus: 'online' | 'offline' | 'unknown';
|
||||
lastSeenTime?: string;
|
||||
uptime?: number;
|
||||
tenantId: string;
|
||||
tenantName?: string;
|
||||
description?: string;
|
||||
networkInterfaces?: AuvikNetworkInterface[];
|
||||
}
|
||||
|
||||
export interface AuvikTenant {
|
||||
id: string;
|
||||
domainPrefix: string;
|
||||
tenantType: 'multiClient' | 'client';
|
||||
parentId?: string;
|
||||
}
|
||||
|
||||
export interface AuvikDeviceResponse {
|
||||
data: Array<{
|
||||
type: string;
|
||||
id: string;
|
||||
attributes: {
|
||||
ipAddresses: string[];
|
||||
deviceName: string;
|
||||
deviceType: string;
|
||||
makeModel?: string;
|
||||
vendorName?: string;
|
||||
softwareVersion?: string;
|
||||
serialNumber?: string;
|
||||
description?: string;
|
||||
firmwareVersion?: string;
|
||||
lastModified?: string;
|
||||
lastSeenTime?: string;
|
||||
onlineStatus: string;
|
||||
};
|
||||
relationships?: {
|
||||
tenant?: {
|
||||
data: {
|
||||
type: string;
|
||||
id: string;
|
||||
attributes?: {
|
||||
domainPrefix: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}>;
|
||||
links?: {
|
||||
next?: string;
|
||||
first?: string;
|
||||
last?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AuvikTenantResponse {
|
||||
data: Array<{
|
||||
type: string;
|
||||
id: string;
|
||||
attributes: {
|
||||
domainPrefix: string;
|
||||
tenantType: string;
|
||||
};
|
||||
relationships?: {
|
||||
parent?: {
|
||||
data: {
|
||||
type: string;
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface AuvikClientConfig {
|
||||
apiUrl: string;
|
||||
apiUser: string;
|
||||
apiKey: string;
|
||||
}
|
||||
|
||||
export interface AuvikTenantMapping {
|
||||
id: number;
|
||||
auvikTenantId: string;
|
||||
auvikTenantName: string;
|
||||
autotaskCompanyId: number;
|
||||
autotaskCompanyName: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
513
lib/types/database.ts
Normal file
513
lib/types/database.ts
Normal file
|
|
@ -0,0 +1,513 @@
|
|||
/**
|
||||
* Database Types and Interfaces
|
||||
* TypeScript definitions matching PostgreSQL table schemas
|
||||
*/
|
||||
|
||||
// Base audit fields present in all tables
|
||||
export interface AuditFields {
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
synced_at: Date;
|
||||
is_deleted: boolean;
|
||||
deleted_at?: Date | null;
|
||||
}
|
||||
|
||||
// Company entity
|
||||
export interface Company extends AuditFields {
|
||||
id: number;
|
||||
company_name?: string | null;
|
||||
company_number?: string | null;
|
||||
phone?: string | null;
|
||||
fax?: string | null;
|
||||
website?: string | null;
|
||||
address1?: string | null;
|
||||
address2?: string | null;
|
||||
city?: string | null;
|
||||
state?: string | null;
|
||||
postal_code?: string | null;
|
||||
country?: string | null;
|
||||
is_active?: boolean;
|
||||
company_type?: number | null;
|
||||
owner_resource_id?: number | null;
|
||||
territory_id?: number | null;
|
||||
market_segment_id?: number | null;
|
||||
competitor_id?: number | null;
|
||||
billing_address1?: string | null;
|
||||
billing_address2?: string | null;
|
||||
billing_city?: string | null;
|
||||
billing_state?: string | null;
|
||||
billing_postal_code?: string | null;
|
||||
billing_country?: string | null;
|
||||
tax_id?: string | null;
|
||||
tax_exempt?: boolean;
|
||||
tax_region_id?: number | null;
|
||||
currency_id?: number | null;
|
||||
invoice_method?: number | null;
|
||||
invoice_template_id?: number | null;
|
||||
quote_template_id?: number | null;
|
||||
key_account_icon?: number | null;
|
||||
last_activity_date?: Date | null;
|
||||
last_tracked_modification_date_time?: Date | null;
|
||||
api_vendor_id?: number | null;
|
||||
}
|
||||
|
||||
// Resource (User) entity
|
||||
export interface Resource extends AuditFields {
|
||||
id: number;
|
||||
first_name?: string | null;
|
||||
last_name?: string | null;
|
||||
email?: string | null;
|
||||
user_name?: string | null;
|
||||
title?: string | null;
|
||||
office_phone?: string | null;
|
||||
mobile_phone?: string | null;
|
||||
office_extension?: string | null;
|
||||
is_active?: boolean;
|
||||
location_id?: number | null;
|
||||
resource_type?: number | null;
|
||||
pay_roll_identifier?: string | null;
|
||||
hire_date?: Date | null;
|
||||
travel_availability_pct?: number | null;
|
||||
survey_resource_rating?: number | null;
|
||||
}
|
||||
|
||||
// Contact entity
|
||||
export interface Contact extends AuditFields {
|
||||
id: number;
|
||||
company_id: number;
|
||||
first_name?: string | null;
|
||||
last_name?: string | null;
|
||||
title?: string | null;
|
||||
email_address?: string | null;
|
||||
email_address2?: string | null;
|
||||
email_address3?: string | null;
|
||||
phone?: string | null;
|
||||
extension?: string | null;
|
||||
alternate_phone?: string | null;
|
||||
mobile_phone?: string | null;
|
||||
fax?: string | null;
|
||||
address_line?: string | null;
|
||||
address_line1?: string | null;
|
||||
city?: string | null;
|
||||
state?: string | null;
|
||||
zip_code?: string | null;
|
||||
country?: string | null;
|
||||
is_active?: boolean;
|
||||
name_prefix?: string | null;
|
||||
name_suffix?: string | null;
|
||||
facebook_url?: string | null;
|
||||
twitter_url?: string | null;
|
||||
linked_in_url?: string | null;
|
||||
primary_contact?: boolean;
|
||||
account_physical_location_id?: number | null;
|
||||
solicitation_opt_out?: boolean;
|
||||
room_number?: string | null;
|
||||
last_activity_date?: Date | null;
|
||||
last_modified_date?: Date | null;
|
||||
api_vendor_id?: number | null;
|
||||
}
|
||||
|
||||
// Project entity
|
||||
export interface Project extends AuditFields {
|
||||
id: number;
|
||||
company_id: number;
|
||||
project_name?: string | null;
|
||||
project_number?: string | null;
|
||||
description?: string | null;
|
||||
start_date_time?: Date | null;
|
||||
end_date_time?: Date | null;
|
||||
estimated_time?: number | null;
|
||||
actual_hours?: number | null;
|
||||
estimated_sale_cost?: number | null;
|
||||
labor_estimated_costs?: number | null;
|
||||
labor_estimated_revenue?: number | null;
|
||||
project_cost_estimated_margin_percentage?: number | null;
|
||||
status?: number | null;
|
||||
type?: number | null;
|
||||
project_lead_resource_id?: number | null;
|
||||
account_executive_resource_id?: number | null;
|
||||
owner_resource_id?: number | null;
|
||||
creator_resource_id?: number | null;
|
||||
completed_percentage?: number | null;
|
||||
completed_date_time?: Date | null;
|
||||
duration?: number | null;
|
||||
original_estimated_revenue?: number | null;
|
||||
estimated_time_cost?: number | null;
|
||||
purchase_order_number?: string | null;
|
||||
business_division_subdivision_id?: number | null;
|
||||
line_of_business_id?: number | null;
|
||||
department?: number | null;
|
||||
last_activity_date_time?: Date | null;
|
||||
last_activity_person_type?: number | null;
|
||||
last_activity_resource_id?: number | null;
|
||||
}
|
||||
|
||||
// Ticket entity
|
||||
export interface Ticket extends AuditFields {
|
||||
id: number;
|
||||
company_id: number;
|
||||
ticket_number?: string | null;
|
||||
title?: string | null;
|
||||
description?: string | null;
|
||||
status?: number | null;
|
||||
priority?: number | null;
|
||||
queue_id?: number | null;
|
||||
issue_type?: number | null;
|
||||
sub_issue_type?: number | null;
|
||||
source?: number | null;
|
||||
assigned_resource_id?: number | null;
|
||||
assigned_resource_role_id?: number | null;
|
||||
contact_id?: number | null;
|
||||
account_physical_location_id?: number | null;
|
||||
due_date_time?: Date | null;
|
||||
estimated_hours?: number | null;
|
||||
completed_date?: Date | null;
|
||||
create_date?: Date | null;
|
||||
created_by_contact_id?: number | null;
|
||||
last_activity_date?: Date | null;
|
||||
last_customer_notification_date_time?: Date | null;
|
||||
last_customer_visible_activity_date_time?: Date | null;
|
||||
first_response_date_time?: Date | null;
|
||||
resolution_plan_date_time?: Date | null;
|
||||
resolved_date_time?: Date | null;
|
||||
first_response_assigned_resource_id?: number | null;
|
||||
first_response_initiating_resource_id?: number | null;
|
||||
project_id?: number | null;
|
||||
opportunity_id?: number | null;
|
||||
change_approval_board?: number | null;
|
||||
change_approval_status?: number | null;
|
||||
change_approval_type?: number | null;
|
||||
change_info_field1?: string | null;
|
||||
change_info_field2?: string | null;
|
||||
change_info_field3?: string | null;
|
||||
change_info_field4?: string | null;
|
||||
change_info_field5?: string | null;
|
||||
contract_id?: number | null;
|
||||
monitor_id?: number | null;
|
||||
monitor_type_id?: number | null;
|
||||
ticket_type?: number | null;
|
||||
ticket_category?: number | null;
|
||||
service_level_agreement_id?: number | null;
|
||||
resolution?: string | null;
|
||||
purchase_order_number?: string | null;
|
||||
ticket_completion_date?: Date | null;
|
||||
last_activity_person_type?: number | null;
|
||||
last_activity_resource_id?: number | null;
|
||||
current_service_thermometer_rating?: number | null;
|
||||
previous_service_thermometer_rating?: number | null;
|
||||
service_thermometer_temperature?: number | null;
|
||||
api_vendor_id?: number | null;
|
||||
}
|
||||
|
||||
// Task entity
|
||||
export interface Task extends AuditFields {
|
||||
id: number;
|
||||
title?: string | null;
|
||||
description?: string | null;
|
||||
status?: number | null;
|
||||
priority?: number | null;
|
||||
assigned_resource_id?: number | null;
|
||||
assigned_resource_role_id?: number | null;
|
||||
department_id?: number | null;
|
||||
estimated_hours?: number | null;
|
||||
remaining_hours?: number | null;
|
||||
hours_to_be_scheduled?: number | null;
|
||||
start_date_time?: Date | null;
|
||||
end_date_time?: Date | null;
|
||||
completed_date_time?: Date | null;
|
||||
create_date_time?: Date | null;
|
||||
creator_resource_id?: number | null;
|
||||
completed_by_resource_id?: number | null;
|
||||
last_activity_date_time?: Date | null;
|
||||
project_id?: number | null;
|
||||
ticket_id?: number | null;
|
||||
phase_id?: number | null;
|
||||
allocation_code_id?: number | null;
|
||||
task_type?: number | null;
|
||||
task_is_billable?: boolean;
|
||||
task_number?: string | null;
|
||||
purchase_order_number?: string | null;
|
||||
can_client_portal_user_complete_task?: boolean;
|
||||
creator_type?: number | null;
|
||||
task_category_id?: number | null;
|
||||
}
|
||||
|
||||
// Configuration Item entity
|
||||
export interface ConfigurationItem extends AuditFields {
|
||||
id: number;
|
||||
company_id: number;
|
||||
product_id?: number | null;
|
||||
reference_title?: string | null;
|
||||
reference_number?: string | null;
|
||||
serial_number?: string | null;
|
||||
install_date?: Date | null;
|
||||
warranty_expiration_date?: Date | null;
|
||||
is_active?: boolean;
|
||||
daily_cost?: number | null;
|
||||
hourly_cost?: number | null;
|
||||
monthly_cost?: number | null;
|
||||
per_use_cost?: number | null;
|
||||
setup_fee?: number | null;
|
||||
contact_id?: number | null;
|
||||
location_id?: number | null;
|
||||
vendor_id?: number | null;
|
||||
installed_by_id?: number | null;
|
||||
installed_by_contact_id?: number | null;
|
||||
parent_configuration_item_id?: number | null;
|
||||
notes?: string | null;
|
||||
create_date?: Date | null;
|
||||
created_by_person_id?: number | null;
|
||||
last_modified_time?: Date | null;
|
||||
last_activity_person_type?: number | null;
|
||||
impersonator_creator_resource_id?: number | null;
|
||||
configuration_item_category_id?: number | null;
|
||||
configuration_item_type?: number | null;
|
||||
datto_availability?: number | null;
|
||||
datto_device_memory_megabytes?: number | null;
|
||||
datto_drives_errors?: boolean | null;
|
||||
datto_hostname?: string | null;
|
||||
datto_internal_ip?: string | null;
|
||||
datto_kernel_version_id?: number | null;
|
||||
datto_last_check_in_date_time?: Date | null;
|
||||
datto_nic_speed_kilobits_per_second?: number | null;
|
||||
datto_number_of_agents?: number | null;
|
||||
datto_number_of_drives?: number | null;
|
||||
datto_number_of_logical_volumes?: number | null;
|
||||
datto_number_of_volumes?: number | null;
|
||||
datto_off_site_storage_used_bytes?: number | null;
|
||||
datto_os_version_id?: number | null;
|
||||
datto_percentage_used?: number | null;
|
||||
datto_protected_kilobytes?: number | null;
|
||||
datto_remote_ip?: string | null;
|
||||
datto_serial_number?: string | null;
|
||||
datto_uptime_seconds?: number | null;
|
||||
datto_used_kilobytes?: number | null;
|
||||
datto_z_pool_percentage?: number | null;
|
||||
device_networking_id?: number | null;
|
||||
last_backup_date?: Date | null;
|
||||
last_backup_status?: number | null;
|
||||
os_version_id?: number | null;
|
||||
service_id?: number | null;
|
||||
service_bundle_id?: number | null;
|
||||
snmp_location?: string | null;
|
||||
snmp_name?: string | null;
|
||||
snmp_contact?: string | null;
|
||||
api_vendor_id?: number | null;
|
||||
device_type?: string | null;
|
||||
rmm_device_uid?: string | null;
|
||||
rmm_device_audit_architecture_id?: number | null;
|
||||
rmm_device_audit_display_adaptor_id?: number | null;
|
||||
rmm_device_audit_domain_id?: number | null;
|
||||
rmm_device_audit_external_ip_address?: string | null;
|
||||
rmm_device_audit_hostname?: string | null;
|
||||
rmm_device_audit_ip_address?: string | null;
|
||||
rmm_device_audit_mac_address?: string | null;
|
||||
rmm_device_audit_manufacturer_id?: number | null;
|
||||
rmm_device_audit_missing_patch_count?: number | null;
|
||||
rmm_device_audit_mobile_network_operator_id?: number | null;
|
||||
rmm_device_audit_mobile_number?: string | null;
|
||||
rmm_device_audit_model_id?: number | null;
|
||||
rmm_device_audit_motherboard_id?: number | null;
|
||||
rmm_device_audit_operating_system_id?: number | null;
|
||||
rmm_device_audit_processor_id?: number | null;
|
||||
rmm_device_audit_service_pack_id?: number | null;
|
||||
rmm_device_audit_snmp_contact?: string | null;
|
||||
rmm_device_audit_snmp_location?: string | null;
|
||||
rmm_device_audit_snmp_name?: string | null;
|
||||
rmm_device_audit_software_status_id?: number | null;
|
||||
rmm_device_audit_storage_bytes?: number | null;
|
||||
rmm_open_alert_count?: number | null;
|
||||
rmm_device_audit_description?: string | null;
|
||||
rmm_device_audit_device_type_id?: number | null;
|
||||
rmm_device_audit_last_user?: string | null;
|
||||
rmm_device_audit_memory_bytes?: number | null;
|
||||
source_cost_id?: number | null;
|
||||
source_cost_type?: number | null;
|
||||
}
|
||||
|
||||
// Contract entity
|
||||
export interface Contract extends AuditFields {
|
||||
id: number;
|
||||
company_id: number;
|
||||
contract_name?: string | null;
|
||||
contract_number?: string | null;
|
||||
description?: string | null;
|
||||
start_date?: Date | null;
|
||||
end_date?: Date | null;
|
||||
time_reporting_requires_start_and_stop_times?: number | null;
|
||||
service_level_agreement_id?: number | null;
|
||||
contract_type?: number | null;
|
||||
contract_category?: number | null;
|
||||
status?: number | null;
|
||||
business_division_subdivision_id?: number | null;
|
||||
contact_id?: number | null;
|
||||
contact_name?: string | null;
|
||||
billing_preference?: number | null;
|
||||
purchase_order_number?: string | null;
|
||||
setup_fee?: number | null;
|
||||
setup_fee_allocation_code_id?: number | null;
|
||||
estimated_cost?: number | null;
|
||||
estimated_hours?: number | null;
|
||||
estimated_revenue?: number | null;
|
||||
over_budget_dollar_amount?: number | null;
|
||||
over_budget_hours?: number | null;
|
||||
contract_period_type?: string | null;
|
||||
opportunity_id?: number | null;
|
||||
renewed_contract_id?: number | null;
|
||||
is_default_contract?: boolean;
|
||||
internal_currency_setup_fee?: number | null;
|
||||
internal_currency_over_budget_dollar_amount?: number | null;
|
||||
internal_currency_estimated_cost?: number | null;
|
||||
internal_currency_estimated_revenue?: number | null;
|
||||
exclusion_contract_id?: number | null;
|
||||
internal_currency_monthly_revenue?: number | null;
|
||||
internal_currency_quarterly_revenue?: number | null;
|
||||
internal_currency_semi_annual_revenue?: number | null;
|
||||
internal_currency_yearly_revenue?: number | null;
|
||||
internal_currency_one_time_revenue?: number | null;
|
||||
compliance?: boolean | null;
|
||||
}
|
||||
|
||||
// Billing Item entity
|
||||
export interface BillingItem extends AuditFields {
|
||||
id: number;
|
||||
company_id?: number | null;
|
||||
product_id?: number | null;
|
||||
description?: string | null;
|
||||
quantity?: number | null;
|
||||
rate?: number | null;
|
||||
total_amount?: number | null;
|
||||
line_discount_dollars?: number | null;
|
||||
line_discount_percent?: number | null;
|
||||
tax_category_id?: number | null;
|
||||
internal_currency_line_discount_dollars?: number | null;
|
||||
allocation_code_id?: number | null;
|
||||
invoice_id?: number | null;
|
||||
vendor_id?: number | null;
|
||||
expense_item?: boolean;
|
||||
task_id?: number | null;
|
||||
ticket_id?: number | null;
|
||||
project_id?: number | null;
|
||||
our_cost?: number | null;
|
||||
list_price?: number | null;
|
||||
unit_cost?: number | null;
|
||||
unit_price?: number | null;
|
||||
extended_price?: number | null;
|
||||
tax_dollars?: number | null;
|
||||
internal_currency_unit_price?: number | null;
|
||||
internal_currency_total_amount?: number | null;
|
||||
}
|
||||
|
||||
// Picklist base interface
|
||||
export interface PicklistValue extends AuditFields {
|
||||
value: number;
|
||||
label: string;
|
||||
is_active?: boolean;
|
||||
is_system?: boolean;
|
||||
sort_order?: number | null;
|
||||
parent_value?: number | null;
|
||||
}
|
||||
|
||||
// Status picklist
|
||||
export interface Status extends PicklistValue {}
|
||||
|
||||
// Issue Type picklist
|
||||
export interface IssueType extends PicklistValue {}
|
||||
|
||||
// Sub-Issue Type picklist
|
||||
export interface SubIssueType extends PicklistValue {}
|
||||
|
||||
// Work Type picklist
|
||||
export interface WorkType extends PicklistValue {}
|
||||
|
||||
// Sync History (matches sync_history table)
|
||||
export interface SyncHistoryRecord {
|
||||
id: number;
|
||||
entity_type: string;
|
||||
sync_type: 'full' | 'incremental' | 'entity-specific';
|
||||
status: 'started' | 'in_progress' | 'completed' | 'failed';
|
||||
started_at: Date;
|
||||
completed_at?: Date | null;
|
||||
records_added: number;
|
||||
records_updated: number;
|
||||
records_deleted: number;
|
||||
error_message?: string | null;
|
||||
triggered_by?: string | null;
|
||||
}
|
||||
|
||||
// Time Entry entity
|
||||
export interface TimeEntry extends AuditFields {
|
||||
id: number;
|
||||
resource_id: number;
|
||||
ticket_id?: number | null;
|
||||
task_id?: number | null;
|
||||
project_id?: number | null;
|
||||
company_id?: number | null;
|
||||
entry_date: Date;
|
||||
hours_worked: number;
|
||||
notes?: string | null;
|
||||
internal_notes?: string | null;
|
||||
title?: string | null;
|
||||
type?: number | null;
|
||||
start_date_time?: Date | null;
|
||||
end_date_time?: Date | null;
|
||||
billable?: boolean;
|
||||
billing_rate?: number | null;
|
||||
billing_rate_currency_id?: number | null;
|
||||
cost_rate?: number | null;
|
||||
cost_rate_currency_id?: number | null;
|
||||
cost?: number | null;
|
||||
cost_currency_id?: number | null;
|
||||
revenue?: number | null;
|
||||
revenue_currency_id?: number | null;
|
||||
margin?: number | null;
|
||||
margin_currency_id?: number | null;
|
||||
approved?: boolean;
|
||||
approved_by_resource_id?: number | null;
|
||||
approved_date_time?: Date | null;
|
||||
non_billable?: boolean;
|
||||
contract_service_id?: number | null;
|
||||
contract_service_bundle_id?: number | null;
|
||||
role_id?: number | null;
|
||||
department_id?: number | null;
|
||||
location_id?: number | null;
|
||||
allocation_code_id?: number | null;
|
||||
imp_project_schedule_id?: number | null;
|
||||
imp_project_schedule_task_id?: number | null;
|
||||
api_vendor_id?: number | null;
|
||||
}
|
||||
|
||||
// Union type for all entities
|
||||
export type Entity =
|
||||
| Company
|
||||
| Resource
|
||||
| Contact
|
||||
| Project
|
||||
| Ticket
|
||||
| Task
|
||||
| ConfigurationItem
|
||||
| Contract
|
||||
| BillingItem
|
||||
| Status
|
||||
| IssueType
|
||||
| SubIssueType
|
||||
| WorkType
|
||||
| TimeEntry;
|
||||
|
||||
// Table name type
|
||||
export type TableName =
|
||||
| 'companies'
|
||||
| 'resources'
|
||||
| 'contacts'
|
||||
| 'projects'
|
||||
| 'tickets'
|
||||
| 'tasks'
|
||||
| 'configuration_items'
|
||||
| 'contracts'
|
||||
| 'billing_items'
|
||||
| 'statuses'
|
||||
| 'issue_types'
|
||||
| 'sub_issue_types'
|
||||
| 'work_types'
|
||||
| 'time_entries'
|
||||
| 'sync_history';
|
||||
266
lib/types/errors.ts
Normal file
266
lib/types/errors.ts
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
/**
|
||||
* Custom Error Types for Sync Operations
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base sync error class
|
||||
*/
|
||||
export class SyncError extends Error {
|
||||
public readonly code: string;
|
||||
public readonly context?: Record<string, any>;
|
||||
public readonly isRetryable: boolean;
|
||||
|
||||
constructor(
|
||||
message: string,
|
||||
code: string,
|
||||
context?: Record<string, any>,
|
||||
isRetryable: boolean = false
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'SyncError';
|
||||
this.code = code;
|
||||
this.context = context;
|
||||
this.isRetryable = isRetryable;
|
||||
|
||||
// Maintains proper stack trace for where our error was thrown
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Network-related errors (connection failures, timeouts)
|
||||
*/
|
||||
export class NetworkError extends SyncError {
|
||||
constructor(message: string, context?: Record<string, any>) {
|
||||
super(message, 'NETWORK_ERROR', context, true);
|
||||
this.name = 'NetworkError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication/Authorization errors
|
||||
*/
|
||||
export class AuthError extends SyncError {
|
||||
constructor(message: string, context?: Record<string, any>) {
|
||||
super(message, 'AUTH_ERROR', context, false);
|
||||
this.name = 'AuthError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rate limit errors
|
||||
*/
|
||||
export class RateLimitError extends SyncError {
|
||||
public readonly retryAfter?: number;
|
||||
|
||||
constructor(message: string, retryAfter?: number, context?: Record<string, any>) {
|
||||
super(message, 'RATE_LIMIT_ERROR', context, true);
|
||||
this.name = 'RateLimitError';
|
||||
this.retryAfter = retryAfter;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* API-related errors
|
||||
*/
|
||||
export class ApiError extends SyncError {
|
||||
public readonly statusCode?: number;
|
||||
|
||||
constructor(message: string, statusCode?: number, context?: Record<string, any>) {
|
||||
super(message, 'API_ERROR', context, statusCode ? statusCode >= 500 : false);
|
||||
this.name = 'ApiError';
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Database-related errors
|
||||
*/
|
||||
export class DatabaseError extends SyncError {
|
||||
constructor(message: string, context?: Record<string, any>, isRetryable: boolean = false) {
|
||||
super(message, 'DATABASE_ERROR', context, isRetryable);
|
||||
this.name = 'DatabaseError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Database constraint violation errors
|
||||
*/
|
||||
export class ConstraintError extends SyncError {
|
||||
constructor(message: string, context?: Record<string, any>) {
|
||||
super(message, 'DATABASE_CONSTRAINT_ERROR', context, false);
|
||||
this.name = 'ConstraintError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data validation errors
|
||||
*/
|
||||
export class ValidationError extends SyncError {
|
||||
public readonly validationErrors: Array<{ field: string; message: string }>;
|
||||
|
||||
constructor(
|
||||
message: string,
|
||||
validationErrors: Array<{ field: string; message: string }>,
|
||||
context?: Record<string, any>
|
||||
) {
|
||||
super(message, 'VALIDATION_ERROR', context, false);
|
||||
this.name = 'ValidationError';
|
||||
this.validationErrors = validationErrors;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data mapping errors
|
||||
*/
|
||||
export class MappingError extends SyncError {
|
||||
constructor(message: string, context?: Record<string, any>) {
|
||||
super(message, 'MAPPING_ERROR', context, false);
|
||||
this.name = 'MappingError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration errors
|
||||
*/
|
||||
export class ConfigError extends SyncError {
|
||||
constructor(message: string, context?: Record<string, any>) {
|
||||
super(message, 'CONFIG_ERROR', context, false);
|
||||
this.name = 'ConfigError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Timeout errors
|
||||
*/
|
||||
export class TimeoutError extends SyncError {
|
||||
constructor(message: string, context?: Record<string, any>) {
|
||||
super(message, 'TIMEOUT_ERROR', context, true);
|
||||
this.name = 'TimeoutError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to categorize generic errors
|
||||
*/
|
||||
export function categorizeError(error: any): SyncError {
|
||||
if (error instanceof SyncError) {
|
||||
return error;
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
const errorString = errorMessage.toLowerCase();
|
||||
|
||||
// Network errors
|
||||
if (
|
||||
errorString.includes('econnrefused') ||
|
||||
errorString.includes('etimedout') ||
|
||||
errorString.includes('enotfound') ||
|
||||
errorString.includes('network')
|
||||
) {
|
||||
return new NetworkError(errorMessage, { originalError: error });
|
||||
}
|
||||
|
||||
// Auth errors
|
||||
if (
|
||||
errorString.includes('401') ||
|
||||
errorString.includes('403') ||
|
||||
errorString.includes('unauthorized') ||
|
||||
errorString.includes('forbidden')
|
||||
) {
|
||||
return new AuthError(errorMessage, { originalError: error });
|
||||
}
|
||||
|
||||
// Rate limit errors
|
||||
if (errorString.includes('429') || errorString.includes('rate limit')) {
|
||||
return new RateLimitError(errorMessage, undefined, { originalError: error });
|
||||
}
|
||||
|
||||
// Database constraint errors
|
||||
if (
|
||||
errorString.includes('constraint') ||
|
||||
errorString.includes('duplicate') ||
|
||||
errorString.includes('unique violation')
|
||||
) {
|
||||
return new ConstraintError(errorMessage, { originalError: error });
|
||||
}
|
||||
|
||||
// Database errors
|
||||
if (
|
||||
errorString.includes('query') ||
|
||||
errorString.includes('sql') ||
|
||||
errorString.includes('database') ||
|
||||
errorString.includes('postgres')
|
||||
) {
|
||||
return new DatabaseError(errorMessage, { originalError: error });
|
||||
}
|
||||
|
||||
// Validation errors
|
||||
if (errorString.includes('validation') || errorString.includes('invalid')) {
|
||||
return new ValidationError(errorMessage, [], { originalError: error });
|
||||
}
|
||||
|
||||
// Mapping errors
|
||||
if (errorString.includes('mapping') || errorString.includes('transform')) {
|
||||
return new MappingError(errorMessage, { originalError: error });
|
||||
}
|
||||
|
||||
// Timeout errors
|
||||
if (errorString.includes('timeout') || errorString.includes('timed out')) {
|
||||
return new TimeoutError(errorMessage, { originalError: error });
|
||||
}
|
||||
|
||||
// API errors (check for HTTP status codes)
|
||||
const statusMatch = errorString.match(/\b([45]\d{2})\b/);
|
||||
if (statusMatch) {
|
||||
const statusCode = parseInt(statusMatch[1]);
|
||||
return new ApiError(errorMessage, statusCode, { originalError: error });
|
||||
}
|
||||
|
||||
// Default to generic sync error
|
||||
return new SyncError(errorMessage, 'UNKNOWN_ERROR', { originalError: error });
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if error is retryable
|
||||
*/
|
||||
export function isRetryableError(error: any): boolean {
|
||||
if (error instanceof SyncError) {
|
||||
return error.isRetryable;
|
||||
}
|
||||
|
||||
const categorized = categorizeError(error);
|
||||
return categorized.isRetryable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format error for logging
|
||||
*/
|
||||
export function formatErrorForLog(error: any): {
|
||||
message: string;
|
||||
code: string;
|
||||
stack?: string;
|
||||
context?: Record<string, any>;
|
||||
isRetryable: boolean;
|
||||
} {
|
||||
if (error instanceof SyncError) {
|
||||
return {
|
||||
message: error.message,
|
||||
code: error.code,
|
||||
stack: error.stack,
|
||||
context: error.context,
|
||||
isRetryable: error.isRetryable,
|
||||
};
|
||||
}
|
||||
|
||||
const categorized = categorizeError(error);
|
||||
return {
|
||||
message: categorized.message,
|
||||
code: categorized.code,
|
||||
stack: categorized.stack,
|
||||
context: categorized.context,
|
||||
isRetryable: categorized.isRetryable,
|
||||
};
|
||||
}
|
||||
194
lib/types/sync.ts
Normal file
194
lib/types/sync.ts
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
/**
|
||||
* Sync Types and Interfaces
|
||||
* TypeScript definitions for sync operations
|
||||
*/
|
||||
|
||||
// Entity types that can be synced from Autotask
|
||||
export enum EntityType {
|
||||
COMPANIES = 'companies',
|
||||
TICKETS = 'tickets',
|
||||
TASKS = 'tasks',
|
||||
PROJECTS = 'projects',
|
||||
RESOURCES = 'resources',
|
||||
STATUSES = 'statuses',
|
||||
ISSUE_TYPES = 'issue_types',
|
||||
SUB_ISSUE_TYPES = 'sub_issue_types',
|
||||
WORK_TYPES = 'work_types',
|
||||
BILLING_ITEMS = 'billing_items',
|
||||
CONFIGURATION_ITEMS = 'configuration_items',
|
||||
CONTACTS = 'contacts',
|
||||
CONTRACTS = 'contracts',
|
||||
TIME_ENTRIES = 'time_entries',
|
||||
}
|
||||
|
||||
// Sync operation types
|
||||
export enum SyncType {
|
||||
FULL = 'full',
|
||||
INCREMENTAL = 'incremental',
|
||||
ENTITY_SPECIFIC = 'entity-specific',
|
||||
}
|
||||
|
||||
// Sync status
|
||||
export enum SyncStatus {
|
||||
STARTED = 'started',
|
||||
IN_PROGRESS = 'in_progress',
|
||||
COMPLETED = 'completed',
|
||||
FAILED = 'failed',
|
||||
}
|
||||
|
||||
// Sync configuration
|
||||
export interface SyncConfig {
|
||||
entities: EntityType[];
|
||||
syncType: SyncType;
|
||||
triggeredBy?: string;
|
||||
batchSize?: number;
|
||||
rateLimit?: number; // requests per second
|
||||
yearsBack?: number; // Number of years to look back for time-based entities (default: 2)
|
||||
}
|
||||
|
||||
// Sync progress information
|
||||
export interface SyncProgress {
|
||||
syncId: string;
|
||||
entityType: EntityType;
|
||||
status: SyncStatus;
|
||||
currentPage?: number;
|
||||
totalPages?: number;
|
||||
recordsProcessed: number;
|
||||
recordsAdded: number;
|
||||
recordsUpdated: number;
|
||||
recordsDeleted: number;
|
||||
startedAt: Date;
|
||||
estimatedCompletion?: Date;
|
||||
error?: string;
|
||||
// Chunking support
|
||||
currentChunk?: number;
|
||||
totalChunks?: number;
|
||||
chunkDescription?: string; // e.g., "Jan 2024"
|
||||
}
|
||||
|
||||
// Chunk progress for detailed tracking
|
||||
export interface ChunkProgress {
|
||||
chunkIndex: number;
|
||||
totalChunks: number;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
description: string; // e.g., "January 2024"
|
||||
status: 'pending' | 'in_progress' | 'completed' | 'failed';
|
||||
recordsProcessed: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// Sync history record (matches database table)
|
||||
export interface SyncHistory {
|
||||
id: number;
|
||||
entity_type: string;
|
||||
sync_type: SyncType;
|
||||
status: SyncStatus;
|
||||
started_at: Date;
|
||||
completed_at?: Date;
|
||||
records_added: number;
|
||||
records_updated: number;
|
||||
records_deleted: number;
|
||||
error_message?: string;
|
||||
triggered_by?: string;
|
||||
}
|
||||
|
||||
// Alias for database compatibility
|
||||
export interface SyncHistoryRecord {
|
||||
id: number;
|
||||
entity_type: string;
|
||||
sync_type: SyncType;
|
||||
status: SyncStatus;
|
||||
started_at: Date;
|
||||
completed_at?: Date;
|
||||
records_added: number;
|
||||
records_updated: number;
|
||||
records_deleted: number;
|
||||
error_message?: string;
|
||||
triggered_by?: string;
|
||||
}
|
||||
|
||||
// Sync result for a single entity
|
||||
export interface EntitySyncResult {
|
||||
entityType: EntityType;
|
||||
success: boolean;
|
||||
recordsAdded: number;
|
||||
recordsUpdated: number;
|
||||
recordsDeleted: number;
|
||||
duration: number; // milliseconds
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// Overall sync result
|
||||
export interface SyncResult {
|
||||
syncId: string;
|
||||
syncType: SyncType;
|
||||
status: SyncStatus;
|
||||
entities: EntitySyncResult[];
|
||||
totalRecordsAdded: number;
|
||||
totalRecordsUpdated: number;
|
||||
totalRecordsDeleted: number;
|
||||
startedAt: Date;
|
||||
completedAt?: Date;
|
||||
duration?: number; // milliseconds
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
// Sync state (stored in Redis for real-time updates)
|
||||
export interface SyncState {
|
||||
syncId: string;
|
||||
status: SyncStatus;
|
||||
progress: SyncProgress[];
|
||||
startedAt: Date;
|
||||
lastUpdated: Date;
|
||||
}
|
||||
|
||||
// Entity dependency map (for determining sync order)
|
||||
export const ENTITY_DEPENDENCIES: Record<EntityType, EntityType[]> = {
|
||||
[EntityType.COMPANIES]: [], // No dependencies
|
||||
[EntityType.RESOURCES]: [], // No dependencies
|
||||
[EntityType.STATUSES]: [], // No dependencies
|
||||
[EntityType.ISSUE_TYPES]: [], // No dependencies
|
||||
[EntityType.SUB_ISSUE_TYPES]: [], // No dependencies
|
||||
[EntityType.WORK_TYPES]: [], // No dependencies
|
||||
[EntityType.CONTACTS]: [EntityType.COMPANIES], // Depends on companies
|
||||
[EntityType.PROJECTS]: [EntityType.COMPANIES, EntityType.RESOURCES], // Depends on companies and resources
|
||||
[EntityType.TICKETS]: [EntityType.COMPANIES, EntityType.RESOURCES, EntityType.CONTACTS], // Depends on companies, resources, contacts
|
||||
[EntityType.TASKS]: [EntityType.RESOURCES, EntityType.PROJECTS, EntityType.TICKETS], // Depends on resources, projects, tickets
|
||||
[EntityType.CONFIGURATION_ITEMS]: [EntityType.COMPANIES, EntityType.CONTACTS], // Depends on companies and contacts
|
||||
[EntityType.CONTRACTS]: [EntityType.COMPANIES, EntityType.CONTACTS], // Depends on companies and contacts
|
||||
[EntityType.BILLING_ITEMS]: [EntityType.COMPANIES, EntityType.TASKS, EntityType.TICKETS, EntityType.PROJECTS], // Depends on multiple entities
|
||||
[EntityType.TIME_ENTRIES]: [EntityType.COMPANIES, EntityType.RESOURCES, EntityType.CONTACTS, EntityType.PROJECTS, EntityType.TASKS, EntityType.TICKETS], // Depends on many entities
|
||||
};
|
||||
|
||||
// Autotask API field names (for incremental sync)
|
||||
export interface AutotaskQueryFilter {
|
||||
field: string;
|
||||
op: 'eq' | 'noteq' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'beginsWith' | 'endsWith';
|
||||
value: any;
|
||||
}
|
||||
|
||||
// Autotask query options
|
||||
export interface AutotaskQueryOptions {
|
||||
filters?: AutotaskQueryFilter[];
|
||||
pageSize?: number;
|
||||
includeFields?: string[];
|
||||
maxRecords?: number;
|
||||
}
|
||||
|
||||
// Last sync information per entity
|
||||
export interface LastSyncInfo {
|
||||
entityType: EntityType;
|
||||
lastSyncTime?: Date;
|
||||
lastSyncStatus: SyncStatus;
|
||||
recordCount: number;
|
||||
}
|
||||
|
||||
// Sync notification
|
||||
export interface SyncNotification {
|
||||
syncId: string;
|
||||
type: 'success' | 'failure' | 'warning';
|
||||
message: string;
|
||||
entityType?: EntityType;
|
||||
timestamp: Date;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue