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

@ -0,0 +1,74 @@
-- ============================================================================
-- Autotask Tags & Tag Groups
-- Standalone tag catalog + junction tables for tickets, companies,
-- configuration items, and contacts.
-- ============================================================================
-- Tag groups (categories that tags belong to)
CREATE TABLE IF NOT EXISTS autotask_tag_groups (
id BIGINT PRIMARY KEY,
label VARCHAR(100) NOT NULL,
display_color INTEGER,
is_active BOOLEAN DEFAULT TRUE,
is_system BOOLEAN DEFAULT FALSE,
synced_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
is_deleted BOOLEAN DEFAULT FALSE,
deleted_at TIMESTAMPTZ
);
-- Tags catalog
CREATE TABLE IF NOT EXISTS autotask_tags (
id BIGINT PRIMARY KEY,
label VARCHAR(100) NOT NULL,
tag_group_id BIGINT REFERENCES autotask_tag_groups(id) ON DELETE SET NULL,
is_active BOOLEAN DEFAULT TRUE,
is_system BOOLEAN DEFAULT FALSE,
is_excluded_from_automatic_tagging BOOLEAN DEFAULT FALSE,
create_date_time TIMESTAMPTZ,
last_modified_date_time TIMESTAMPTZ,
synced_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
is_deleted BOOLEAN DEFAULT FALSE,
deleted_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_autotask_tags_group ON autotask_tags (tag_group_id);
CREATE INDEX IF NOT EXISTS idx_autotask_tags_label ON autotask_tags (label);
CREATE INDEX IF NOT EXISTS idx_autotask_tags_active ON autotask_tags (is_active) WHERE is_active = true;
-- Junction: ticket ↔ tag
CREATE TABLE IF NOT EXISTS ticket_tags (
ticket_id BIGINT NOT NULL,
tag_id BIGINT NOT NULL REFERENCES autotask_tags(id) ON DELETE CASCADE,
synced_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (ticket_id, tag_id)
);
CREATE INDEX IF NOT EXISTS idx_ticket_tags_tag ON ticket_tags (tag_id);
-- Junction: company ↔ tag
CREATE TABLE IF NOT EXISTS company_tags (
company_id BIGINT NOT NULL,
tag_id BIGINT NOT NULL REFERENCES autotask_tags(id) ON DELETE CASCADE,
synced_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (company_id, tag_id)
);
CREATE INDEX IF NOT EXISTS idx_company_tags_tag ON company_tags (tag_id);
-- Junction: configuration_item ↔ tag
CREATE TABLE IF NOT EXISTS configuration_item_tags (
configuration_item_id BIGINT NOT NULL,
tag_id BIGINT NOT NULL REFERENCES autotask_tags(id) ON DELETE CASCADE,
synced_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (configuration_item_id, tag_id)
);
CREATE INDEX IF NOT EXISTS idx_config_item_tags_tag ON configuration_item_tags (tag_id);
-- Junction: contact ↔ tag
CREATE TABLE IF NOT EXISTS contact_tags (
contact_id BIGINT NOT NULL,
tag_id BIGINT NOT NULL REFERENCES autotask_tags(id) ON DELETE CASCADE,
synced_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (contact_id, tag_id)
);
CREATE INDEX IF NOT EXISTS idx_contact_tags_tag ON contact_tags (tag_id);