wulf-pulse/docs/AUTOTASK_SYNC.md
lorentz 07067bef19 feat: Display Settings UI + Company Category/Type sync
- Add /admin/display-settings page with Kiosk and Mobile sections
- Company category checkbox filter + excluded companies searchable multi-select
- New DB tables: company_categories, company_types (migration 064)
- Sync COMPANY_CATEGORIES via CompanyCategories entity (id/name/isActive)
- Sync COMPANY_TYPES via Companies.companyType picklist
- Add to EntityType, ENTITY_DEPENDENCIES, sync-helpers, entity-mapper, entity-sync
- New API routes: /api/admin/display-settings (GET/POST), /api/data/company-categories, /api/data/companies-list
- Update all 4 routes (kiosk/stats, kiosk/activity, mobile/tickets, mobile/dashboard)
  to filter by kiosk_settings company_category_ids + excluded_company_ids
- Add Display Settings nav link (SlidersHorizontal icon) to Admin menu
- Seed kiosk_settings: kiosk_company_category_ids=1, mobile_company_category_ids=1
2026-04-06 09:03:19 -04:00

156 lines
6.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Autotask Sync — Reference Guide
## Overview
Pulse maintains a local PostgreSQL mirror of Autotask data in the `pulse_autotask` database. Three mechanisms keep it current: **scheduled syncs**, **real-time webhooks**, and **manual/API-triggered syncs**.
---
## Automatic Syncs (Scheduled)
All schedules are managed via the **Admin → Sync Scheduler** UI or directly in the `sync_schedules` table. They can be enabled/disabled individually.
| Schedule ID | Name | Cron (UTC) | Local (EST) | Type | Enabled |
|---|---|---|---|---|---|
| `sync-6am` | Morning Sync | `0 11 * * *` | 6:00 AM daily | incremental | ✅ |
| `sync-830am` | Morning Sync | `30 13 * * *` | 8:30 AM daily | incremental | ✅ |
| `sync-11am` | Midday Sync | `0 16 * * *` | 11:00 AM daily | incremental | ✅ |
| `sync-230pm` | Afternoon Sync | `30 19 * * *` | 2:30 PM daily | incremental | ✅ |
| `weekly-full` | Weekly Full Sync | `0 3 * * 0` | 3:00 AM Sunday | full | ✅ |
| `contract-services` | Contract Services | `0 4 * * *` | 4:00 AM daily | contract-services | ✅ |
### Incremental vs Full
**Incremental sync** — pulls only records modified since the last successful sync timestamp (stored in `sync_history`). Much faster. Runs 4× per day.
**Full sync** — pulls everything regardless of modification date, going back `yearsBack` years (default: 2). Runs every Sunday at 3 AM. Also used for initial setup or repair.
### What incremental syncs cover
Every incremental run syncs all entities in dependency order:
1. Companies, Resources, Statuses, Issue Types, Sub-Issue Types, Work Types, Queues, Priorities, Ticket Categories *(no dependencies)*
2. Contacts *(requires Companies)*
3. Projects *(requires Companies, Resources)*
4. Tickets *(requires Companies, Resources, Contacts)*
5. Tasks *(requires Resources, Projects, Tickets)*
6. Configuration Items *(requires Companies, Contacts)*
7. Contracts *(requires Companies, Contacts)*
8. Autotask Services *(standalone)*
9. Billing Items *(requires Companies, Tasks, Tickets, Projects)*
10. Time Entries *(requires Companies, Resources, Contacts, Projects, Tasks, Tickets)*
11. Ticket Notes *(requires Tickets)*
12. Tag Groups, Tags, Ticket Tag Associations
> **Note:** Companies and Resources do not support date-based filtering in the Autotask API, so they always do a full pull on every run (fast — ~57 resources, ~236 companies).
### Time-windowed entities
Some entities are too large to sync in full and are filtered by date:
| Entity | Filter field | Default window |
|---|---|---|
| Tickets | `createDate` | Last 2 years |
| Tasks | `createDate` | Last 2 years |
| Time Entries | `dateWorked` | Last 2 years |
| Billing Items | `itemDate` | Last 2 years |
The `yearsBack` parameter (default `2`) controls how far back these go. The full sync and chunked sync accept a custom `yearsBack` value.
---
## Real-Time Webhooks
Autotask pushes change events to `/api/webhooks/autotask` immediately when a ticket (or other entity) is created or updated. The webhook handler fetches the full record from the Autotask API and upserts it directly — no waiting for the next scheduled sync.
This means tickets assigned, updated, or closed in Autotask appear in Pulse within seconds. The webhook path **bypasses** the resource ID validation step, so it always writes exactly what Autotask sends.
---
## Manual / API-Triggered Syncs
### Via Admin UI
**Admin → Sync** — the sync overview page has buttons to trigger syncs per integration.
### Via API endpoints
All endpoints are POST and non-blocking (fire-and-forget) — they return immediately and run in the background.
| Endpoint | Payload | Description |
|---|---|---|
| `POST /api/sync/full` | `{ yearsBack?, triggeredBy? }` | Full sync of all entities |
| `POST /api/sync/entity` | `{ entities: [...], syncType?, yearsBack? }` | Sync specific entity types only |
| `POST /api/sync/tickets-chunked` | `{ yearsBack? }` | Chunked ticket sync with progress logging (good for large date ranges) |
**Example — sync only tickets and resources:**
```bash
curl -X POST http://localhost:3100/api/sync/entity \
-H "Content-Type: application/json" \
-d '{"entities": ["tickets", "resources"], "triggeredBy": "manual"}'
```
**Example — full sync going back 3 years:**
```bash
curl -X POST http://localhost:3100/api/sync/full \
-H "Content-Type: application/json" \
-d '{"yearsBack": 3, "triggeredBy": "manual"}'
```
### Via OpenClaw (external agent API)
```bash
# Incremental sync
POST /api/openclaw/sync/autotask/incremental
# Full sync
POST /api/openclaw/sync/autotask/full { "yearsBack": 2 }
# Specific entities
POST /api/openclaw/sync/autotask/entity { "entities": ["tickets"] }
```
Auth: `x-openclaw-key: <OPENCLAW_API_KEY>`
---
## Valid Entity Names
Use these string values in the `entities` array:
```
companies, resources, statuses, issue_types, sub_issue_types, work_types,
queues, priorities, ticket_categories, contacts, projects, tickets, tasks,
configuration_items, contracts, contract_services, autotask_services,
billing_items, time_entries, ticket_notes, tag_groups, tags
```
---
## Sync History & Status
Every sync run writes to `sync_history`. You can query it directly:
```sql
-- Last sync per entity type
SELECT entity_type, sync_type, status, records_added, records_updated, completed_at
FROM sync_history
WHERE status = 'completed'
ORDER BY completed_at DESC;
-- Check when tickets last synced successfully
SELECT completed_at FROM sync_history
WHERE entity_type = 'tickets' AND status = 'completed'
ORDER BY completed_at DESC LIMIT 1;
```
The incremental sync uses `completed_at` from `sync_history` to determine how far back to pull. **If a sync fails, the timestamp does not advance** — the next run re-fetches from the last successful point.
---
## Known Constraints & Gotchas
- **One sync at a time.** If a sync is already running, new requests return `409 Conflict`.
- **`assigned_resource_id` is the only resource assignment on a ticket.** There is no separate "primary resource" — `assigned_resource_id` is it. Other resource fields (`first_response_assigned_resource_id`, `last_activity_resource_id`, `creator_resource_id`) are audit/SLA tracking fields.
- **BIGINT IDs come back as strings from pg.** The sync code uses `Number()` coercion when building validation sets to avoid false-negative ID comparisons.
- **FK constraints on tickets are deferrable.** `tickets_company_id_fkey` and all resource FKs are `DEFERRABLE INITIALLY DEFERRED ON DELETE SET NULL`. This means a ticket can be inserted even if its referenced company or resource isn't in the local DB yet — the field is set to NULL rather than rejecting the row.
- **Webhook and scheduled sync can race.** If a webhook fires during a scheduled sync batch, it may be overwritten by the batch. This is harmless — both write the same Autotask data.