feat: add Autotask tags sync (tag groups, tags, ticket tag associations)

- Migration 057: autotask_tag_groups, autotask_tags, and junction tables
  (ticket_tags, company_tags, configuration_item_tags, contact_tags)
- Add TAG_GROUPS and TAGS to EntityType enum and dependency map
- Add mapTagGroup() and mapTag() entity mapper functions
- Add syncTagGroups(), syncTags(), syncTicketTagAssociations() methods
- Wire TicketTagAssociations bulk sync into full/incremental sync flow
- Add 'exist' operator to QueryFilter type
- No FK on ticket_id (tagged tickets may be outside 2yr sync window)

Synced: 26 tag groups, 7299 tags, 10141 ticket-tag associations
This commit is contained in:
lorentz 2026-03-20 09:22:40 -04:00
parent fe9f806c50
commit ff9e34cafe
8 changed files with 238 additions and 3 deletions

View file

@ -72,6 +72,12 @@ export function mapAutotaskToDatabase(
case EntityType.WORK_TYPES:
mapped = mapPicklist(data);
break;
case EntityType.TAG_GROUPS:
mapped = mapTagGroup(data);
break;
case EntityType.TAGS:
mapped = mapTag(data);
break;
default:
// Fallback: auto-convert camelCase to snake_case
mapped = {};
@ -724,6 +730,35 @@ function mapTimeEntry(data: any): Record<string, any> {
};
}
/**
* Map TagGroup entity
*/
function mapTagGroup(data: any): Record<string, any> {
return {
id: data.id,
label: data.label,
display_color: data.displayColor,
is_active: data.isActive !== undefined ? data.isActive : true,
is_system: data.isSystem || false,
};
}
/**
* Map Tag entity
*/
function mapTag(data: any): Record<string, any> {
return {
id: data.id,
label: data.label,
tag_group_id: data.tagGroupID,
is_active: data.isActive !== undefined ? data.isActive : true,
is_system: data.isSystem || false,
is_excluded_from_automatic_tagging: data.isExcludedFromAutomaticTagging || false,
create_date_time: data.createDateTime,
last_modified_date_time: data.lastModifiedDateTime,
};
}
/**
* Map Picklist entity (statuses, issue types, etc.)
*/

View file

@ -69,6 +69,8 @@ export function getAllEntitiesInOrder(): EntityType[] {
EntityType.AUTOTASK_SERVICES,
EntityType.BILLING_ITEMS,
EntityType.TIME_ENTRIES,
EntityType.TAG_GROUPS,
EntityType.TAGS,
]);
}
@ -122,6 +124,8 @@ export function getAutotaskEntityName(entity: EntityType): string {
[EntityType.AUTOTASK_SERVICES]: 'Services',
[EntityType.TIME_ENTRIES]: 'TimeEntries',
[EntityType.TICKET_NOTES]: 'TicketNotes',
[EntityType.TAG_GROUPS]: 'TagGroups',
[EntityType.TAGS]: 'Tags',
};
return mapping[entity] || entity;
@ -171,6 +175,8 @@ export function getLastModifiedField(entity: EntityType): string {
[EntityType.QUEUES]: 'lastModifiedDate',
[EntityType.PRIORITIES]: 'lastModifiedDate',
[EntityType.TICKET_CATEGORIES]: 'lastModifiedDate',
[EntityType.TAG_GROUPS]: 'lastModifiedDate',
[EntityType.TAGS]: 'lastModifiedDateTime',
};
return mapping[entity] || 'lastModifiedDate';
@ -203,6 +209,8 @@ export function getActiveField(entity: EntityType): string | null {
[EntityType.QUEUES]: 'isActive',
[EntityType.PRIORITIES]: 'isActive',
[EntityType.TICKET_CATEGORIES]: 'isActive',
[EntityType.TAG_GROUPS]: 'isActive',
[EntityType.TAGS]: 'isActive',
};
return mapping[entity] || null;
@ -288,6 +296,8 @@ export function buildDateRangeFilter(
[EntityType.CONTACTS]: null,
[EntityType.CONFIGURATION_ITEMS]: null,
[EntityType.TICKET_NOTES]: null,
[EntityType.TAG_GROUPS]: null,
[EntityType.TAGS]: null,
[EntityType.STATUSES]: null,
[EntityType.ISSUE_TYPES]: null,
[EntityType.SUB_ISSUE_TYPES]: null,
@ -486,6 +496,8 @@ export function getEntityDisplayName(entity: EntityType): string {
[EntityType.AUTOTASK_SERVICES]: 'Autotask Services',
[EntityType.TIME_ENTRIES]: 'Time Entries',
[EntityType.TICKET_NOTES]: 'Ticket Notes',
[EntityType.TAG_GROUPS]: 'Tag Groups',
[EntityType.TAGS]: 'Tags',
};
return mapping[entity] || entity;