toga-ai 1.0.459 → 1.0.461

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.
@@ -5,7 +5,7 @@
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
6
  | [App_Sso — Reusable 1.0 SSO Initiation (SP-initiated SAML via saml.togahub.com)](features/app-sso-initiation.md) | `App_Sso` (`library/app/sso.php`) is the **1.0 port of the 2.0 SAML gateway's SP-initiated SSO initiation**, packaged as a reusable, framework-level capability | library/app/sso.php, library/sso/togahub_private_key.key |
7
7
  | [Cron Execution Monitoring (App_Framework check-in/out → CronJobExecutions)](features/cron-execution-monitoring.md) | `App_Framework::cronInitialization()` / `App_Framework::cronFinished()` (in `library/app/framework.php`) give every 1.0 (`App_`) cron job a check-in/check-out l | library/app/framework.php |
8
- | [Diagnostic Dialog — View Recommended Services Routing](features/diagnostic-dialog-view-recommended-services.md) | `App_Model_Toga_Diagnostic::initializeDiagnosticDialog()` renders the device modal used across all TOGa service request views. | library/app/model/toga/diagnostic.php |
8
+ | [Diagnostic Dialog — View Recommended Services Routing](features/diagnostic-dialog-view-recommended-services.md) | Two "View Recommended Services" buttons exist in the TOGa Refresh 2026 SR view: 1. | library/app/model/toga/diagnostic.php, library/app/model/servicerequest.php |
9
9
  | [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 |
10
10
  | [Branded HTML Email Templates (App_Email_Template)](features/email-templates.md) | `App_Email_Template` (`app/email/template.php`) is the base class for branded HTML emails in the 1.0 (`App_`) framework. | library/app/email/template.php, library/app/email/agilant.php |
11
11
  | [1.0 MVC Page Pattern & New-App Skeleton](features/mvc-page-pattern-and-app-skeleton.md) | This is the **reusable recipe for standing up a new 1.0 (`App_`) application** and for adding pages to one — the folder-based MVC routing, the page lifecycle, t | library/app/framework.php, library/app/frameworkindex.php, library/app/mvc.php, library/app/database.php, library/app/model.php, library/app/config.php |
@@ -6,44 +6,103 @@ project: Library
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-23
9
+ updated: 2026-07-28
10
10
  owners: [snaredla]
11
11
  files:
12
12
  - library/app/model/toga/diagnostic.php
13
+ - library/app/model/servicerequest.php
13
14
  related:
14
15
  - ../../../clients/office-depot/features/togarefresh2026-linkbuilder-routing.md
15
16
  ---
16
17
 
17
18
  ## Summary
18
- `App_Model_Toga_Diagnostic::initializeDiagnosticDialog()` renders the device modal
19
- used across all TOGa service request views. It includes a "View Recommended Services"
20
- button whose URL depends on whether the store is a TOGaRefresh 2026 store and whether
21
- the diagnostic passed or failed.
19
+ Two "View Recommended Services" buttons exist in the TOGa Refresh 2026 SR view:
20
+ 1. **Static button** rendered on page load inside `view.php`, always visible when a
21
+ diagnostic has been completed on the SR.
22
+ 2. **Device dialog button** — inside the device click modal (`dialogDevice`), shown only
23
+ when that device has a completed diagnostic.
24
+
25
+ Both buttons route to either `serviceselection` (repair + subscription) or `techsupport`,
26
+ based on `resultCode` of the latest completed diagnostic. The routing logic is
27
+ **per-device** — each device's latest result is evaluated independently.
28
+
29
+ ## Key files / entry points
30
+ - `library/app/model/toga/diagnostic.php` — `initializeDiagnosticDialog()`: renders
31
+ `dialogDevice` dialog; device dialog button uses a JS global `_deviceBuilderUrl` set
32
+ dynamically per device click.
33
+ - `library/app/model/servicerequest.php` — `ajaxLoadDeviceDialog($deviceId,
34
+ $serviceRequestId)`: computes per-device routing and returns `builderUrl` in the AJAX
35
+ response.
36
+ - `toga/app/togarefresh2026/servicerequests/view.php` — static button routing block
37
+ (search for `LatestPerDevice`).
22
38
 
23
39
  ## How it works
24
- 1. Checks `$_SESSION['isTogaRefresh2023']` or `$_SESSION['isTogaRefresh2026']` to
25
- determine if this is a refresh store.
26
- 2. For refresh stores, casts `$serviceRequestId` to `(int)` then queries `Diagnostics`
27
- for the latest `pass` value for the service request.
28
- 3. Routes:
29
- - **Diagnostic pass** (`pass >= 1`) → `?content=togarefresh2026_servicerequests_techsupport&serviceRequestId=...`
30
- - **Diagnostic fail / no diagnostic** (`pass < 1`) → `?content=togarefresh2026_servicerequests_serviceselection&jobJacketId=1&showServices=195,101899&serviceRequestId=...`
31
- 4. For non-refresh stores, falls back to `App_Model_ServiceRequest::linkBuilder()`.
40
+
41
+ ### Static button (view.php)
42
+ Runs at page load. Queries the latest completed diagnostic **per device** on the SR using
43
+ a `MAX(id) GROUP BY deviceId` subquery. If **any** device's latest result is a
44
+ software/virus problem code → routes to repair + subscription. Otherwise → tech support.
45
+
46
+ SQL filter: `serviceRequestId = X AND status = 'COMPLETED' AND pass > 0`
47
+
48
+ ### Device dialog button (diagnostic.php + ajaxLoadDeviceDialog)
49
+ `initializeDiagnosticDialog` pre-renders the button with `window.location.href =
50
+ _deviceBuilderUrl`. When the user clicks a device, `loadDeviceDialog(deviceId)` fires
51
+ an AJAX call passing both `deviceId` and `serviceRequestId`. `ajaxLoadDeviceDialog`
52
+ queries the latest diagnostic for **that specific device** on **that specific SR**
53
+ (`deviceId + serviceRequestId + status='COMPLETED' + pass>0 ORDER BY id DESC LIMIT 1`),
54
+ checks `resultCode`, and returns `builderUrl` in the response. The JS
55
+ `loadDeviceDialogResponse` sets `_deviceBuilderUrl = resp['builderUrl']`.
56
+
57
+ ## Result codes that trigger repair routing
58
+
59
+ ### BITSTORM exact-match codes
60
+ ```
61
+ 'Has A/V, Software problem(s) found.'
62
+ 'No A/V, Software problem(s) found.'
63
+ 'No A/V, Quarantined item(s) found.'
64
+ 'Has A/V, Quarantined item(s) found.'
65
+ 'No A/V, Quarantined item(s) found, PUPS found.'
66
+ 'Has A/V, Quarantined item(s) found, PUPS found.'
67
+ 'Has A/V, PUP(s) found.'
68
+ 'No A/V, PUP(s) found.'
69
+ ```
70
+
71
+ ### STARTECH dynamic patterns (strpos)
72
+ - `'antivirus and firewall status failed'` — number of registry entries varies
73
+ - `'potentially unwanted programs'` — number varies
32
74
 
33
75
  ## Data model
34
76
  - Table: `Diagnostics` (db_toga)
35
- - Field read: `pass` (TINYINT boolean — 1 = passed, 0 = failed)
36
- - Query: latest record by `id DESC LIMIT 1` for the given `serviceRequestId`
77
+ - Fields read: `resultCode` (VARCHAR), `deviceId` (INT), `serviceRequestId` (INT),
78
+ `status` (VARCHAR = 'COMPLETED'), `pass` (TINYINT > 0)
37
79
 
38
80
  ## Gotchas / known issues
39
- - `$serviceRequestId` is cast to `(int)` before SQL interpolation do not remove this cast (SQL injection prevention).
40
- - If no diagnostic record exists, `$diagnosticPass` defaults to `0` (fail path) — shows service selection with `showServices=195,101899`.
41
- - The same pass/fail routing logic exists in `toga/app/togarefresh2026/servicerequests/view.php` (lines 2358–2381) — keep both in sync.
42
- - `isTogaRefresh2023` is kept alongside `isTogaRefresh2026` for backward compatibility do not remove it.
43
- - `$_SESSION['isTogaRefresh2026']` is set in `toga/_/app/frameworkindex.php` and `toga/app/home/action.php` at login time.
81
+ - **`pass=1` does NOT mean "no problems found"**it means the scan ran successfully.
82
+ "No A/V, Software problem(s) found." has `pass=1`. Never use `pass` alone for routing;
83
+ always check `resultCode`.
84
+ - **Multi-device SRs**: if a second device is scanned clean after a first device showed
85
+ problems, a naive `ORDER BY id DESC LIMIT 1` across all devices picks the clean result
86
+ and hides repair. The static button uses `MAX(id) GROUP BY deviceId` (LatestPerDevice
87
+ subquery); the dialog button filters by `deviceId + serviceRequestId` directly.
88
+ - **`_deviceBuilderUrl` is a page-global JS var** — set fresh each time a device dialog
89
+ opens. If the dialog is opened, closed, and a different device opened, the var is
90
+ overwritten correctly.
91
+ - `$serviceRequestId` is cast to `(int)` in both PHP callers before SQL interpolation —
92
+ do not remove this cast.
93
+ - `isTogaRefresh2023` is kept alongside `isTogaRefresh2026` in the session check — do not
94
+ remove it.
95
+ - The same `$softwareVirusResultCodes` array and `strpos` checks appear in three places
96
+ (view.php, diagnostic.php, servicerequest.php) — keep all three in sync when adding new
97
+ result codes.
44
98
 
45
99
  ## Related docs
46
100
  - `clients/office-depot/features/togarefresh2026-linkbuilder-routing.md`
47
101
 
48
102
  ## Change history
49
- - 2026-06-23Initial doc. Diagnostic modal now routes pass→techsupport, fail→serviceselection with showServices=195,101899. (snaredla)
103
+ - 2026-07-28Replaced pass-based routing with resultCode-based per-device routing.
104
+ Static button uses LatestPerDevice subquery; device dialog button is now dynamic via
105
+ _deviceBuilderUrl JS global. ajaxLoadDeviceDialog now accepts serviceRequestId and
106
+ returns builderUrl. (snaredla)
107
+ - 2026-06-23 — Initial doc. Diagnostic modal routes pass→techsupport,
108
+ fail→serviceselection with showServices=195,101899. (snaredla)
@@ -6,13 +6,14 @@ project: Library
6
6
  client: elite
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-07-10
9
+ updated: 2026-07-28
10
10
  owners: ["snaredla"]
11
11
  files:
12
12
  - library/app/api/toga2.php
13
13
  related:
14
14
  - clients/elite/profile.md
15
15
  - 2.0/apps/worker2/features/elite-freshservice-sync.md
16
+ - 1.0/apps/worker/features/elite-togadesk-attachment-sync.md
16
17
  ---
17
18
 
18
19
  ## Summary
@@ -135,6 +136,12 @@ Direction: **TOGA 2 → TOGaDesk**
135
136
  - Write to disk: `/var/www/html/ontrack/desk/uploads/{fileId}-{name}`
136
137
  - `UPDATE files SET file='{fileId}-{name}', filetype=?, filesize=? WHERE id=?`
137
138
 
139
+ ⚠ **`data.files.data` is null for Freshservice-originated files.** Files that entered
140
+ TOGA 2 via the inbound Freshservice sync store their binary in S3 and expose a `sourceUrl`
141
+ (presigned URL) instead of a base64 `data` field. This method silently skips those files.
142
+ For Elite, a separate standalone cron handles them:
143
+ → see [`1.0/apps/worker/features/elite-togadesk-attachment-sync.md`](../../../1.0/apps/worker/features/elite-togadesk-attachment-sync.md)
144
+
138
145
  ### `syncTogadesk1NoteFilesIntoToga2(int $togadeskReplyId, string $ticketNoteUuid)`
139
146
  Direction: **TOGaDesk → TOGA 2**
140
147
 
@@ -211,3 +218,4 @@ The Elite team lookup block inside `syncTogaDesk1TicketIntoToga2Ticket` must use
211
218
  ## Change history
212
219
  - 2026-06-10 — Documented `App_Api_Toga2::syncWithTogadesk` orchestration (bidirectional TOGA 2 ↔ TOGaDesk ticket/note/file sync, watermarks, `ticketNoteFiles` gotcha). (snaredla)
213
220
  - 2026-07-10 — Added Elite-specific section: department reverse-lookup via `c_togadeskDepartmentId`, timestamp guard, reply status badge mapping fix (`case 2` → Open), variable pollution gotcha. (snaredla)
221
+ - 2026-07-28 — Added gotcha to `syncToga2NoteFilesIntoTogadesk1`: `data.files.data` is null for Freshservice-originated files (sourceUrl approach handled by standalone cron). Added cross-link to `elite-togadesk-attachment-sync.md`. (snaredla)
@@ -5,6 +5,7 @@
5
5
  | [Worker (1.0 Framework) Architecture](architecture.md) | `worker` is the legacy (**1.0** `App_` framework) **background-job tier**. | worker/index.php, worker/_/app/framework.php, worker/crons/, worker/schedules/, worker/ebs/cron.worker.php, worker/.ebextensions/035_cron.worker.config |
6
6
  | [Compass MA Sales Order Exception Report](features/compass-ma-sales-order-exception-report.md) | A worker cron that emails operations the "Compass Refresh Exception Report" — Compass `MA%` sales orders whose corresponding Office Depot (ODP) sales order has | worker/crons/toga2/compass/workflow/7_generate_ma_sales_order_exception_report.php |
7
7
  | [Compass Partial In-Transit & Delivered Emails (per package)](features/compass-partial-in-transit-delivered-emails.md) | Compass USA and Compass Canada send a **per-package** in-transit email (and a matching delivered email) instead of one email listing the whole order. | worker/crons/toga2/compass/update_salesorder_status_from_odp.php, worker/crons/toga2/compasscanada/workflow/3_update_salesorder_status_from_grand_and_toy.php |
8
+ | [Elite TOGA 2.0 → TOGaDeskSupport Standalone Attachment Sync](features/elite-togadesk-attachment-sync.md) | `sync_togadesk_elite_attachments.php` is a standalone cron (every 5 minutes) that syncs file attachments from TOGA 2.0 into TOGaDeskSupport for Elite. | worker/crons/toga2/elite/sync_togadesk_elite_attachments.php, worker/crons/toga2/elite/test_sync_togadesk_elite_attachments.php |
8
9
  | [Forecast2 ↔ NetSuite Reconciliation & Trueup Tooling](features/forecast2-netsuite-reconciliation.md) | CLI tools to **audit** and **repair** drift between the production `Forecast` DB (core2) and NetSuite. | test/@dave/checker.php, worker2/Component/Forecast/SaleImport/SaleImport.php, test/@dave/looper.php, test/@dave/reconcile_netsuite_totals.php, test/@dave/fixer.php, test/@dave/analyze_netsuite_forecast_diff.php, test/@dave/trueup_sales.php, test/@dave/reconcile_drift_2023plus.php, test/@dave/probe_invoice_gap_2026.php, test/@dave/probe_creditmemo_gap_detail.php, test/@dave/trueup_open_orders.php, test/@dave/loop_trueup_open_orders.php, test/@dave/trueup_opportunities.php, test/@dave/probe_sales_gap_direct.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, test/@dave/probe_profit_invoices.php, test/@dave/probe_profit_gap.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php, worker/crons/toga2/forecast2/periodic_forecast_discrepancy_fix_open_orders.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/schedules/cron.worker.infrastructure.json |
9
10
  | [NetSuite → TOGa Supply Per-Client Sync (thin wrappers)](features/netsuite-togasupply-per-client-sync.md) | Syncs NetSuite transactions (sales orders, purchase orders, invoices, item receipts, item fulfillments, inventory adjustments) into each TOGa Supply (2.0) clien | worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/sync_togasupply_canon.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql, library/app/api/netsuite/rest.php, library/app/systemmonitor/netsuiteintegration.php |
10
11
  | [OneUptime external uptime monitoring for 1.0 workers](features/oneuptime-worker-uptime-monitoring.md) | Every 1.0 worker box self-reports its liveness to an external OneUptime monitor once per minute by curl-POSTing to a per-worker "Incoming Request" heartbeat URL | library/app/worker.php, worker/crons/worker/worker_heartbeat.php |
@@ -0,0 +1,143 @@
1
+ ---
2
+ title: Elite TOGA 2.0 → TOGaDeskSupport Standalone Attachment Sync
3
+ framework: "1.0"
4
+ repo: worker
5
+ project: Worker
6
+ client: elite
7
+ type: client-feature
8
+ status: active
9
+ updated: 2026-07-28
10
+ owners: ["snaredla"]
11
+ files:
12
+ - worker/crons/toga2/elite/sync_togadesk_elite_attachments.php
13
+ - worker/crons/toga2/elite/test_sync_togadesk_elite_attachments.php
14
+ related:
15
+ - clients/elite/profile.md
16
+ - 1.0/apps/library/features/elite-freshservice-sync.md
17
+ - 2.0/apps/worker2/features/elite-freshservice-sync.md
18
+ ---
19
+
20
+ ## Summary
21
+
22
+ `sync_togadesk_elite_attachments.php` is a standalone cron (every 5 minutes) that syncs
23
+ file attachments from TOGA 2.0 into TOGaDeskSupport for Elite. It supplements — but does
24
+ not replace — the `syncWithTogadesk()` orchestration in `library/app/api/toga2.php`.
25
+
26
+ **Why this exists**: `syncWithTogadesk()` has a built-in helper `syncToga2NoteFilesIntoTogadesk1()`
27
+ that reads `data.files.data` (base64-encoded file content) from the TOGA 2.0 `/files/{uuid}`
28
+ endpoint. For files that originated in Freshservice, this field is `null` — the file binary
29
+ lives in S3 and is accessed via `sourceUrl` (a presigned URL). This standalone cron handles
30
+ that path: it downloads from `sourceUrl` directly via cURL and writes the binary to disk.
31
+
32
+ ## Key files / entry points
33
+
34
+ | File | Purpose |
35
+ |------|---------|
36
+ | `worker/crons/toga2/elite/sync_togadesk_elite_attachments.php` | Production cron — runs every 5 min |
37
+ | `worker/crons/toga2/elite/test_sync_togadesk_elite_attachments.php` | 7-section test script |
38
+ | `worker/crons/toga2/elite/sync_togasupply_elite.php` | Prerequisite — must run first (creates tickets + tickets_replies rows) |
39
+
40
+ ## How it works
41
+
42
+ 1. Query TOGaDeskSupport for recently active Elite tickets that have a `referenceId` (TOGA2 UUID):
43
+ ```sql
44
+ SELECT DISTINCT t.id AS deskTicketId, t.referenceId AS toga2Uuid
45
+ FROM tickets t
46
+ INNER JOIN tickets_replies tr ON tr.ticketid = t.id
47
+ WHERE t.clientid = 163
48
+ AND t.referenceId IS NOT NULL
49
+ AND tr.timestamp >= '<7 days ago>'
50
+ LIMIT 200
51
+ ```
52
+
53
+ 2. For each ticket: `GET /tickets/{toga2Uuid}?depth=3` to fetch the ticket with embedded notes and `ticketNoteFiles`.
54
+
55
+ 3. Walk every `isExternal = true` note that has `ticketNoteFiles`.
56
+
57
+ 4. Match each note to the TOGaDeskSupport `tickets_replies` row via `referenceid = note.uuid` (the dedup join key set by `syncWithTogadesk()`).
58
+
59
+ 5. For each `ticketNoteFile` (JOIN record), resolve the actual file UUID:
60
+ - `GET /ticket-note-files/{tnfUuid}` → `data.ticketNoteFiles.file.uuid`
61
+ - **Note**: `ticketNoteFiles[].uuid` in a depth-3 ticket is the JOIN RECORD uuid, not the file uuid. Must call `/ticket-note-files/{tnfUuid}` to get the real file object.
62
+
63
+ 6. Dedup: `SELECT id FROM files WHERE ticketreplyid = $replyId AND name = '$fileName'` — skip if already saved.
64
+
65
+ 7. Get download URL: `GET /files/{fileUuid}` → `data.files.sourceUrl` (Freshservice S3 presigned URL stored at inbound sync time).
66
+
67
+ 8. Download binary via cURL, write to `TOGADESK_UPLOADS_DIR` with a `{timestamp}_{filename}` prefix.
68
+
69
+ 9. `INSERT INTO files SET clientid=163, ticketreplyid=$replyId, name='...', file='...', projectid=0, assetid=0`.
70
+
71
+ ## Key constants
72
+
73
+ ```php
74
+ const TOGADESK_ELITE_CLIENT_ID = 163;
75
+ const TOGADESK_UPLOADS_DIR = '/var/www/html/ontrack/desk/uploads/';
76
+ const SYNC_LOOKBACK_DAYS = 7;
77
+ const TOGA2_TICKET_DEPTH = 3;
78
+ ```
79
+
80
+ ## Data model
81
+
82
+ TOGaDeskSupport tables touched:
83
+
84
+ | Table | Columns read | Columns written |
85
+ |-------|-------------|-----------------|
86
+ | `tickets` | `id`, `referenceId`, `clientid` | — |
87
+ | `tickets_replies` | `id`, `ticketid`, `referenceid` | — |
88
+ | `files` | `id` (dedup check) | `clientid`, `ticketreplyid`, `name`, `file`, `projectid`, `assetid` |
89
+
90
+ TOGA 2.0 API calls:
91
+ - `GET /tickets/{uuid}?depth=3` → `data.tickets.ticketNotes[].ticketNoteFiles[]`
92
+ - `GET /ticket-note-files/{tnfUuid}` → `data.ticketNoteFiles.file.{uuid,name}`
93
+ - `GET /files/{fileUuid}` → `data.files.{uuid,name,size,sourceUrl}`
94
+
95
+ ## Dedup mechanism
96
+
97
+ Two layers:
98
+ 1. **DB dedup**: `SELECT id FROM files WHERE ticketreplyid = ? AND name = ?` — skips files already saved for that reply.
99
+ 2. **URL dedup**: If `sourceUrl` is empty or expired (S3 presigned URLs have a TTL), the download is skipped with an `error_log` entry and the run continues.
100
+
101
+ ## Test script
102
+
103
+ `test_sync_togadesk_elite_attachments.php` — run on the worker EC2:
104
+
105
+ ```bash
106
+ # Read-only state check:
107
+ php test_sync_togadesk_elite_attachments.php
108
+
109
+ # Run sync then verify:
110
+ php test_sync_togadesk_elite_attachments.php --sync
111
+ ```
112
+
113
+ Sections:
114
+ 1. TOGA 2.0 note/file counts (confirms ticketNoteFiles exist)
115
+ 2. TOGaDeskSupport replies exist for test tickets (prereq check)
116
+ 3. Pre-sync file count (baseline)
117
+ 4. Run `sync_togadesk_elite_attachments.php` via `exec()`
118
+ 5. Post-sync file count reconciliation (`TOGaDeskSupport >= TOGA2`)
119
+ 6. Spot-check: `referenceid` set on replies, files on disk
120
+ 7. Dedup check: second run adds 0 files
121
+
122
+ **Test ticket IDs** (safe sandbox tickets):
123
+
124
+ | Label | Freshservice id | TOGaDeskSupport `tickets.id` | TOGA2 uuid |
125
+ |-------|----------------|------------------------------|------------|
126
+ | INC-65 | 65 | **213254** | `f4fac245-8bb2-8df4-dc1a-34dd28b67dc6` |
127
+ | INC-66 | 66 | **213267** | `550df55f-32c2-1fbb-4a59-e21c86588e93` |
128
+
129
+ The `tickets.id` values (213254/213267) are TOGaDeskSupport auto-increment IDs.
130
+ They are **not** the TOGA2 integer ticket IDs (33/34) — using those returns 0 rows.
131
+
132
+ ## Gotchas / known issues
133
+
134
+ **sourceUrl expiry**: S3 presigned URLs expire. For recently synced Freshservice files the URL stored in TOGA2 is still valid. For files synced weeks ago the URL may have expired — the script logs and skips those rather than failing.
135
+
136
+ **Depends on `sync_togasupply_elite.php` running first**: The `tickets_replies.referenceid` join key is set by `syncWithTogadesk()`. If that cron is delayed, this script finds no matching reply rows and silently skips those notes.
137
+
138
+ **`ticketNoteFiles[].uuid` is the JOIN RECORD uuid, not the file uuid**: Calling `GET /files/{tnfUuid}` returns 404. Must call `GET /ticket-note-files/{tnfUuid}` first to resolve to `data.ticketNoteFiles.file.uuid`.
139
+
140
+ **depth=3 ticket fetch**: `ticketNoteFiles` are embedded at depth=3. Depth=1 or depth=2 returns the notes but not the file links.
141
+
142
+ ## Change history
143
+ - 2026-07-28 — Created `sync_togadesk_elite_attachments.php` and test script. `sourceUrl` approach required because `data.files.data` is null for Freshservice-originated files. (snaredla)
@@ -5,7 +5,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
5
5
  ## 1.0 framework
6
6
 
7
7
  - **library** (Library) _(framework core)_ — 14 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
8
- - **worker** (Worker) — 15 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
8
+ - **worker** (Worker) — 16 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
9
9
  - **worker1.5** (Worker 1.5) — 0 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
10
10
  - **togadesk** (TOGa Desk) — 10 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
11
11
  - **togaview** (TOGa View) — 6 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
@@ -3,5 +3,5 @@
3
3
  | Doc | Framework | Summary | Files |
4
4
  |-----|-----------|---------|-------|
5
5
  | [ODP Tech Support Agent (Office Depot variant)](features/odp-tech-support.md) | 2.0 | ODP-specific customization of the shared TOGa Voice agent. | voice-to-voice/clients/odp/agent.py, voice-to-voice/clients/odp/agents/tech_support.py, voice-to-voice/clients/odp/prompts.py, voice-to-voice/clients/odp/config.yaml, voice-to-voice/clients/odp/.env.example |
6
- | [TOGaRefresh 2026 — linkBuilderRefresh URL Routing (ODP)](features/togarefresh2026-linkbuilder-routing.md) | 1.0 | In the TOGaRefresh 2026 UI, any button that navigates to the service builder must use `App_Model_ServiceRequest::linkBuilderRefresh()` (→ `togarefresh2026_servi | toga/app/togarefresh2026/servicerequests/review.php, toga/app/togarefresh2026/customers/view.php, library/app/model/toga/diagnostic.php |
6
+ | [TOGaRefresh 2026 — linkBuilderRefresh URL Routing (ODP)](features/togarefresh2026-linkbuilder-routing.md) | 1.0 | In the TOGaRefresh 2026 UI, any button that navigates to the service builder must use `App_Model_ServiceRequest::linkBuilderRefresh()` (→ `togarefresh2026_servi | toga/app/togarefresh2026/servicerequests/review.php, toga/app/togarefresh2026/customers/view.php, toga/app/togarefresh2026/servicerequests/view.php, library/app/model/toga/diagnostic.php |
7
7
  | [Office Depot](profile.md) | 2.0 | Office Depot is the first deployed tenant of the **TOGa Voice** (`voice-to-voice`) platform. | |
@@ -6,11 +6,12 @@ project: TOGa
6
6
  client: office-depot
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-06-23
9
+ updated: 2026-07-28
10
10
  owners: [snaredla]
11
11
  files:
12
12
  - toga/app/togarefresh2026/servicerequests/review.php
13
13
  - toga/app/togarefresh2026/customers/view.php
14
+ - toga/app/togarefresh2026/servicerequests/view.php
14
15
  - library/app/model/toga/diagnostic.php
15
16
  related:
16
17
  - ../profile.md
@@ -22,6 +23,11 @@ In the TOGaRefresh 2026 UI, any button that navigates to the service builder mus
22
23
  `App_Model_ServiceRequest::linkBuilderRefresh()` (→ `togarefresh2026_servicerequests_serviceselection`)
23
24
  instead of the legacy `linkBuilder()` (→ `servicerequests_builder`).
24
25
 
26
+ The "View Recommended Services" button has two placements in the SR view: a static page-load
27
+ button and a per-device dialog button. Both route to either `serviceselection` (repair +
28
+ subscription) or `techsupport` based on the device's latest diagnostic `resultCode`.
29
+ See the library doc for full routing details.
30
+
25
31
  ## Key files / entry points
26
32
  - `App_Model_ServiceRequest::BUILDER` = `'servicerequests_builder'` (legacy)
27
33
  - `App_Model_ServiceRequest::BUILDERREFRESH` = `'togarefresh2026_servicerequests_serviceselection&jobJacketId=1'`
@@ -34,16 +40,21 @@ Files updated to use `linkBuilderRefresh`:
34
40
  |------|----------|---------|
35
41
  | `togarefresh2026/servicerequests/review.php` | line 217 | Quote submission POST URL for non-EXISTING job jackets |
36
42
  | `togarefresh2026/customers/view.php` | line 64 | "Create Work Order For This Customer" button |
37
- | `library/app/model/toga/diagnostic.php` | `initializeDiagnosticDialog()` | Device modal button — routes by diagnostic pass/fail (see library doc) |
43
+ | `togarefresh2026/servicerequests/view.php` | `LatestPerDevice` block | Static "View Recommended Services" button — resultCode-based per-device routing |
44
+ | `library/app/model/toga/diagnostic.php` | `initializeDiagnosticDialog()` | Device modal button — dynamic URL via `_deviceBuilderUrl` JS global |
38
45
 
39
46
  ## Gotchas / known issues
40
47
  - The old `servicerequests/view.php` (legacy non-refresh view) intentionally keeps `linkBuilder()` — 2026 stores do not route through it.
41
48
  - Do not change `App_Model_ServiceRequest::BUILDER` — legacy stores still depend on it.
42
- - The diagnostic modal routing is pass/fail conditional see `1.0/apps/library/features/diagnostic-dialog-view-recommended-services.md` for full details.
43
- - `toga` app repo was not in registry.json before this session it has been added (framework 1.0, role app, dependsOn library).
49
+ - The static button (view.php) uses a `MAX(id) GROUP BY deviceId` LatestPerDevice subquery — any device with a current problem code triggers repair for the whole SR.
50
+ - The device dialog button routes per-device: only the clicked device's latest result determines the URL.
51
+ - Routing uses `resultCode` (not `pass`) — `pass=1` appears on both problem and clean results for BITSTORM. See the library doc for full result code list.
52
+ - `toga` app repo was not in registry.json before 2026-06-23 — it has been added (framework 1.0, role app, dependsOn library).
44
53
 
45
54
  ## Related docs
46
55
  - `1.0/apps/library/features/diagnostic-dialog-view-recommended-services.md`
47
56
 
48
57
  ## Change history
58
+ - 2026-07-28 — Added view.php to files list. Updated routing description from pass-based
59
+ to resultCode-based per-device. Two-button architecture documented. (snaredla)
49
60
  - 2026-06-23 — Initial doc. All 2026 builder buttons now use linkBuilderRefresh; diagnostic modal uses pass/fail routing. toga added to registry.json. (snaredla)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.459",
3
+ "version": "1.0.461",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",