748 lines
19 KiB
Markdown
748 lines
19 KiB
Markdown
|
|
# Sync Behavior Documentation
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
This document provides comprehensive details about how the Autotask sync system works, including the differences between sync types, date range filtering, database operations, and entity-specific behaviors.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Table of Contents
|
||
|
|
|
||
|
|
- [Sync Types](#sync-types)
|
||
|
|
- [Date Range Filtering](#date-range-filtering)
|
||
|
|
- [Database Operations](#database-operations)
|
||
|
|
- [Entity-Specific Behaviors](#entity-specific-behaviors)
|
||
|
|
- [Foreign Key Validation](#foreign-key-validation)
|
||
|
|
- [Soft Deletes](#soft-deletes)
|
||
|
|
- [Performance Considerations](#performance-considerations)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Sync Types
|
||
|
|
|
||
|
|
### Full Sync
|
||
|
|
|
||
|
|
**What it does:**
|
||
|
|
- Fetches **ALL** records from Autotask that match the configured filters
|
||
|
|
- Performs an **UPSERT** operation on every fetched record
|
||
|
|
- Optionally performs **soft deletes** for missing records (entity-dependent)
|
||
|
|
|
||
|
|
**When to use:**
|
||
|
|
- Initial setup or first-time sync
|
||
|
|
- After major data changes in Autotask
|
||
|
|
- To ensure complete data consistency
|
||
|
|
- When you suspect data drift between systems
|
||
|
|
|
||
|
|
**Characteristics:**
|
||
|
|
- Processes all records within filter criteria (date range, status, etc.)
|
||
|
|
- Does NOT check if records have changed - upserts everything
|
||
|
|
- Slower than incremental sync
|
||
|
|
- More thorough and ensures data consistency
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```
|
||
|
|
Full Sync of Tickets (last 2 years):
|
||
|
|
1. Fetch all tickets created in last 2 years from Autotask
|
||
|
|
2. Upsert all fetched tickets to PostgreSQL
|
||
|
|
3. Skip soft deletes (has date filter)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Incremental Sync
|
||
|
|
|
||
|
|
**What it does:**
|
||
|
|
- Fetches only records **modified since the last successful sync**
|
||
|
|
- Uses `lastModifiedDate` or similar timestamp fields
|
||
|
|
- Performs UPSERT on changed records only
|
||
|
|
- Does NOT perform soft deletes
|
||
|
|
|
||
|
|
**When to use:**
|
||
|
|
- Regular scheduled syncs (hourly, daily)
|
||
|
|
- To keep data up-to-date with minimal overhead
|
||
|
|
- When you only need recent changes
|
||
|
|
|
||
|
|
**Characteristics:**
|
||
|
|
- Only processes records that changed since last sync
|
||
|
|
- Much faster than full sync
|
||
|
|
- Requires a previous successful sync to establish baseline
|
||
|
|
- Falls back to full sync if no previous sync found
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```
|
||
|
|
Incremental Sync of Companies:
|
||
|
|
1. Get last sync time: 2026-01-24 08:00:00
|
||
|
|
2. Fetch companies where lastModifiedDate >= 2026-01-24 08:00:00
|
||
|
|
3. Upsert only the changed companies
|
||
|
|
4. No soft deletes
|
||
|
|
```
|
||
|
|
|
||
|
|
### Sync Selected (Entity-Specific)
|
||
|
|
|
||
|
|
**What it does:**
|
||
|
|
- Syncs only the selected entities
|
||
|
|
- Uses full sync behavior for each selected entity
|
||
|
|
- Respects all entity-specific filters and behaviors
|
||
|
|
|
||
|
|
**When to use:**
|
||
|
|
- To sync specific entities without syncing everything
|
||
|
|
- After fixing entity-specific issues
|
||
|
|
- For testing or troubleshooting
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Date Range Filtering
|
||
|
|
|
||
|
|
### Entities with Required Filters
|
||
|
|
|
||
|
|
These entities **REQUIRE** filters - the Autotask API will return an error without them:
|
||
|
|
|
||
|
|
| Entity | Filter Field | Default Range | Reason |
|
||
|
|
|--------|-------------|---------------|---------|
|
||
|
|
| **Time Entries** | `dateWorked` | Last 2 years | API requirement |
|
||
|
|
| **Billing Items** | `itemDate` | Last 2 years | API requirement |
|
||
|
|
| **Contracts** | `status` | Active only (status=1) | API requirement |
|
||
|
|
| **Projects** | `status` | Non-completed (status≠5) | API requirement |
|
||
|
|
|
||
|
|
### Entities with Optional Date Filters
|
||
|
|
|
||
|
|
These entities have date filters applied for **performance** and **data management**:
|
||
|
|
|
||
|
|
| Entity | Filter Field | Default Range | Configurable |
|
||
|
|
|--------|-------------|---------------|--------------|
|
||
|
|
| **Tickets** | `createDate` | Last 2 years | Yes (via yearsBack) |
|
||
|
|
| **Tasks** | `createDateTime` | Last 2 years | Yes (via yearsBack) |
|
||
|
|
|
||
|
|
### Entities Without Date Filters
|
||
|
|
|
||
|
|
These entities sync **ALL** records (no date filtering):
|
||
|
|
|
||
|
|
- **Companies** - All active companies
|
||
|
|
- **Resources** - All active resources
|
||
|
|
- **Contacts** - All active contacts
|
||
|
|
- **Configuration Items** - All active items
|
||
|
|
- **Picklists** (Statuses, Issue Types, Sub-Issue Types, Work Types) - All values
|
||
|
|
|
||
|
|
### Configuring Date Range
|
||
|
|
|
||
|
|
The `yearsBack` parameter controls the date range:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Default: 2 years
|
||
|
|
syncService.fullSync(2);
|
||
|
|
|
||
|
|
// Custom: 5 years
|
||
|
|
syncService.fullSync(5);
|
||
|
|
|
||
|
|
// Via API
|
||
|
|
POST /api/sync/full
|
||
|
|
{
|
||
|
|
"yearsBack": 3,
|
||
|
|
"triggeredBy": "user@example.com"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Important Notes:**
|
||
|
|
|
||
|
|
1. **Date filters apply to BOTH full and incremental sync**
|
||
|
|
- Full sync: Fetches all records within date range
|
||
|
|
- Incremental sync: Fetches changed records within date range
|
||
|
|
|
||
|
|
2. **Soft deletes are skipped for date-filtered entities**
|
||
|
|
- Prevents deleting old records outside the sync window
|
||
|
|
- See [Soft Deletes](#soft-deletes) section
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Database Operations
|
||
|
|
|
||
|
|
### UPSERT Behavior
|
||
|
|
|
||
|
|
All syncs use PostgreSQL's `ON CONFLICT DO UPDATE` (UPSERT):
|
||
|
|
|
||
|
|
```sql
|
||
|
|
INSERT INTO table_name (id, field1, field2, ...)
|
||
|
|
VALUES ($1, $2, $3, ...)
|
||
|
|
ON CONFLICT (id)
|
||
|
|
DO UPDATE SET
|
||
|
|
field1 = EXCLUDED.field1,
|
||
|
|
field2 = EXCLUDED.field2,
|
||
|
|
...
|
||
|
|
synced_at = NOW()
|
||
|
|
```
|
||
|
|
|
||
|
|
**What this means:**
|
||
|
|
|
||
|
|
- **If record exists**: All fields are updated with new values from Autotask
|
||
|
|
- **If record doesn't exist**: Record is inserted as new
|
||
|
|
- **Every fetched record is processed**, regardless of whether data actually changed
|
||
|
|
- The `synced_at` timestamp is always updated
|
||
|
|
|
||
|
|
### Record Counts
|
||
|
|
|
||
|
|
Sync history tracks three metrics:
|
||
|
|
|
||
|
|
| Metric | Description | How It's Calculated |
|
||
|
|
|--------|-------------|---------------------|
|
||
|
|
| `records_added` | New records inserted | **Estimated** (~10% of upserted) |
|
||
|
|
| `records_updated` | Existing records updated | **Estimated** (~90% of upserted) |
|
||
|
|
| `records_deleted` | Records soft-deleted | **Actual count** from soft delete operation |
|
||
|
|
|
||
|
|
**Important:** `records_added` and `records_updated` are **estimates** based on the total upserted count. PostgreSQL doesn't easily distinguish between inserts and updates in bulk UPSERT operations.
|
||
|
|
|
||
|
|
### Bulk Processing
|
||
|
|
|
||
|
|
Records are processed in batches for performance:
|
||
|
|
|
||
|
|
- **Fetch batch size**: 500 records per API call
|
||
|
|
- **Upsert batch size**: 100 records per database transaction
|
||
|
|
- **Pagination**: Automatic for large datasets
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Entity-Specific Behaviors
|
||
|
|
|
||
|
|
### Picklist Entities
|
||
|
|
|
||
|
|
**Entities:** Statuses, Issue Types, Sub-Issue Types, Work Types
|
||
|
|
|
||
|
|
**Special Handling:**
|
||
|
|
- Fetched from `/entity/entityInformation/fields` endpoint (not `/query`)
|
||
|
|
- No date filtering
|
||
|
|
- No soft deletes
|
||
|
|
- Synced as key-value pairs
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```typescript
|
||
|
|
// Statuses from Tickets.status field
|
||
|
|
GET /Tickets/entityInformation/fields
|
||
|
|
|
||
|
|
// Maps to:
|
||
|
|
{ value: 1, label: "New", is_active: true }
|
||
|
|
{ value: 5, label: "Complete", is_active: true }
|
||
|
|
```
|
||
|
|
|
||
|
|
### Tickets
|
||
|
|
|
||
|
|
**Filters:**
|
||
|
|
- Date: `createDate >= (now - 2 years)`
|
||
|
|
- Active: `isActive = true` (if applicable)
|
||
|
|
|
||
|
|
**Foreign Key Validation:**
|
||
|
|
- `assigned_resource_id` → Resources
|
||
|
|
- `first_response_assigned_resource_id` → Resources
|
||
|
|
- `first_response_initiating_resource_id` → Resources
|
||
|
|
- Invalid references are set to `null`
|
||
|
|
|
||
|
|
**Soft Deletes:** ❌ Skipped (has date filter)
|
||
|
|
|
||
|
|
### Tasks
|
||
|
|
|
||
|
|
**Filters:**
|
||
|
|
- Date: `createDateTime >= (now - 2 years)`
|
||
|
|
|
||
|
|
**Foreign Key Validation:**
|
||
|
|
- `project_id` → Projects
|
||
|
|
- `assigned_resource_id` → Resources
|
||
|
|
- `creator_resource_id` → Resources
|
||
|
|
- `completed_by_resource_id` → Resources
|
||
|
|
- `last_activity_resource_id` → Resources
|
||
|
|
- Invalid references are set to `null`
|
||
|
|
|
||
|
|
**Soft Deletes:** ❌ Skipped (has date filter)
|
||
|
|
|
||
|
|
### Time Entries
|
||
|
|
|
||
|
|
**Filters:**
|
||
|
|
- **Required**: `dateWorked >= (now - 2 years)`
|
||
|
|
|
||
|
|
**Special Notes:**
|
||
|
|
- `company_id` is nullable (time entries can exist without a company)
|
||
|
|
- No foreign key validation needed
|
||
|
|
|
||
|
|
**Soft Deletes:** ❌ Skipped (has date filter)
|
||
|
|
|
||
|
|
### Billing Items
|
||
|
|
|
||
|
|
**Filters:**
|
||
|
|
- **Required**: `itemDate >= (now - 2 years)`
|
||
|
|
|
||
|
|
**Special Notes:**
|
||
|
|
- API requires a filter parameter
|
||
|
|
- Returns 500 error without filter
|
||
|
|
|
||
|
|
**Soft Deletes:** ❌ Skipped (has date filter)
|
||
|
|
|
||
|
|
### Companies
|
||
|
|
|
||
|
|
**Filters:**
|
||
|
|
- Active: `isActive = true`
|
||
|
|
|
||
|
|
**Special Notes:**
|
||
|
|
- No date filtering - syncs all active companies
|
||
|
|
- Foundation entity for many foreign keys
|
||
|
|
|
||
|
|
**Soft Deletes:** ✅ Yes (no date filter)
|
||
|
|
|
||
|
|
### Resources
|
||
|
|
|
||
|
|
**Filters:**
|
||
|
|
- Active: `isActive = true`
|
||
|
|
|
||
|
|
**Special Notes:**
|
||
|
|
- No date filtering - syncs all active resources
|
||
|
|
- Referenced by Tickets, Tasks, Time Entries
|
||
|
|
|
||
|
|
**Soft Deletes:** ✅ Yes (no date filter)
|
||
|
|
|
||
|
|
### Contacts
|
||
|
|
|
||
|
|
**Filters:**
|
||
|
|
- Active: `isActive = true`
|
||
|
|
|
||
|
|
**Special Notes:**
|
||
|
|
- No date filtering - syncs all active contacts
|
||
|
|
- Referenced by Configuration Items
|
||
|
|
|
||
|
|
**Soft Deletes:** ✅ Yes (no date filter)
|
||
|
|
|
||
|
|
### Configuration Items
|
||
|
|
|
||
|
|
**Filters:**
|
||
|
|
- Active: `isActive = true`
|
||
|
|
|
||
|
|
**Foreign Key Validation:**
|
||
|
|
- `contact_id` → Contacts
|
||
|
|
- Invalid references are set to `null`
|
||
|
|
|
||
|
|
**Soft Deletes:** ✅ Yes (no date filter)
|
||
|
|
|
||
|
|
### Contracts
|
||
|
|
|
||
|
|
**Filters:**
|
||
|
|
- **Required**: `status = 1` (Active)
|
||
|
|
|
||
|
|
**Special Notes:**
|
||
|
|
- API requires status filter
|
||
|
|
- Only syncs active contracts
|
||
|
|
|
||
|
|
**Soft Deletes:** ❌ Skipped (has status filter)
|
||
|
|
|
||
|
|
### Projects
|
||
|
|
|
||
|
|
**Filters:**
|
||
|
|
- **Required**: `status != 5` (Not Complete)
|
||
|
|
|
||
|
|
**Special Notes:**
|
||
|
|
- API requires status filter
|
||
|
|
- Excludes completed projects
|
||
|
|
|
||
|
|
**Soft Deletes:** ❌ Skipped (has status filter)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Foreign Key Validation
|
||
|
|
|
||
|
|
### Why It's Needed
|
||
|
|
|
||
|
|
Autotask data may reference entities that:
|
||
|
|
- Haven't been synced yet
|
||
|
|
- Are outside the sync date range
|
||
|
|
- Have been deleted in Autotask
|
||
|
|
- Don't exist due to data inconsistencies
|
||
|
|
|
||
|
|
Without validation, these references cause PostgreSQL foreign key constraint violations.
|
||
|
|
|
||
|
|
### How It Works
|
||
|
|
|
||
|
|
Before upserting records, the sync validates foreign key references:
|
||
|
|
|
||
|
|
1. **Fetch valid IDs** from the database
|
||
|
|
```typescript
|
||
|
|
const validResourceIds = await getValidResourceIds();
|
||
|
|
// Returns Set of all resource IDs where is_deleted = false
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Validate each record**
|
||
|
|
```typescript
|
||
|
|
if (task.assigned_resource_id && !validResourceIds.has(task.assigned_resource_id)) {
|
||
|
|
task.assigned_resource_id = null; // Nullify invalid reference
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
3. **Log warnings** for nullified references
|
||
|
|
```
|
||
|
|
[WARN] Nullified invalid resource references: 15 records
|
||
|
|
```
|
||
|
|
|
||
|
|
### Validated Relationships
|
||
|
|
|
||
|
|
| Entity | Foreign Key Field | References | Action on Invalid |
|
||
|
|
|--------|------------------|------------|-------------------|
|
||
|
|
| **Tickets** | `assigned_resource_id` | Resources | Set to null |
|
||
|
|
| **Tickets** | `first_response_assigned_resource_id` | Resources | Set to null |
|
||
|
|
| **Tickets** | `first_response_initiating_resource_id` | Resources | Set to null |
|
||
|
|
| **Tasks** | `project_id` | Projects | Set to null |
|
||
|
|
| **Tasks** | `assigned_resource_id` | Resources | Set to null |
|
||
|
|
| **Tasks** | `creator_resource_id` | Resources | Set to null |
|
||
|
|
| **Tasks** | `completed_by_resource_id` | Resources | Set to null |
|
||
|
|
| **Tasks** | `last_activity_resource_id` | Resources | Set to null |
|
||
|
|
| **Configuration Items** | `contact_id` | Contacts | Set to null |
|
||
|
|
|
||
|
|
### Performance Impact
|
||
|
|
|
||
|
|
Foreign key validation queries are:
|
||
|
|
- Executed once per sync (cached)
|
||
|
|
- Indexed queries (fast)
|
||
|
|
- Minimal overhead compared to API fetching
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Soft Deletes
|
||
|
|
|
||
|
|
### What Are Soft Deletes?
|
||
|
|
|
||
|
|
Instead of permanently deleting records, the system marks them as deleted:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
UPDATE table_name
|
||
|
|
SET is_deleted = true, deleted_at = NOW()
|
||
|
|
WHERE id NOT IN (fetched_ids)
|
||
|
|
AND is_deleted = false
|
||
|
|
```
|
||
|
|
|
||
|
|
### When Soft Deletes Occur
|
||
|
|
|
||
|
|
**Only during Full Sync** for entities **without date filters**:
|
||
|
|
|
||
|
|
| Entity | Soft Deletes? | Reason |
|
||
|
|
|--------|--------------|---------|
|
||
|
|
| Companies | ✅ Yes | No date filter - safe to delete |
|
||
|
|
| Resources | ✅ Yes | No date filter - safe to delete |
|
||
|
|
| Contacts | ✅ Yes | No date filter - safe to delete |
|
||
|
|
| Configuration Items | ✅ Yes | No date filter - safe to delete |
|
||
|
|
| Tickets | ❌ No | Has date filter - would delete old records |
|
||
|
|
| Tasks | ❌ No | Has date filter - would delete old records |
|
||
|
|
| Time Entries | ❌ No | Has date filter - would delete old records |
|
||
|
|
| Billing Items | ❌ No | Has date filter - would delete old records |
|
||
|
|
| Projects | ❌ No | Has status filter - would delete completed |
|
||
|
|
| Contracts | ❌ No | Has status filter - would delete inactive |
|
||
|
|
| Picklists | ❌ No | Picklist values don't get deleted |
|
||
|
|
|
||
|
|
### Why Skip Soft Deletes for Date-Filtered Entities?
|
||
|
|
|
||
|
|
**Problem:**
|
||
|
|
If we soft-deleted records not in the fetched set for date-filtered entities, we would incorrectly delete old records outside the sync window.
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```
|
||
|
|
Full Sync of Tickets (last 2 years):
|
||
|
|
1. Fetch tickets created >= 2024-01-24
|
||
|
|
2. Ticket #12345 created in 2020 exists in DB
|
||
|
|
3. Ticket #12345 NOT in fetched set (outside date range)
|
||
|
|
4. ❌ Would incorrectly soft-delete Ticket #12345
|
||
|
|
|
||
|
|
Solution: Skip soft deletes for date-filtered entities
|
||
|
|
```
|
||
|
|
|
||
|
|
### Restoring Soft-Deleted Records
|
||
|
|
|
||
|
|
If a record is soft-deleted but appears in a later sync:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- Record is automatically restored
|
||
|
|
UPDATE table_name
|
||
|
|
SET is_deleted = false, deleted_at = NULL, ...
|
||
|
|
WHERE id = $1
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Performance Considerations
|
||
|
|
|
||
|
|
### Sync Duration Factors
|
||
|
|
|
||
|
|
| Factor | Impact | Mitigation |
|
||
|
|
|--------|--------|------------|
|
||
|
|
| **Number of records** | High | Use date range filters |
|
||
|
|
| **API rate limits** | Medium | Built-in rate limiting (10 req/sec) |
|
||
|
|
| **Network latency** | Medium | Pagination, batch processing |
|
||
|
|
| **Database operations** | Low | Bulk upserts, indexed queries |
|
||
|
|
| **Foreign key validation** | Low | Cached, indexed queries |
|
||
|
|
|
||
|
|
### Estimated Sync Times
|
||
|
|
|
||
|
|
Based on typical data volumes:
|
||
|
|
|
||
|
|
| Entity | Record Count | Full Sync Time | Incremental Sync Time |
|
||
|
|
|--------|-------------|----------------|----------------------|
|
||
|
|
| Companies | 500 | ~30 seconds | ~5 seconds |
|
||
|
|
| Resources | 100 | ~10 seconds | ~2 seconds |
|
||
|
|
| Tickets (2 years) | 10,000 | ~10 minutes | ~1 minute |
|
||
|
|
| Tasks (2 years) | 5,000 | ~5 minutes | ~30 seconds |
|
||
|
|
| Time Entries (2 years) | 50,000 | ~45 minutes | ~5 minutes |
|
||
|
|
| Billing Items (2 years) | 50,000+ | ~45-60 minutes | ~5 minutes |
|
||
|
|
|
||
|
|
**Note:** Times vary based on data volume, network speed, and system load.
|
||
|
|
|
||
|
|
### Optimization Tips
|
||
|
|
|
||
|
|
1. **Use Incremental Sync for regular updates**
|
||
|
|
- Schedule incremental syncs hourly or daily
|
||
|
|
- Reserve full syncs for weekly/monthly maintenance
|
||
|
|
|
||
|
|
2. **Adjust date range based on needs**
|
||
|
|
- Default 2 years is usually sufficient
|
||
|
|
- Reduce to 1 year for faster syncs if older data isn't needed
|
||
|
|
- Increase to 5+ years only if historical data is required
|
||
|
|
|
||
|
|
3. **Sync entities in dependency order**
|
||
|
|
- Companies → Resources → Tickets → Tasks
|
||
|
|
- Reduces foreign key validation overhead
|
||
|
|
|
||
|
|
4. **Monitor sync history**
|
||
|
|
- Check for failures and patterns
|
||
|
|
- Identify slow entities for optimization
|
||
|
|
|
||
|
|
5. **Use Sync Selected for troubleshooting**
|
||
|
|
- Test individual entities
|
||
|
|
- Isolate and fix issues without full sync
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Sync Workflow Examples
|
||
|
|
|
||
|
|
### Example 1: Initial Setup
|
||
|
|
|
||
|
|
```
|
||
|
|
1. Full Sync - All Entities (2 years)
|
||
|
|
├─ Companies (all active)
|
||
|
|
├─ Resources (all active)
|
||
|
|
├─ Contacts (all active)
|
||
|
|
├─ Configuration Items (all active)
|
||
|
|
├─ Contracts (active only)
|
||
|
|
├─ Projects (non-completed)
|
||
|
|
├─ Tickets (last 2 years)
|
||
|
|
├─ Tasks (last 2 years)
|
||
|
|
├─ Time Entries (last 2 years)
|
||
|
|
├─ Billing Items (last 2 years)
|
||
|
|
└─ Picklists (all values)
|
||
|
|
|
||
|
|
Result: Complete database populated with all relevant data
|
||
|
|
```
|
||
|
|
|
||
|
|
### Example 2: Daily Maintenance
|
||
|
|
|
||
|
|
```
|
||
|
|
1. Incremental Sync - All Entities
|
||
|
|
├─ Fetch only records modified since last sync
|
||
|
|
├─ Upsert changed records
|
||
|
|
└─ No soft deletes
|
||
|
|
|
||
|
|
Result: Database updated with latest changes (fast)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Example 3: Fixing Specific Entity
|
||
|
|
|
||
|
|
```
|
||
|
|
1. Identify failed entity: Billing Items
|
||
|
|
2. Sync Selected - Billing Items only
|
||
|
|
├─ Full sync with date filter (last 2 years)
|
||
|
|
├─ Upsert all billing items
|
||
|
|
└─ No soft deletes
|
||
|
|
|
||
|
|
Result: Billing Items data refreshed and consistent
|
||
|
|
```
|
||
|
|
|
||
|
|
### Example 4: Historical Data Sync
|
||
|
|
|
||
|
|
```
|
||
|
|
1. Full Sync - Tickets (5 years)
|
||
|
|
├─ Adjust yearsBack parameter to 5
|
||
|
|
├─ Fetch tickets created >= 2021-01-24
|
||
|
|
├─ Upsert all fetched tickets
|
||
|
|
└─ Skip soft deletes
|
||
|
|
|
||
|
|
Result: 5 years of ticket history in database
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Troubleshooting
|
||
|
|
|
||
|
|
### Common Issues
|
||
|
|
|
||
|
|
#### Issue: Foreign Key Constraint Violations
|
||
|
|
|
||
|
|
**Symptoms:**
|
||
|
|
```
|
||
|
|
[DATABASE_CONSTRAINT_ERROR] violates foreign key constraint "tasks_project_id_fkey"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Cause:** Referenced entity hasn't been synced or is outside date range
|
||
|
|
|
||
|
|
**Solution:**
|
||
|
|
1. Sync dependency entities first (Companies → Resources → Projects)
|
||
|
|
2. Foreign key validation will nullify invalid references
|
||
|
|
3. Check logs for nullified reference warnings
|
||
|
|
|
||
|
|
#### Issue: API Filter Required Error
|
||
|
|
|
||
|
|
**Symptoms:**
|
||
|
|
```
|
||
|
|
[API_ERROR] Value cannot be null. Parameter name: filters
|
||
|
|
```
|
||
|
|
|
||
|
|
**Cause:** Entity requires a filter (Time Entries, Billing Items, Contracts, Projects)
|
||
|
|
|
||
|
|
**Solution:** Filters are automatically applied - this shouldn't occur. If it does, check entity-sync.ts for proper filter configuration.
|
||
|
|
|
||
|
|
#### Issue: Slow Sync Performance
|
||
|
|
|
||
|
|
**Symptoms:** Sync takes longer than expected
|
||
|
|
|
||
|
|
**Solutions:**
|
||
|
|
1. Reduce `yearsBack` parameter (e.g., from 2 to 1 year)
|
||
|
|
2. Use incremental sync instead of full sync
|
||
|
|
3. Sync entities individually instead of all at once
|
||
|
|
4. Check network connectivity and API response times
|
||
|
|
|
||
|
|
#### Issue: Records Not Appearing
|
||
|
|
|
||
|
|
**Symptoms:** Records exist in Autotask but not in database
|
||
|
|
|
||
|
|
**Possible Causes:**
|
||
|
|
1. Outside date range filter
|
||
|
|
2. Not active (filtered out by `isActive`)
|
||
|
|
3. Foreign key validation nullified references
|
||
|
|
4. Sync failed (check sync history)
|
||
|
|
|
||
|
|
**Solutions:**
|
||
|
|
1. Check sync history for errors
|
||
|
|
2. Verify record meets filter criteria
|
||
|
|
3. Check logs for validation warnings
|
||
|
|
4. Run full sync for the entity
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Best Practices
|
||
|
|
|
||
|
|
### 1. Sync Strategy
|
||
|
|
|
||
|
|
- **Initial Setup:** Full sync all entities (2 years)
|
||
|
|
- **Daily Maintenance:** Incremental sync all entities
|
||
|
|
- **Weekly/Monthly:** Full sync for data consistency
|
||
|
|
- **Troubleshooting:** Sync selected entities as needed
|
||
|
|
|
||
|
|
### 2. Date Range Configuration
|
||
|
|
|
||
|
|
- **Default (2 years):** Good for most use cases
|
||
|
|
- **1 year:** Faster syncs, less historical data
|
||
|
|
- **3-5 years:** More historical data, slower syncs
|
||
|
|
- **Adjust based on:** Business needs, performance requirements
|
||
|
|
|
||
|
|
### 3. Monitoring
|
||
|
|
|
||
|
|
- Review sync history regularly
|
||
|
|
- Set up alerts for failed syncs
|
||
|
|
- Monitor sync duration trends
|
||
|
|
- Check for foreign key validation warnings
|
||
|
|
|
||
|
|
### 4. Dependency Management
|
||
|
|
|
||
|
|
Sync entities in this order for best results:
|
||
|
|
1. Companies
|
||
|
|
2. Resources
|
||
|
|
3. Contacts
|
||
|
|
4. Configuration Items
|
||
|
|
5. Contracts
|
||
|
|
6. Projects
|
||
|
|
7. Tickets
|
||
|
|
8. Tasks
|
||
|
|
9. Time Entries
|
||
|
|
10. Billing Items
|
||
|
|
11. Picklists
|
||
|
|
|
||
|
|
### 5. Error Handling
|
||
|
|
|
||
|
|
- Failed syncs are logged in `sync_history` table
|
||
|
|
- Check error messages for specific issues
|
||
|
|
- Re-run failed entity syncs individually
|
||
|
|
- Contact support if errors persist
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## API Reference
|
||
|
|
|
||
|
|
### Full Sync
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
POST /api/sync/full
|
||
|
|
{
|
||
|
|
"yearsBack": 2,
|
||
|
|
"triggeredBy": "user@example.com"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Incremental Sync
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
POST /api/sync/incremental
|
||
|
|
{
|
||
|
|
"triggeredBy": "user@example.com"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Sync Selected Entities
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
POST /api/sync/entity
|
||
|
|
{
|
||
|
|
"entities": ["tickets", "tasks"],
|
||
|
|
"yearsBack": 2,
|
||
|
|
"triggeredBy": "user@example.com"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Get Sync History
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
GET /api/sync/history?limit=50&offset=0
|
||
|
|
```
|
||
|
|
|
||
|
|
### Get Sync Status
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
GET /api/sync/status
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Related Documentation
|
||
|
|
|
||
|
|
- [Sync Interface Guide](./SYNC_INTERFACE_GUIDE.md) - User guide for the sync UI
|
||
|
|
- [Sync Logging Improvements](./SYNC_LOGGING_IMPROVEMENTS.md) - Structured logging details
|
||
|
|
- [Database Schema](../migrations/001_initial_schema.sql) - PostgreSQL table definitions
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Changelog
|
||
|
|
|
||
|
|
### 2026-01-24
|
||
|
|
- Added billing items filter requirement
|
||
|
|
- Added task foreign key validation
|
||
|
|
- Documented sync behavior comprehensively
|
||
|
|
|
||
|
|
### 2026-01-23
|
||
|
|
- Fixed statuses and work types sync (picklist API)
|
||
|
|
- Added structured logging
|
||
|
|
- Created sync interface documentation
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Support
|
||
|
|
|
||
|
|
For issues or questions:
|
||
|
|
1. Check sync history in the admin interface
|
||
|
|
2. Review error messages and logs
|
||
|
|
3. Consult this documentation
|
||
|
|
4. Contact system administrator
|