toga-ai 1.0.35 → 1.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/knowledge/1.0/apps/library/INDEX.md +1 -0
- package/knowledge/1.0/apps/library/features/elite-freshservice-sync.md +166 -0
- package/knowledge/2.0/apps/worker2/INDEX.md +1 -0
- package/knowledge/2.0/apps/worker2/features/elite-freshservice-sync.md +142 -0
- package/knowledge/INDEX.md +3 -2
- package/knowledge/clients/elite/INDEX.md +5 -0
- package/knowledge/clients/elite/profile.md +61 -0
- package/package.json +1 -1
|
@@ -3,3 +3,4 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [Library (1.0 Framework) Architecture](architecture.md) | `library` is the shared library repository for **all 1.0 (legacy) applications** — the `App_` framework. | library/_.php, library/app/, library/browser/ |
|
|
6
|
+
| [Elite Freshservice Sync (library)](features/elite-freshservice-sync.md) | `App_Api_Toga2` in `library/app/api/toga2.php` orchestrates bidirectional sync between TOGA 2 and TOGaDesk. | library/app/api/toga2.php |
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Elite Freshservice Sync (library)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: library
|
|
5
|
+
project: Library
|
|
6
|
+
client: elite
|
|
7
|
+
type: client-feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-10
|
|
10
|
+
owners: []
|
|
11
|
+
files:
|
|
12
|
+
- library/app/api/toga2.php
|
|
13
|
+
related:
|
|
14
|
+
- clients/elite/profile.md
|
|
15
|
+
- 2.0/apps/worker2/features/elite-freshservice-sync.md
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Summary
|
|
19
|
+
|
|
20
|
+
`App_Api_Toga2` in `library/app/api/toga2.php` orchestrates bidirectional sync between
|
|
21
|
+
TOGA 2 and TOGaDesk. The top-level entry point is `syncWithTogadesk()`. Three private
|
|
22
|
+
helper methods handle file-attachment sync within that orchestration.
|
|
23
|
+
|
|
24
|
+
## Top-level entry point — `syncWithTogadesk()`
|
|
25
|
+
|
|
26
|
+
14-parameter static method. Called from a cron script for each client.
|
|
27
|
+
|
|
28
|
+
```php
|
|
29
|
+
App_Api_Toga2::syncWithTogadesk(
|
|
30
|
+
int $togadeskClientId,
|
|
31
|
+
int $togadeskTicketDepartmentId,
|
|
32
|
+
string $togaClientUuid,
|
|
33
|
+
string $togaApiKey,
|
|
34
|
+
string $togaApiSecret,
|
|
35
|
+
string $integrationType, // 'REPAIR_ORDERS' or 'TICKETS'
|
|
36
|
+
bool $isEnabledToga2ToTogadeskIntegration,
|
|
37
|
+
bool $isEnabledTogadeskToToga2Integration,
|
|
38
|
+
bool $isEnabledToga2ContactsToTogadeskPeopleIntegration,
|
|
39
|
+
bool $isEnabledToga2ItemsUnitsToTogadeskAssetsIntegration,
|
|
40
|
+
bool $isEnabledToga2PredefinedRepliesToTogadeskTicketsPrIntegration,
|
|
41
|
+
bool $isEnabledToga2TicketTeamsToTogadeskGroupsIntegration,
|
|
42
|
+
bool $isEnabledToga2TicketCategoriesToTogadeskCategorySubcategoryItemsIntegration,
|
|
43
|
+
array $monitorTogadeskDepartmentIds = []
|
|
44
|
+
);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Constants for `$integrationType`:
|
|
48
|
+
```php
|
|
49
|
+
App_Api_Toga2::SYNC_WITH_TOGADESK_INTEGRATION_TYPE__REPAIR_ORDERS // = 'REPAIR_ORDERS'
|
|
50
|
+
App_Api_Toga2::SYNC_WITH_TOGADESK_INTEGRATION_TYPE__TICKETS // = 'TICKETS'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Watermark timestamps
|
|
54
|
+
|
|
55
|
+
The sync uses two watermarks stored as TOGA 2 `/parameters` records. These are fetched
|
|
56
|
+
at the start of each run and updated at the end:
|
|
57
|
+
|
|
58
|
+
| TOGA 2 parameter key | Meaning | Used for |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| `TOGADESK_LAST_TICKET_INTEGRATION_DATETIME` | Newest `_updated` timestamp of tickets synced from TOGA 2 | Drives TOGA 2 → TOGaDesk query |
|
|
61
|
+
| `TOGA_LAST_TICKET_INTEGRATION_DATETIME` | Newest `timestamp` of tickets/replies synced from TOGaDesk | Drives TOGaDesk → TOGA 2 query |
|
|
62
|
+
|
|
63
|
+
Both default to `2000-01-01 00:00:00` if not yet set (full initial sync).
|
|
64
|
+
|
|
65
|
+
## TOGA 2 → TOGaDesk sync (`isEnabledToga2ToTogadeskIntegration`)
|
|
66
|
+
|
|
67
|
+
Runs when this flag is `true`. Processes 200 records per page.
|
|
68
|
+
|
|
69
|
+
**Step 1 — Changed tickets:**
|
|
70
|
+
- `GET /tickets?where[_updated > TOGADESK_LAST_TICKET_INTEGRATION_DATETIME]` (paged)
|
|
71
|
+
- Each ticket re-fetched at `depth=5` for full details
|
|
72
|
+
- Calls `syncToga2TicketIntoTogadesk1Ticket($togaTicket)`
|
|
73
|
+
|
|
74
|
+
**Step 2 — Changed ticket-notes (catches notes on already-processed tickets):**
|
|
75
|
+
- `GET /ticket-notes?where[_updated > TOGADESK_LAST_TICKET_INTEGRATION_DATETIME]` (paged)
|
|
76
|
+
- Skips tickets already processed in Step 1 (tracked in `$alreadyProcessedToga2TicketUuids`)
|
|
77
|
+
- Re-fetches the parent ticket at `depth=5`, calls `syncToga2TicketIntoTogadesk1Ticket`
|
|
78
|
+
|
|
79
|
+
**Inside `syncToga2TicketIntoTogadesk1Ticket`:**
|
|
80
|
+
- `isExternal = true` note → creates `tickets_replies` record with `referenceid = ticketNote->uuid`
|
|
81
|
+
- `isExternal = false` note → creates `comments` record with `referenceid = ticketNote->uuid`
|
|
82
|
+
- After creating a new reply: calls `syncToga2NoteFilesIntoTogadesk1($ticketNote, $togadeskReplyId)`
|
|
83
|
+
- Also writes a `tickets_history` row for each new reply
|
|
84
|
+
|
|
85
|
+
## TOGaDesk → TOGA 2 sync (`isEnabledTogadeskToToga2Integration`)
|
|
86
|
+
|
|
87
|
+
Runs when this flag is `true`.
|
|
88
|
+
|
|
89
|
+
**Ticket selection:**
|
|
90
|
+
```sql
|
|
91
|
+
SELECT tickets.id,
|
|
92
|
+
GREATEST(
|
|
93
|
+
COALESCE(latestReply.timestamp, tickets.timestamp),
|
|
94
|
+
COALESCE(latestComment.timestamp, tickets.timestamp)
|
|
95
|
+
) AS ticketIntegrationTimestamp
|
|
96
|
+
FROM tickets
|
|
97
|
+
LEFT JOIN (SELECT ticketid, MAX(timestamp) FROM tickets_replies GROUP BY ticketid) AS latestReply ...
|
|
98
|
+
LEFT JOIN (SELECT ticketid, MAX(timestamp) FROM comments GROUP BY ticketid) AS latestComment ...
|
|
99
|
+
WHERE tickets.clientid = ? AND tickets.departmentId IN (?)
|
|
100
|
+
AND GREATEST(...) > '$dtTogaLastTicketIntegration'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Per ticket:** calls `syncTogaDesk1TicketIntoToga2Ticket($togadeskTicketId)`
|
|
104
|
+
|
|
105
|
+
**Inside that method (replies loop):**
|
|
106
|
+
- For each `tickets_replies` row without a `referenceId`: POST to `/ticket-notes` in TOGA 2
|
|
107
|
+
with `c_eliteConversationId` (for Elite) or `c_wjeConversationId` (for WJE)
|
|
108
|
+
- Stores returned `ticketNotes.uuid` back into `tickets_replies.referenceId`
|
|
109
|
+
- Calls `syncTogadesk1NoteFilesIntoToga2($togadeskReplyId, $ticketNoteUuid)`
|
|
110
|
+
|
|
111
|
+
**`referenceid` field** in TOGaDesk (`tickets_replies.referenceId`, `comments.referenceId`)
|
|
112
|
+
is the foreign key to TOGA 2 — it holds the TOGA 2 `ticketNotes.uuid`. When `referenceId`
|
|
113
|
+
is already populated the row was previously synced and is skipped.
|
|
114
|
+
|
|
115
|
+
## Other sync operations within `syncWithTogadesk`
|
|
116
|
+
|
|
117
|
+
| Flag | What it syncs | TOGA 2 source → TOGaDesk target |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `isEnabledToga2ContactsToTogadeskPeopleIntegration` | Contacts → People | `/contacts` → `people` table |
|
|
120
|
+
| `isEnabledToga2ItemsUnitsToTogadeskAssetsIntegration` | Units → Assets | `/units` → `assets` table |
|
|
121
|
+
| `isEnabledToga2PredefinedRepliesToTogadeskTicketsPrIntegration` | Predefined replies → TicketsPR | `/predefined-replies` → `tickets_pr` table |
|
|
122
|
+
| `isEnabledToga2TicketTeamsToTogadeskGroupsIntegration` | Ticket teams → Groups | `/ticket-teams` → `groups` table |
|
|
123
|
+
| `isEnabledToga2TicketCategoriesToTogadeskCategorySubcategoryItemsIntegration` | Categories → custom field | Three-level tree flattened to comma-separated string, stored in TOGaDesk `tickets_customfields` |
|
|
124
|
+
|
|
125
|
+
## File sync helpers
|
|
126
|
+
|
|
127
|
+
### `syncToga2NoteFilesIntoTogadesk1(object $ticketNote, int $togadeskReplyId)`
|
|
128
|
+
Direction: **TOGA 2 → TOGaDesk**
|
|
129
|
+
|
|
130
|
+
1. `GET /ticket-notes/{uuid}?depth=5` → reads `ticketNoteFiles` array
|
|
131
|
+
2. `SELECT name FROM files WHERE ticketreplyid = $togadeskReplyId` — existing filename dedup
|
|
132
|
+
3. For each new file:
|
|
133
|
+
- `GET /files/{uuid}?depth=1` → `base64_decode($fileData->data->files->data)`
|
|
134
|
+
- INSERT `App_Model_TogaDesk_File` (ticketreplyid, name) → get `$fileId`
|
|
135
|
+
- Write to disk: `/var/www/html/ontrack/desk/uploads/{fileId}-{name}`
|
|
136
|
+
- `UPDATE files SET file='{fileId}-{name}', filetype=?, filesize=? WHERE id=?`
|
|
137
|
+
|
|
138
|
+
### `syncTogadesk1NoteFilesIntoToga2(int $togadeskReplyId, string $ticketNoteUuid)`
|
|
139
|
+
Direction: **TOGaDesk → TOGA 2**
|
|
140
|
+
|
|
141
|
+
1. `SELECT id, name, file FROM files WHERE ticketreplyid = $togadeskReplyId`
|
|
142
|
+
2. `GET /ticket-notes/{uuid}?depth=5` → reads `ticketNoteFiles` — existing filename dedup
|
|
143
|
+
3. For each new file:
|
|
144
|
+
- Read from disk: `/var/www/html/ontrack/desk/uploads/{file}`
|
|
145
|
+
- `POST /files` with base64-encoded data → get `$fileUuid`
|
|
146
|
+
- `POST /ticket-notes/{uuid}/ticket-note-files` with `{file: {uuid: $fileUuid}}`
|
|
147
|
+
|
|
148
|
+
### `syncFreshserviceConversationAttachmentsIntoToga2(...)`
|
|
149
|
+
Direction: **Freshservice → TOGA 2** — see worker2 doc.
|
|
150
|
+
|
|
151
|
+
## TOGA 2 API — critical gotchas
|
|
152
|
+
|
|
153
|
+
**Response key is `ticketNoteFiles`, not `ticketNotesFiles`.**
|
|
154
|
+
The extra `s` causes a silent empty array — dedup never fires, files re-upload every run.
|
|
155
|
+
|
|
156
|
+
**`GET /ticket-notes/{uuid}/ticket-note-files` is NOT a list endpoint.**
|
|
157
|
+
`childPolicy = MATCH`: 0 records → 404 EV-6, 2+ records → 404 EV-14.
|
|
158
|
+
Always use `depth=5` on the parent note for listing.
|
|
159
|
+
|
|
160
|
+
**`throwExceptionOnApiError` (8th param of `App_Api_Toga2::send()`).**
|
|
161
|
+
Pass `true` for the dedup GET — errors must not silently empty the dedup array.
|
|
162
|
+
|
|
163
|
+
## Error handling
|
|
164
|
+
|
|
165
|
+
Each ticket sync is wrapped in `try/catch` — exceptions are sent to Sentry and the run
|
|
166
|
+
continues with the next ticket. A single bad ticket does not abort the whole sync.
|
|
@@ -5,3 +5,4 @@
|
|
|
5
5
|
| [Worker (worker2) Architecture](architecture.md) | Worker (repo `worker2`) is an AWS Elastic Beanstalk **Worker Tier** application that processes background jobs. | worker2/Controller/Index.php, worker2/Worker/, worker2/LambdaFunctions/, _underscore/Worker.php |
|
|
6
6
|
| [ClickUp Project & Opportunity Multi-List Routing](features/clickup-project-routing.md) | Routes ClickUp tasks into the correct **secondary multi-list memberships** based on their custom-field values, via the `clickup` webhook. | worker2/Worker/Clickup/Project.php, worker2/Worker/Clickup.php |
|
|
7
7
|
| [Creating Worker Actions](features/creating-worker-actions.md) | How to add a new callable Worker action — a PHP class whose `public static` methods are invoked as background jobs (via webhook, cron, or `_Worker::runTask()`). | worker2/Worker/, worker2/Controller/Index.php, _underscore/Worker.php |
|
|
8
|
+
| [Elite Freshservice Sync (worker2)](features/elite-freshservice-sync.md) | `_Worker_Elite` processes Freshservice webhook events and syncs them into TOGA 2. | worker2/Worker/Elite.php, worker2/Config/dev-kmaramreddy-laptop.ini |
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Elite Freshservice Sync (worker2)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: worker2
|
|
5
|
+
project: Worker
|
|
6
|
+
client: elite
|
|
7
|
+
type: client-feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-10
|
|
10
|
+
owners: []
|
|
11
|
+
files:
|
|
12
|
+
- worker2/Worker/Elite.php
|
|
13
|
+
- worker2/Config/dev-kmaramreddy-laptop.ini
|
|
14
|
+
related:
|
|
15
|
+
- clients/elite/profile.md
|
|
16
|
+
- 1.0/apps/library/features/elite-freshservice-sync.md
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Summary
|
|
20
|
+
|
|
21
|
+
`_Worker_Elite` processes Freshservice webhook events and syncs them into TOGA 2.
|
|
22
|
+
Dispatched via action `Elite/Webhook` on the worker queue.
|
|
23
|
+
|
|
24
|
+
## Key files / entry points
|
|
25
|
+
|
|
26
|
+
- `Worker/Elite.php` — `_Worker_Elite` class, all sync logic lives here
|
|
27
|
+
- `_Component_Api_Elite` (`_underscore`) — authenticated Freshservice HTTP client
|
|
28
|
+
- `_Component_Api_Toga` (`_underscore`) — authenticated TOGA 2 HTTP client
|
|
29
|
+
|
|
30
|
+
## How the webhook flow works
|
|
31
|
+
|
|
32
|
+
1. Freshservice fires a webhook to `webhook.togahub.com/elite`
|
|
33
|
+
2. Lambda inserts a `WorkerJobs` row with action `Elite/Webhook`
|
|
34
|
+
3. Worker picks up the job → calls `_Worker_Elite::Webhook($payload, $headers)`
|
|
35
|
+
4. Worker syncs the ticket and all conversations into TOGA 2
|
|
36
|
+
|
|
37
|
+
## Constants
|
|
38
|
+
|
|
39
|
+
```php
|
|
40
|
+
const CLIENT_UUID_ELITE = '97627e9d-605f-3ded-9e83-97a4ec7a0c15';
|
|
41
|
+
const API_UUID_ELITE = 'ee08cf3d-caee-462d-adb5-acc5c36b1f01';
|
|
42
|
+
const API_SECRET_ELITE = 'c2701d21-1908-4895-aaed-2e6456710e8d';
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Freshservice API — gotchas
|
|
46
|
+
|
|
47
|
+
**No single-conversation GET endpoint.**
|
|
48
|
+
`GET /api/v2/conversations/{id}` only supports PATCH/PUT/DELETE — it returns 405 on GET.
|
|
49
|
+
To fetch a specific conversation, retrieve the full list for the ticket:
|
|
50
|
+
|
|
51
|
+
```php
|
|
52
|
+
$conversations = _Component_Api_Elite::send('GET', '/tickets/' . $ticketId . '/conversations', null);
|
|
53
|
+
foreach ($conversations->conversations as $conv) {
|
|
54
|
+
if ((string) $conv->id === $targetId) { ... }
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Attachment URLs are presigned S3 URLs — they expire.**
|
|
59
|
+
Call the attachment sync method immediately at webhook time. Do not queue the download.
|
|
60
|
+
|
|
61
|
+
**Attachment shape on a conversation object:**
|
|
62
|
+
```
|
|
63
|
+
$attachment->name
|
|
64
|
+
$attachment->content_type
|
|
65
|
+
$attachment->size
|
|
66
|
+
$attachment->attachment_url // presigned S3 URL
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## TOGA 2 API — gotchas
|
|
70
|
+
|
|
71
|
+
**`childPolicy = MATCH` nested routes are get-ONE, not a list.**
|
|
72
|
+
`GET /ticket-notes/{uuid}/ticket-note-files` returns:
|
|
73
|
+
- 404 EV-6 when 0 bridge records exist
|
|
74
|
+
- 200 object when exactly 1 exists
|
|
75
|
+
- 404 EV-14 ("multiple found, expected one") when 2+ exist
|
|
76
|
+
|
|
77
|
+
**Never use this route for listing or dedup.** Use `depth=5` on the parent note instead:
|
|
78
|
+
|
|
79
|
+
```php
|
|
80
|
+
$togaNote = _Component_Api_Toga::send(..., 'GET', '/ticket-notes/' . $uuid, null, ['depth' => 5], true, $endpoint);
|
|
81
|
+
$existingFileNames = [];
|
|
82
|
+
foreach (($togaNote->data->ticketNotes->ticketNoteFiles ?? []) as $link) {
|
|
83
|
+
if (!empty($link->file->name)) {
|
|
84
|
+
$existingFileNames[] = $link->file->name;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Response key is `ticketNoteFiles` (not `ticketNotesFiles` — no `s` before `Files`).
|
|
90
|
+
|
|
91
|
+
## Local dev setup
|
|
92
|
+
|
|
93
|
+
To test worker2 Elite code locally against production TOGA 2 and Freshservice:
|
|
94
|
+
|
|
95
|
+
1. **Dev config** — `[api]` section needs both keys (`endpoint` for some callers,
|
|
96
|
+
`_` for `_Config::api('_')`):
|
|
97
|
+
```ini
|
|
98
|
+
[api]
|
|
99
|
+
_ = "https://api.togahub.com/v2"
|
|
100
|
+
endpoint = "https://api.togahub.com/v2"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
2. **`ClientLogs` database** must exist locally — the framework logs every API call there:
|
|
104
|
+
```sql
|
|
105
|
+
CREATE DATABASE IF NOT EXISTS ClientLogs CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
|
106
|
+
USE ClientLogs;
|
|
107
|
+
CREATE TABLE `Api` (
|
|
108
|
+
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
109
|
+
`uuid` CHAR(36) NOT NULL,
|
|
110
|
+
`dtStamp` DATETIME NOT NULL,
|
|
111
|
+
`transactionId` VARCHAR(64) NULL,
|
|
112
|
+
`apiId` INT UNSIGNED NULL,
|
|
113
|
+
`userId` INT UNSIGNED NULL,
|
|
114
|
+
`isAuthRequest` TINYINT UNSIGNED NOT NULL DEFAULT 0,
|
|
115
|
+
`direction` ENUM('IN','OUT') NOT NULL,
|
|
116
|
+
`source` VARCHAR(255) NULL,
|
|
117
|
+
`sourceIp` VARCHAR(45) NULL,
|
|
118
|
+
`instanceId` VARCHAR(128) NULL,
|
|
119
|
+
`executionTimeSeconds` DECIMAL(10,4) NULL,
|
|
120
|
+
`method` VARCHAR(10) NOT NULL,
|
|
121
|
+
`hostname` VARCHAR(255) NOT NULL,
|
|
122
|
+
`route` VARCHAR(1024) NOT NULL,
|
|
123
|
+
`responseCode` SMALLINT UNSIGNED NULL,
|
|
124
|
+
`queryString` TEXT NULL,
|
|
125
|
+
`requestHeaders` TEXT NULL,
|
|
126
|
+
`requestPayload` MEDIUMTEXT NULL,
|
|
127
|
+
`responseHeaders` TEXT NULL,
|
|
128
|
+
`responsePayload` MEDIUMTEXT NULL
|
|
129
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
3. **Bootstrap** for a standalone test script:
|
|
133
|
+
```php
|
|
134
|
+
chdir(__DIR__);
|
|
135
|
+
putenv('ENVIRONMENT=dev-kmaramreddy-laptop');
|
|
136
|
+
$_ENV['ENVIRONMENT'] = 'dev-kmaramreddy-laptop';
|
|
137
|
+
require_once 'vendor/autoload.php';
|
|
138
|
+
require '_underscore.php';
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
4. **PHP version** — worker2 vendor targets PHP >= 8.5. Local PHP 8.1 requires a
|
|
142
|
+
temporary bypass in `vendor/composer/platform_check.php` — revert before committing.
|
package/knowledge/INDEX.md
CHANGED
|
@@ -4,13 +4,13 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
4
4
|
|
|
5
5
|
## 1.0 framework
|
|
6
6
|
|
|
7
|
-
- **library** (Library) _(framework core)_ —
|
|
7
|
+
- **library** (Library) _(framework core)_ — 2 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
8
|
- **worker** (Worker) — 1 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
|
|
9
9
|
|
|
10
10
|
## 2.0 framework
|
|
11
11
|
|
|
12
12
|
- **_underscore** (_Underscore) _(framework core)_ — 3 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
13
|
-
- **worker2** (Worker) —
|
|
13
|
+
- **worker2** (Worker) — 4 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
14
14
|
- **api2** (API) — 1 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
15
15
|
- **dbchanges2** (Database Changes) _(framework core)_ — 1 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
16
16
|
- **toga2-supply** (TOGa Supply) — 2 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
@@ -19,4 +19,5 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
19
19
|
|
|
20
20
|
- **compass-canada** → [clients/compass-canada/INDEX.md](clients/compass-canada/INDEX.md)
|
|
21
21
|
- **compass-usa** → [clients/compass-usa/INDEX.md](clients/compass-usa/INDEX.md)
|
|
22
|
+
- **elite** → [clients/elite/INDEX.md](clients/elite/INDEX.md)
|
|
22
23
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Client: elite
|
|
2
|
+
|
|
3
|
+
| Doc | Framework | Summary | Files |
|
|
4
|
+
|-----|-----------|---------|-------|
|
|
5
|
+
| [Elite Client Profile](profile.md) | 2.0 | Elite is a managed-services client that uses **Freshservice** as their helpdesk platform. | worker2/Worker/Elite.php, library/app/api/toga2.php |
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Elite Client Profile
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
project: Worker
|
|
5
|
+
client: elite
|
|
6
|
+
type: client-feature
|
|
7
|
+
status: active
|
|
8
|
+
updated: 2026-06-10
|
|
9
|
+
owners: []
|
|
10
|
+
files:
|
|
11
|
+
- worker2/Worker/Elite.php
|
|
12
|
+
- library/app/api/toga2.php
|
|
13
|
+
related:
|
|
14
|
+
- 2.0/apps/worker2/features/elite-freshservice-sync.md
|
|
15
|
+
- 1.0/apps/library/features/elite-freshservice-sync.md
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Summary
|
|
19
|
+
|
|
20
|
+
Elite is a managed-services client that uses **Freshservice** as their helpdesk platform.
|
|
21
|
+
TOGA 2 is the source of truth for tickets, notes, and file attachments. The integration
|
|
22
|
+
keeps Freshservice and TOGA 2 in sync via webhook-driven worker jobs.
|
|
23
|
+
|
|
24
|
+
## TOGA 2 API credentials
|
|
25
|
+
|
|
26
|
+
Constants live in `worker2/Worker/Elite.php`:
|
|
27
|
+
|
|
28
|
+
```php
|
|
29
|
+
const CLIENT_UUID_ELITE = '97627e9d-605f-3ded-9e83-97a4ec7a0c15';
|
|
30
|
+
const API_UUID_ELITE = 'ee08cf3d-caee-462d-adb5-acc5c36b1f01';
|
|
31
|
+
const API_SECRET_ELITE = 'c2701d21-1908-4895-aaed-2e6456710e8d';
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The library version of the sync methods (`library/app/api/toga2.php`) passes these in as
|
|
35
|
+
parameters — they are not constants there.
|
|
36
|
+
|
|
37
|
+
## Freshservice instance
|
|
38
|
+
|
|
39
|
+
- Base URL: `https://eliteservicedesk.freshservice.com`
|
|
40
|
+
- Configured in `_underscore` framework via `_Component_Api_Elite`
|
|
41
|
+
- API logs written to the `ClientLogs` database (`ClientLogs.Api` table) — this DB must
|
|
42
|
+
exist locally when running worker2 scripts against production Freshservice
|
|
43
|
+
|
|
44
|
+
## Key custom fields
|
|
45
|
+
|
|
46
|
+
| TOGA 2 field | Purpose |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `c_eliteTicketId` | Freshservice ticket numeric ID (on `Tickets`) |
|
|
49
|
+
| `c_eliteConversationId` | Freshservice conversation ID (on `TicketNotes`) |
|
|
50
|
+
| `c_eliteUserId` | Freshservice user ID (on `Contacts`) |
|
|
51
|
+
| `c_eliteCategoryId` | Freshservice category ID (on `TicketCategories`) |
|
|
52
|
+
| `c_eliteGroupId` | Freshservice group ID (on `TicketTeams`) |
|
|
53
|
+
|
|
54
|
+
## Systems involved
|
|
55
|
+
|
|
56
|
+
| System | Role |
|
|
57
|
+
|---|---|
|
|
58
|
+
| Freshservice | Client helpdesk — source of ticket/conversation events |
|
|
59
|
+
| TOGA 2 (`api2`) | System of record — tickets, notes, contacts, files |
|
|
60
|
+
| worker2 `_Worker_Elite` | Webhook processor — syncs Freshservice events into TOGA 2 |
|
|
61
|
+
| library `App_Api_Toga2` | Shared sync helpers (note-file bidirectional sync) |
|
package/package.json
CHANGED