feat: Autotask webhook integration, TicketNotes, Datto RMM, workflow engine, Veeam agents/alarms, AI triage, misc improvements

This commit is contained in:
lorentz 2026-02-20 10:28:15 -05:00
parent 347cf4e298
commit d7c3dc7168
74 changed files with 37844 additions and 322 deletions

View file

@ -140,6 +140,19 @@ export interface Company {
competitorID?: number;
}
export interface TicketNote {
id: number;
ticketID: number;
title?: string;
description?: string;
noteType?: number;
publish?: number;
creatorResourceID?: number;
creatorType?: number;
lastActivityDate?: string;
createDateTime?: string;
}
export interface ConfigurationItem {
id: number;
companyID: number;

View file

@ -435,6 +435,13 @@ export interface SyncHistoryRecord {
triggered_by?: string | null;
}
// Device Lifecycle Policy entity
export interface DeviceLifecyclePolicy extends AuditFields {
id: number;
device_type: string;
expected_months: number;
}
// Time Entry entity
export interface TimeEntry extends AuditFields {
id: number;
@ -492,9 +499,9 @@ export type Entity =
| IssueType
| SubIssueType
| WorkType
| TimeEntry;
| TimeEntry
| DeviceLifecyclePolicy;
// Table name type
export type TableName =
| 'companies'
| 'resources'
@ -510,4 +517,5 @@ export type TableName =
| 'sub_issue_types'
| 'work_types'
| 'time_entries'
| 'sync_history';
| 'sync_history'
| 'device_lifecycle_policies';

View file

@ -62,28 +62,72 @@ export interface DattoRMMDevice {
}
export interface DattoRMMSite {
id: string;
id: number;
uid: string;
accountUid?: string;
name: string;
description: string;
notes: string;
notes: string | null;
onDemand: boolean;
splashtopAutoInstall?: boolean;
proxySettings?: {
host: string;
port: number;
username?: string;
} | null;
devicesStatus?: {
numberOfDevices: number;
numberOfOnlineDevices: number;
numberOfOfflineDevices: number;
};
autotaskCompanyName?: string;
autotaskCompanyId?: string;
portalUrl?: string;
devices?: DattoRMMDevice[];
}
export interface DattoRMMAlert {
alertUid: string;
priority: string;
diagnostics: string | null;
resolved: boolean;
resolvedBy: string | null;
resolvedOn: number | null;
muted: boolean;
ticketNumber: string | null;
timestamp: number;
alertMonitorInfo?: {
sendsEmails: boolean;
createsTicket: boolean;
};
alertContext?: {
'@class': string;
[key: string]: any;
};
alertSourceInfo?: {
deviceUid: string;
deviceName: string;
siteUid: string;
siteName: string;
};
responseActions?: Array<{
actionTime: number;
actionType: string;
description: string;
actionReference: string | null;
actionReferenceInt: string | null;
}> | null;
autoresolveMins?: number;
}
export interface DattoRMMApiResponse<T> {
items?: T[];
item?: T;
pageDetails?: {
page: number;
perPage: number;
totalPages: number;
totalItems: number;
count: number;
totalCount?: number;
prevPageUrl: string | null;
nextPageUrl: string | null;
};
}

View file

@ -14,11 +14,15 @@ export enum EntityType {
ISSUE_TYPES = 'issue_types',
SUB_ISSUE_TYPES = 'sub_issue_types',
WORK_TYPES = 'work_types',
QUEUES = 'queues',
PRIORITIES = 'priorities',
TICKET_CATEGORIES = 'ticket_categories',
BILLING_ITEMS = 'billing_items',
CONFIGURATION_ITEMS = 'configuration_items',
CONTACTS = 'contacts',
CONTRACTS = 'contracts',
TIME_ENTRIES = 'time_entries',
TICKET_NOTES = 'ticket_notes',
}
// Sync operation types
@ -151,6 +155,9 @@ export const ENTITY_DEPENDENCIES: Record<EntityType, EntityType[]> = {
[EntityType.ISSUE_TYPES]: [], // No dependencies
[EntityType.SUB_ISSUE_TYPES]: [], // No dependencies
[EntityType.WORK_TYPES]: [], // No dependencies
[EntityType.QUEUES]: [], // No dependencies
[EntityType.PRIORITIES]: [], // No dependencies
[EntityType.TICKET_CATEGORIES]: [], // 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
@ -159,6 +166,7 @@ export const ENTITY_DEPENDENCIES: Record<EntityType, EntityType[]> = {
[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
[EntityType.TICKET_NOTES]: [EntityType.TICKETS], // Depends on tickets
};
// Autotask API field names (for incremental sync)

View file

@ -367,3 +367,57 @@ export interface VeeamComplianceSummary {
backedUpNotContracted: number;
computedAt: Date | null;
}
// ============================================================================
// New entity types: Backup Agents + Alarms
// ============================================================================
export interface VspcBackupAgent {
instanceUid: string;
organizationUid: string;
siteUid?: string;
managementAgentUid?: string;
name: string;
agentPlatform: string;
status: string;
managementAgentStatus: string;
operationMode: string;
guiMode?: string;
platform?: string;
version?: string;
versionStatus?: string;
managementMode?: string;
installationType?: string;
activationTime?: string;
totalJobsCount: number;
runningJobsCount: number;
successJobsCount: number;
}
export interface VspcAlarmActivation {
instanceUid: string;
time: string;
status: string;
message: string;
remark?: string;
}
export interface VspcAlarmObject {
instanceUid: string;
type: string;
organizationUid?: string;
locationUid?: string;
managementAgentUid?: string;
computerName?: string;
objectUid?: string;
objectName?: string;
}
export interface VspcAlarm {
instanceUid: string;
alarmTemplateUid: string;
repeatCount: number;
object: VspcAlarmObject;
lastActivation: VspcAlarmActivation;
area?: string;
}

View file

@ -18,6 +18,7 @@ export enum WebhookEventType {
export enum WebhookEntityType {
COMPANIES = 'Companies',
TICKETS = 'Tickets',
TICKET_NOTES = 'TicketNotes',
TASKS = 'Tasks',
PROJECTS = 'Projects',
TIME_ENTRIES = 'TimeEntries',
@ -27,43 +28,125 @@ export enum WebhookEntityType {
}
/**
* Autotask webhook payload structure
* Autotask entities that support webhooks via their REST API
*/
export const WEBHOOK_SUPPORTED_ENTITIES: WebhookEntityType[] = [
WebhookEntityType.COMPANIES,
WebhookEntityType.CONTACTS,
WebhookEntityType.CONFIGURATION_ITEMS,
WebhookEntityType.TICKETS,
WebhookEntityType.TICKET_NOTES,
];
/**
* Autotask webhook registration request
*/
export interface AutotaskWebhookRegistration {
IsActive: boolean;
DeactivationUrl: string;
IsSubscribedToCreateEvents?: boolean;
IsSubscribedToUpdateEvents?: boolean;
IsSubscribedToDeleteEvents?: boolean;
Name: string;
SecretKey: string;
SendThresholdExceededNotification: boolean;
WebhookUrl: string;
NotificationEmailAddress: string;
}
/**
* Autotask webhook field trigger
*/
export interface AutotaskWebhookFieldTrigger {
FieldID: number;
IsDisplayAlwaysField: boolean;
IsSubscribedField: boolean;
WebhookID: number;
}
/**
* Autotask webhook registration result
*/
export interface AutotaskWebhookRegistrationResult {
entityType: WebhookEntityType;
webhookId: number;
fieldsRegistered: number;
excludedResources: number;
success: boolean;
error?: string;
}
/**
* Raw Autotask webhook payload as actually sent by Autotask
*/
export interface AutotaskRawWebhookPayload {
Action: string; // "Create", "Update", "Delete"
Guid: string; // Unique event GUID
EntityType: string; // Singular: "Ticket", "Company", "Contact", "ConfigurationItem", "TicketNote"
Id: number; // Entity ID
Fields?: Record<string, string>; // Changed/created fields
EventTime: string; // ISO timestamp
SequenceNumber?: number;
PersonId?: number; // Resource who triggered the event
}
/**
* Normalized webhook payload used internally
*/
export interface AutotaskWebhookPayload {
/**
* Unique identifier for the webhook event
*/
eventId: string;
/**
* Type of event (create, update, delete)
*/
eventType: WebhookEventType;
/**
* Entity type that triggered the webhook
*/
entityType: WebhookEntityType;
/**
* ID of the entity that changed
*/
entityId: number;
/**
* Timestamp when the event occurred
*/
eventTimestamp: string;
/**
* Optional: Full entity data (if configured in webhook)
*/
fields?: Record<string, string>;
entity?: Record<string, any>;
personId?: number;
}
/**
* Optional: Previous values for update events
*/
previousValues?: Record<string, any>;
/**
* Map Autotask singular EntityType to our WebhookEntityType enum
*/
const ENTITY_TYPE_MAP: Record<string, WebhookEntityType> = {
'Company': WebhookEntityType.COMPANIES,
'Contact': WebhookEntityType.CONTACTS,
'ConfigurationItem': WebhookEntityType.CONFIGURATION_ITEMS,
'Ticket': WebhookEntityType.TICKETS,
'TicketNote': WebhookEntityType.TICKET_NOTES,
};
/**
* Map Autotask Action string to our WebhookEventType enum
*/
const ACTION_MAP: Record<string, WebhookEventType> = {
'Create': WebhookEventType.CREATE,
'Update': WebhookEventType.UPDATE,
'Delete': WebhookEventType.DELETE,
};
/**
* Normalize a raw Autotask webhook payload into our internal format
*/
export function normalizeWebhookPayload(raw: AutotaskRawWebhookPayload): AutotaskWebhookPayload {
const entityType = ENTITY_TYPE_MAP[raw.EntityType];
if (!entityType) {
throw new Error(`Unknown Autotask EntityType: ${raw.EntityType}`);
}
const eventType = ACTION_MAP[raw.Action];
if (!eventType) {
throw new Error(`Unknown Autotask Action: ${raw.Action}`);
}
return {
eventId: raw.Guid,
eventType,
entityType,
entityId: raw.Id,
eventTimestamp: raw.EventTime,
fields: raw.Fields,
personId: raw.PersonId,
};
}
/**

427
lib/types/workflow.ts Normal file
View file

@ -0,0 +1,427 @@
/**
* Workflow Engine Types
* TypeScript definitions for the ticket triage workflow engine
*/
// ============================================================================
// Enums & Constants
// ============================================================================
export type RuleType =
| 'branch_routing'
| 'ticket_type'
| 'issue_classification'
| 'priority'
| 'queue_routing';
export type MatchField =
| 'title'
| 'description'
| 'title_or_description'
| 'ticket_category'
| 'policy_name'
| 'device_name'
| 'creator_resource_id'
| 'priority'
| 'ticket_type'
| 'person_id'
| 'company_id';
export type MatchOperator =
| 'contains'
| 'starts_with'
| 'regex'
| 'equals'
| 'in'
| 'not_in';
export type ConditionOperator =
| 'equals'
| 'not_equals'
| 'in'
| 'not_in'
| 'contains'
| 'not_contains'
| 'regex'
| 'gt'
| 'lt'
| 'is_null'
| 'is_not_null';
export type ConfidenceLevel = 'high' | 'medium' | 'low';
export type Branch = 'service_desk' | 'noc' | 'soc';
export type ClassificationMethod = 'robotic' | 'ai' | 'hybrid';
export type ExecutionStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
export type StepMethod = 'robotic' | 'ai' | 'skipped';
export type ActionType =
| 'set_field'
| 'classify'
| 'ai_enhance'
| 'create_note'
| 'update_autotask'
| 'delay'
| 'skip';
export type PromptPurpose =
| 'title_cleanup'
| 'description_rewrite'
| 'ambiguous_classification'
| 'troubleshooting_steps'
| 'noc_format'
| 'soc_analysis';
export type TriggerEvent = 'ticket.created' | 'ticket.updated';
export type StepName =
| 'filter'
| 'branch_routing'
| 'ticket_type'
| 'issue_classification'
| 'priority'
| 'queue_routing'
| 'validation'
| 'ai_title'
| 'ai_description'
| 'ai_classification'
| 'ai_troubleshooting'
| 'autotask_update'
| 'create_note';
// ============================================================================
// Database Row Types
// ============================================================================
export interface ClassificationRule {
id: number;
name: string;
description: string | null;
rule_type: RuleType;
sort_order: number;
is_active: boolean;
match_field: MatchField;
match_operator: MatchOperator;
match_value: any; // JSONB: string | string[] | regex pattern
match_case_sensitive: boolean;
result_field: string;
result_value: any; // JSONB
result_field_2: string | null;
result_value_2: any | null;
confidence: ConfidenceLevel;
stop_on_match: boolean;
created_at: Date;
updated_at: Date;
}
export interface WorkflowRule {
id: number;
name: string;
description: string | null;
is_active: boolean;
sort_order: number;
trigger_event: TriggerEvent;
trigger_entity: string;
stop_processing: boolean;
created_at: Date;
updated_at: Date;
}
export interface WorkflowCondition {
id: number;
rule_id: number;
condition_group: number;
field: string;
operator: ConditionOperator;
value: any; // JSONB
created_at: Date;
}
export interface WorkflowAction {
id: number;
rule_id: number;
sort_order: number;
action_type: ActionType;
config: Record<string, any>;
created_at: Date;
}
export interface AiPromptTemplate {
id: number;
name: string;
purpose: PromptPurpose;
system_prompt: string;
user_prompt_template: string;
provider: string;
model: string;
temperature: number;
max_tokens: number;
is_active: boolean;
version: number;
created_at: Date;
updated_at: Date;
}
export interface WorkflowExecution {
id: number;
trigger_event: string;
entity_type: string;
entity_id: number;
ticket_number: string | null;
status: ExecutionStatus;
classification_method: ClassificationMethod | null;
branch: Branch | null;
started_at: Date;
completed_at: Date | null;
duration_ms: number | null;
error_message: string | null;
created_at: Date;
}
export interface WorkflowExecutionStep {
id: number;
execution_id: number;
step_name: StepName;
step_order: number;
status: ExecutionStatus;
method: StepMethod | null;
input_data: Record<string, any> | null;
output_data: Record<string, any> | null;
field_changes: Record<string, { before: any; after: any }> | null;
classification_rule_id: number | null;
confidence: ConfidenceLevel | null;
ai_request: Record<string, any> | null;
ai_response: string | null;
attempt_number: number;
error_message: string | null;
duration_ms: number | null;
started_at: Date | null;
completed_at: Date | null;
}
export interface WorkflowSetting {
key: string;
value: any; // JSONB
description: string | null;
updated_at: Date;
}
// ============================================================================
// Runtime Types (used during processing)
// ============================================================================
/** Ticket data as consumed by the workflow engine */
export interface TicketData {
id: number;
ticket_number: string | null;
title: string;
description: string | null;
ticket_category: number | null;
ticket_type: number | null;
priority: number | null;
queue_id: number | null;
issue_type: number | null;
sub_issue_type: number | null;
company_id: number;
contact_id: number | null;
assigned_resource_id: number | null;
creator_resource_id: number | null;
person_id: number | null;
status: number | null;
source: number | null;
// Computed/extracted fields for classification
policy_name?: string | null;
device_name?: string | null;
}
/** Result from a single classification step */
export interface ClassificationStepResult {
field: string;
value: any;
field_2?: string;
value_2?: any;
confidence: ConfidenceLevel;
matched_rule_id: number | null;
matched_rule_name: string | null;
method: StepMethod;
}
/** Full classification result from robotic classifier */
export interface ClassificationResult {
branch: ClassificationStepResult | null;
ticket_type: ClassificationStepResult | null;
issue_classification: ClassificationStepResult | null;
priority: ClassificationStepResult | null;
queue: ClassificationStepResult | null;
overall_confidence: ConfidenceLevel;
needs_ai: boolean;
ai_reasons: string[];
}
/** Validation result */
export interface ValidationResult {
is_valid: boolean;
errors: ValidationError[];
}
export interface ValidationError {
field: string;
message: string;
value: any;
}
/** AI enhancement result */
export interface AiEnhancementResult {
title?: string;
description?: string;
classification?: {
issue_type?: number;
sub_issue_type?: number;
ticket_type?: number;
priority?: number;
};
troubleshooting_steps?: string;
method: 'ai';
}
/** Field changes to write back to Autotask */
export interface FieldChanges {
[field: string]: {
before: any;
after: any;
};
}
/** Full execution result */
export interface ExecutionResult {
execution_id: number;
status: ExecutionStatus;
classification_method: ClassificationMethod;
branch: Branch;
field_changes: FieldChanges;
steps: ExecutionStepSummary[];
duration_ms: number;
error?: string;
}
export interface ExecutionStepSummary {
step_name: StepName;
status: ExecutionStatus;
method: StepMethod;
confidence?: ConfidenceLevel;
matched_rule?: string;
duration_ms: number;
}
/** Workflow event that triggers processing */
export interface WorkflowEvent {
trigger_event: TriggerEvent;
entity_type: string;
entity_id: number;
ticket_number?: string;
ticket_data?: TicketData;
}
/** Workflow settings loaded into memory */
export interface WorkflowSettings {
workflow_engine_enabled: boolean;
default_ai_provider: 'openai' | 'anthropic';
openai_api_key: string;
openai_model: string;
anthropic_api_key: string;
anthropic_model: string;
ai_for_title_cleanup: boolean;
ai_for_description_rewrite: boolean;
ai_for_ambiguous_classification: boolean;
ai_for_troubleshooting: boolean;
autotask_update_delay_ms: number;
max_ai_retries: number;
classification_confidence_threshold: ConfidenceLevel;
log_retention_days: number;
}
// ============================================================================
// API Types (request/response shapes for API routes)
// ============================================================================
export interface ClassificationRuleInput {
name: string;
description?: string;
rule_type: RuleType;
sort_order?: number;
is_active?: boolean;
match_field: MatchField;
match_operator: MatchOperator;
match_value: any;
match_case_sensitive?: boolean;
result_field: string;
result_value: any;
result_field_2?: string;
result_value_2?: any;
confidence?: ConfidenceLevel;
stop_on_match?: boolean;
}
export interface WorkflowRuleInput {
name: string;
description?: string;
is_active?: boolean;
sort_order?: number;
trigger_event: TriggerEvent;
trigger_entity?: string;
stop_processing?: boolean;
conditions: WorkflowConditionInput[];
actions: WorkflowActionInput[];
}
export interface WorkflowConditionInput {
condition_group?: number;
field: string;
operator: ConditionOperator;
value: any;
}
export interface WorkflowActionInput {
sort_order?: number;
action_type: ActionType;
config?: Record<string, any>;
}
export interface AiPromptTemplateInput {
name: string;
purpose: PromptPurpose;
system_prompt: string;
user_prompt_template: string;
provider?: string;
model?: string;
temperature?: number;
max_tokens?: number;
is_active?: boolean;
}
export interface WorkflowTestRequest {
ticket_id: number;
dry_run?: boolean; // default true
}
export interface WorkflowTestResult {
ticket: TicketData;
classification: ClassificationResult;
validation: ValidationResult;
proposed_changes: FieldChanges;
execution_steps: ExecutionStepSummary[];
}
/** Workflow rule with its conditions and actions loaded */
export interface WorkflowRuleWithDetails extends WorkflowRule {
conditions: WorkflowCondition[];
actions: WorkflowAction[];
}
/** Execution with its steps loaded */
export interface WorkflowExecutionWithSteps extends WorkflowExecution {
steps: WorkflowExecutionStep[];
}