toga-ai 1.0.176 → 1.0.178
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/toga/INDEX.md +1 -0
- package/knowledge/1.0/apps/toga/features/bundleconfirmation-cart-preservation.md +68 -0
- package/knowledge/2.0/apps/api2/INDEX.md +1 -0
- package/knowledge/2.0/apps/api2/features/language-translation-layer.md +110 -0
- package/knowledge/2.0/apps/worker2/INDEX.md +1 -0
- package/knowledge/2.0/apps/worker2/features/wje-freshservice-sync.md +143 -0
- package/knowledge/INDEX.md +3 -3
- package/knowledge/clients/wje/INDEX.md +1 -1
- package/knowledge/clients/wje/profile.md +23 -9
- package/package.json +1 -1
|
@@ -2,3 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
|
+
| [Bundle Confirmation — Cart Preservation When Adding Add-On Services](features/bundleconfirmation-cart-preservation.md) | Fix: adding any add-on service (Data Transfer, Promotional Bundle, New PC Services, etc.) was silently removing the 1-year tech support SKU from the cart. | toga/app/togarefresh2026/servicerequests/bundleconfirmation.php |
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Bundle Confirmation — Cart Preservation When Adding Add-On Services
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: toga
|
|
5
|
+
project: TOGa
|
|
6
|
+
client: office-depot
|
|
7
|
+
type: client-feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-23
|
|
10
|
+
owners: [snaredla]
|
|
11
|
+
files:
|
|
12
|
+
- toga/app/togarefresh2026/servicerequests/bundleconfirmation.php
|
|
13
|
+
related:
|
|
14
|
+
- ../../../clients/office-depot/features/togarefresh2026-linkbuilder-routing.md
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Summary
|
|
18
|
+
Fix: adding any add-on service (Data Transfer, Promotional Bundle, New PC Services, etc.)
|
|
19
|
+
was silently removing the 1-year tech support SKU from the cart. The `?services=` block in
|
|
20
|
+
`bundleconfirmation.php` now only strips tech support when the service being added IS itself
|
|
21
|
+
a tech support plan.
|
|
22
|
+
|
|
23
|
+
## Key files / entry points
|
|
24
|
+
- `toga/app/togarefresh2026/servicerequests/bundleconfirmation.php` — the `$_GET['services']`
|
|
25
|
+
block (lines ~118–150) is the only place this logic lives.
|
|
26
|
+
|
|
27
|
+
## How it works
|
|
28
|
+
Two callers pass `?services=` to `bundleconfirmation.php`:
|
|
29
|
+
|
|
30
|
+
| Caller | Service IDs passed | Expected behavior |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `techsupport.php` (lines 88, 120, 152) | 194, 195, 101900 (tech support plans) | Remove old tech support, add new, set session vars |
|
|
33
|
+
| `additionalservices.php` (lines 277, 288) | Add-on IDs (categories 174, 175, 178, 179) | Append to cart only — never touch existing items |
|
|
34
|
+
|
|
35
|
+
The fixed block:
|
|
36
|
+
```php
|
|
37
|
+
if (in_array($serviceID, $techSupportServiceIds)) {
|
|
38
|
+
// Replacing tech support — remove the old plan before adding the new selection
|
|
39
|
+
$_SESSION['cartProducts'] = array_values(array_filter(...));
|
|
40
|
+
$_SESSION['techSupportServiceId'] = $serviceID;
|
|
41
|
+
$_SESSION['selectedJobJacket'] = 2;
|
|
42
|
+
}
|
|
43
|
+
if (!in_array($serviceID, $_SESSION['cartProducts'])) {
|
|
44
|
+
$_SESSION['cartProducts'][] = $serviceID;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Data model
|
|
49
|
+
Session vars touched:
|
|
50
|
+
- `$_SESSION['cartProducts']` — array of service IDs in the quote
|
|
51
|
+
- `$_SESSION['techSupportServiceId']` — ID of the selected tech support plan
|
|
52
|
+
- `$_SESSION['selectedJobJacket']` — job jacket type (2 = tech support flow)
|
|
53
|
+
|
|
54
|
+
## Gotchas / known issues
|
|
55
|
+
- The old code also called `unset($_SESSION['techSupportServiceId'])` for every add-on.
|
|
56
|
+
This was also a bug — it would flip `$isTechSupportFlow` (line ~296) to false mid-session
|
|
57
|
+
even when tech support was still in the cart. The fix leaves the var alone for add-ons.
|
|
58
|
+
- Four total `cartProducts` removal points exist in the 2026 service request flow. The other
|
|
59
|
+
three are all safe: McAfee replacement (`bundleconfirmation.php:103`, removes only the
|
|
60
|
+
prior McAfee ID), and two empty-value cleanups (lines ~641 and `mcafeeupdates.php:453`).
|
|
61
|
+
- `$techSupportServiceIds = [194, 195, 101900]` — if a new tech support SKU is introduced,
|
|
62
|
+
this array must be updated or the new plan will be treated as an add-on (won't replace old).
|
|
63
|
+
|
|
64
|
+
## Related docs
|
|
65
|
+
- `clients/office-depot/features/togarefresh2026-linkbuilder-routing.md`
|
|
66
|
+
|
|
67
|
+
## Change history
|
|
68
|
+
- 2026-06-23 — Bug fix: add-on services no longer strip tech support from cart. (snaredla)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [API (api2 / TOGa API v2) Architecture](architecture.md) | `api2` is the backend powering the public **TOGa 2.0 API**. | api2/Controller/Index.php, api2/Component/Api/V2/V2.php, api2/Component/Api/Cxml/Cxml.php, api2/Component/Api/V2/Response/Response.php, api2/Config/ |
|
|
6
|
+
| [Language Translation Layer (audience.language + sidecar tables)](features/language-translation-layer.md) | Serves the same TOGa data (Item title/description/longDescription, expanding later) in multiple languages without forking the schema or breaking English consume | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Setting.php, _underscore/Model/Core/RecordField.php, _underscore/Model/Core/DefaultGlobalSetting.php, _underscore/Model/Client/ItemTranslation.php, dbchanges2/Client/2026-06-23a - ItemTranslations.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql, dbchanges2/Core/2026-06-23a - RecordFieldsTranslationColumn.sql, dbchanges2/Core/2026-06-23b - ItemTranslationsRecord.sql |
|
|
6
7
|
| [POST + JSON-body args for scripted APIs](features/scripted-api-post-body-args.md) | The V2 engine can run a Record Script (scripted API) for a **POST** request, and a scripted API can receive its arguments from the **JSON request body** instead | api2/Component/Api/V2/V2.php |
|
|
7
8
|
| [Tickets API (/v2/tickets)](features/tickets-api.md) | The generic ticket endpoint of the 2.0 REST API. | Component/Api/V2/V2.php |
|
|
8
9
|
| [AWS CodePipeline Deployment via CodeConnections (GitHub → Elastic Beanstalk)](workflows/codepipeline-codeconnections-deploy.md) | 2.0 apps (`api2`, `_underscore`) are deployed through **AWS CodePipeline**. | |
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Language Translation Layer (audience.language + sidecar tables)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: api2
|
|
5
|
+
project: API
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-23
|
|
10
|
+
owners: ["jcardinal"]
|
|
11
|
+
files:
|
|
12
|
+
- api2/Component/Api/V2/V2.php
|
|
13
|
+
- _underscore/Model/Core/Setting.php
|
|
14
|
+
- _underscore/Model/Core/RecordField.php
|
|
15
|
+
- _underscore/Model/Core/DefaultGlobalSetting.php
|
|
16
|
+
- _underscore/Model/Client/ItemTranslation.php
|
|
17
|
+
- dbchanges2/Client/2026-06-23a - ItemTranslations.sql
|
|
18
|
+
- dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql
|
|
19
|
+
- dbchanges2/Core/2026-06-23a - RecordFieldsTranslationColumn.sql
|
|
20
|
+
- dbchanges2/Core/2026-06-23b - ItemTranslationsRecord.sql
|
|
21
|
+
related:
|
|
22
|
+
- ./acl-permission-chain.md
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Summary
|
|
26
|
+
|
|
27
|
+
Serves the same TOGa data (Item title/description/longDescription, expanding later) in multiple
|
|
28
|
+
languages without forking the schema or breaking English consumers. English stays in the source
|
|
29
|
+
table; per-language overlays live in per-table **sidecar** tables (`ItemTranslations` first).
|
|
30
|
+
The caller's language is resolved once at authentication, surfaced on every response's
|
|
31
|
+
`audience.language`, and embedded in the JWT; reads/writes are redirected to the sidecar in the
|
|
32
|
+
**API layer** (`V2.php`) — the `_Model` ORM is untouched, so workers/crons/cXML keep returning
|
|
33
|
+
base English. Missing translations fall back to English with a warning in `messages[]` (still 200).
|
|
34
|
+
|
|
35
|
+
## Key files / entry points
|
|
36
|
+
|
|
37
|
+
- `_underscore/Model/Core/Setting.php` — `SLUG_LANGUAGE` const + `valueIsSet()` helper (the cascade
|
|
38
|
+
resolver itself lives in V2.php, since it is a language/API concern).
|
|
39
|
+
- `api2/Component/Api/V2/V2.php`:
|
|
40
|
+
- `resolveSettingValue($slug, $context)` — the 8-layer Settings-cascade resolver (private).
|
|
41
|
+
- `resolveValidLanguageCode()` — validates the resolved code against `Languages.code`, default `en`.
|
|
42
|
+
- `getTranslatedFieldValue()` / `loadTranslationSidecar()` / `shouldTranslate()` / `addTranslationFallbackWarning()` — read path.
|
|
43
|
+
- `extractTranslationWrites()` / `saveTranslationWrites()` — write path.
|
|
44
|
+
- `lookupTranslatableFieldByRecordFieldId` built in the RecordFields load (~line 200).
|
|
45
|
+
- `_underscore/Model/Client/ItemTranslation.php` — the first sidecar model.
|
|
46
|
+
|
|
47
|
+
## How it works
|
|
48
|
+
|
|
49
|
+
**Language resolution (auth time, user auth only).** `resolveSettingValue('language', …)` walks the
|
|
50
|
+
Settings Matrix in order — DefaultGlobal, DefaultApp, ClientGlobal, ClientApp, PersonaGlobal,
|
|
51
|
+
PersonaApp, UserGlobal, UserApp — each overriding the previous when set. A Client/Persona layer with
|
|
52
|
+
`isOverridable = 0` **locks** the value against all later layers. For the persona layers, the **first
|
|
53
|
+
persona** (in the user's persona order) that sets it wins. The resolved code is validated against
|
|
54
|
+
`Languages.code` (fallback `en`), embedded in the JWT `id.language` claim, and echoed on every
|
|
55
|
+
response as `audience.language`. API-credential auth gets no language. Token refresh copies the claim,
|
|
56
|
+
so a language change requires re-authentication.
|
|
57
|
+
|
|
58
|
+
**Which fields are translatable (metadata-driven).** `Core.RecordFields.translationRecordFieldId`
|
|
59
|
+
(new column, positioned after `recordId`) points a source field's RecordField at the sidecar field's
|
|
60
|
+
RecordField. `buildLookups`/RecordFields-load resolves this into
|
|
61
|
+
`lookupTranslatableFieldByRecordFieldId[sourceRecordFieldId] => {sidecarRecordId, sidecarField}`.
|
|
62
|
+
|
|
63
|
+
**Read path (PHP `_Model::load()`, NOT a SQL JOIN — deliberate choice).** At every response
|
|
64
|
+
serialization site (top-level full-model, custom-fields, FK child, both inherent-child paths)
|
|
65
|
+
`getTranslatedFieldValue($record, $field, $sourceModel, $defaultValue)` is called. When a non-base
|
|
66
|
+
language is active and the field is translatable, it loads the sidecar row for that source row +
|
|
67
|
+
`languageId` (cached per row so multiple fields = one load) and returns the sidecar value if non-null;
|
|
68
|
+
otherwise it returns the English default and queues a deduped `W*` warning.
|
|
69
|
+
|
|
70
|
+
**Write path.** On create and update, `extractTranslationWrites()` pulls translatable fields out of
|
|
71
|
+
the write set for a non-base language (so the English source is never overwritten), and after the
|
|
72
|
+
source row saves, `saveTranslationWrites()` upserts them into the sidecar for the current language.
|
|
73
|
+
Nested-child translatable writes are NOT auto-redirected — use the dedicated `/v2/item-translations`
|
|
74
|
+
endpoint for those.
|
|
75
|
+
|
|
76
|
+
## Data model
|
|
77
|
+
|
|
78
|
+
- `Client.ItemTranslations` — `id, uuid, dtCreated, dtUpdated, itemId (FK Items, RESTRICT),
|
|
79
|
+
languageId (FK Languages, RESTRICT), title, description, longDescription`; `UNIQUE(itemId, languageId)`.
|
|
80
|
+
English stays in `Items`; sidecar holds only non-English overlays (null → English fallback).
|
|
81
|
+
- `Core.RecordFields.translationRecordFieldId` — new nullable self-FK (after `recordId`).
|
|
82
|
+
- `Core.Records` 331 = `item-translations` (`aclDatabase='CLIENT'`); `Core.RecordFields` 2233–2239
|
|
83
|
+
(its fields), 2240 (the self-describing `translationRecordFieldId` field).
|
|
84
|
+
- `item-translations` ACL chain lives in each client DB targeting the Base role — see
|
|
85
|
+
[acl-permission-chain.md](./acl-permission-chain.md).
|
|
86
|
+
|
|
87
|
+
## Client variations
|
|
88
|
+
|
|
89
|
+
None — uniform across all clients. The sidecar table + ACL ship via `dbchanges2/Client/` (all client DBs).
|
|
90
|
+
|
|
91
|
+
## Gotchas / known issues
|
|
92
|
+
|
|
93
|
+
- The resolver/read/write deliberately use `_Model::load()` per row, not a SQL JOIN/COALESCE — less
|
|
94
|
+
SQL-efficient but the team's chosen approach; the sidecar is cached per row to avoid N-per-field loads.
|
|
95
|
+
- `audience.language` reflects the *requested* language even if a given field lacks a translation; the
|
|
96
|
+
per-field fallback warning signals the English fallback.
|
|
97
|
+
- The base language is `en`; when the resolved language is `en` (or API auth) all translation logic is
|
|
98
|
+
skipped and responses are byte-identical to pre-feature.
|
|
99
|
+
- Migrations not yet executed at time of writing; needs live verification.
|
|
100
|
+
|
|
101
|
+
## Change history
|
|
102
|
+
|
|
103
|
+
- 2026-06-23 — Initial build: audience.language + JWT embedding, ItemTranslations sidecar + metadata +
|
|
104
|
+
full ACL chain, and translation-aware read/write at the API layer. Also fixed a latent autoload bug
|
|
105
|
+
by renaming `DefaultFlobalSetting.php` → `DefaultGlobalSetting.php`. (jcardinal)
|
|
106
|
+
|
|
107
|
+
## Related docs
|
|
108
|
+
|
|
109
|
+
- [ACL Permission Chain](./acl-permission-chain.md)
|
|
110
|
+
- [api2 Architecture](../architecture.md)
|
|
@@ -13,3 +13,4 @@
|
|
|
13
13
|
| [NetSuite → Forecast Open-Orders Sync (salesOrder webhook → OpenOrderItems)](features/netsuite-salesorder-open-orders-sync.md) | Webhook-driven, single-record port of the legacy open-orders importer (TRUE-79142). | worker2/Worker/Netsuite/SalesOrder.php, worker2/Worker/Netsuite.php, test/@dave/probe_salesorder_rest_shape.php, test/@dave/probe_open_order_lines.php, test/@dave/check_so_status.php, test/@dave/check_so_history.php, test/@dave/probe_so_rest_lines.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
|
|
14
14
|
| [Startech Webhook Handler (worker2)](features/startech-webhook-handler.md) | Receives inbound webhook events from Startech (Easeedesk) and creates or updates the corresponding ticket in TOGA 2.0. | worker2/Worker/Startech.php |
|
|
15
15
|
| [Teams Meeting Transcript Export](features/teams-transcript-export.md) | `_Worker_Team_Transcripts` (action `Team/Transcripts/Export`) polls Microsoft Graph for Teams meeting transcripts produced by a set of organizers, classifies ea | worker2/Worker/Team/Transcripts.php, worker2/Config/production.ini, worker2/Database/TeamsTranscriptExports.sql, dbchanges2/Core/2026-06-18a - Teams Transcript Export schedule.sql |
|
|
16
|
+
| [WJE Freshservice Sync (worker2)](features/wje-freshservice-sync.md) | WJE ("WJE IT", helpdesk `wje.freshservice.com`) is a **Freshservice**-based help-desk client whose tickets, contacts, assets, groups, categories, and canned res | worker2/Worker/Wje.php, _underscore/Component/Api/Wje/Wje.php, _underscore/Model/Wje/Ticket.php, _underscore/Model/Wje/TicketNote.php, _underscore/Model/Wje/Contact.php, _underscore/Model/Wje/Unit.php, _underscore/Model/Wje/TicketTeam.php, _underscore/Model/Wje/TicketCategory.php, _underscore/Model/Wje/AssetType.php, _underscore/Model/Wje/PredefinedReply.php, library/app/api/wje.php, worker/crons/toga2/wje/import_supporting_records.php, worker/crons/toga2/wje/sync_togasupply_wje.php, worker/crons/notifications/reports/wje/wje_common.php, library/app/systemmonitor/wje.php, dbchanges2/Client_Wje/2024-10-04 - WjeOnboarding.sql |
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: WJE Freshservice Sync (worker2)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: worker2
|
|
5
|
+
project: Worker
|
|
6
|
+
client: wje
|
|
7
|
+
type: client-feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-23
|
|
10
|
+
owners: ["snaredla"]
|
|
11
|
+
files:
|
|
12
|
+
- worker2/Worker/Wje.php
|
|
13
|
+
- _underscore/Component/Api/Wje/Wje.php
|
|
14
|
+
- _underscore/Model/Wje/Ticket.php
|
|
15
|
+
- _underscore/Model/Wje/TicketNote.php
|
|
16
|
+
- _underscore/Model/Wje/Contact.php
|
|
17
|
+
- _underscore/Model/Wje/Unit.php
|
|
18
|
+
- _underscore/Model/Wje/TicketTeam.php
|
|
19
|
+
- _underscore/Model/Wje/TicketCategory.php
|
|
20
|
+
- _underscore/Model/Wje/AssetType.php
|
|
21
|
+
- _underscore/Model/Wje/PredefinedReply.php
|
|
22
|
+
- library/app/api/wje.php
|
|
23
|
+
- worker/crons/toga2/wje/import_supporting_records.php
|
|
24
|
+
- worker/crons/toga2/wje/sync_togasupply_wje.php
|
|
25
|
+
- worker/crons/notifications/reports/wje/wje_common.php
|
|
26
|
+
- library/app/systemmonitor/wje.php
|
|
27
|
+
- dbchanges2/Client_Wje/2024-10-04 - WjeOnboarding.sql
|
|
28
|
+
related:
|
|
29
|
+
- clients/wje/profile.md
|
|
30
|
+
- 2.0/apps/worker2/features/elite-freshservice-sync.md
|
|
31
|
+
- 1.0/apps/library/features/toga2-api-client-and-bridge.md
|
|
32
|
+
- 2.0/apps/worker2/features/startech-webhook-handler.md
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Summary
|
|
36
|
+
|
|
37
|
+
WJE ("WJE IT", helpdesk `wje.freshservice.com`) is a **Freshservice**-based help-desk client whose
|
|
38
|
+
tickets, contacts, assets, groups, categories, and canned responses are synced into TOGA. Unlike
|
|
39
|
+
Startech (multi-client, resolved dynamically by `company_id`), **WJE is a single hardcoded client** —
|
|
40
|
+
its IDs and credentials are baked into the integration files.
|
|
41
|
+
|
|
42
|
+
Data reaches TOGA two ways: a **real-time Freshservice webhook** handled by `_Worker_Wje::Webhook`, and
|
|
43
|
+
a **daily batch import** of supporting records. A separate 2-minute cron bridges Toga2 ↔ TOGaDesk (see
|
|
44
|
+
`clients/wje/profile.md`).
|
|
45
|
+
|
|
46
|
+
## Key files / entry points
|
|
47
|
+
|
|
48
|
+
- **Webhook handler:** `worker2/Worker/Wje.php` — `abstract _Worker_Wje`, entry `Webhook($payload, $headers)`.
|
|
49
|
+
Reached via `webhook.togahub.com/wje` → WebhookIngestion Lambda → `Core.WorkerJobs` action `Wje/Webhook`
|
|
50
|
+
→ SQS → EB worker.
|
|
51
|
+
- **API clients (two parallel, same creds):** `_Component_Api_Wje` (`_underscore/Component/Api/Wje/Wje.php`,
|
|
52
|
+
used by worker2/2.0) and `App_Api_Wje extends App_Api` (`library/app/api/wje.php`, used by 1.0 crons).
|
|
53
|
+
Both hit `https://wje.freshservice.com` with basic auth; `send($method, $route, $payload)`, 90s timeout,
|
|
54
|
+
1s sleep between calls. **Production only — no test/stage Freshservice instance.**
|
|
55
|
+
- **Crons (worker 1.0):** `crons/toga2/wje/import_supporting_records.php` (daily import),
|
|
56
|
+
`crons/toga2/wje/sync_togasupply_wje.php` (2-min Togadesk bridge),
|
|
57
|
+
`crons/notifications/reports/wje/{wje_monthly_report,wje_quarterly_report,wje_common}.php`.
|
|
58
|
+
- **Models:** `_underscore/Model/Wje/*` (8 classes, see Data model).
|
|
59
|
+
- **Monitoring:** `library/app/systemmonitor/wje.php` (`App_SystemMonitor_WJE`).
|
|
60
|
+
|
|
61
|
+
## How it works
|
|
62
|
+
|
|
63
|
+
### Real-time webhook (`_Worker_Wje::Webhook`)
|
|
64
|
+
1. Payload arrives JSON-wrapped under `freshdesk_webhook` (it is Freshservice despite the name):
|
|
65
|
+
`$wjeRecord = (object)((object)json_decode($payload, true))->freshdesk_webhook;`
|
|
66
|
+
2. Branches on shape: **ticket** webhook if `property_exists($wjeRecord, 'ticket_id')`; **asset** webhook
|
|
67
|
+
if `property_exists($wjeRecord, 'asset_display_id')`.
|
|
68
|
+
3. **Ticket path:** upserts associated assets (`associated_asset_ids`), the requester contact
|
|
69
|
+
(`requester_id`), and the agent contact (`agent_id`). Sleeps a few seconds to dodge duplicate-ticket
|
|
70
|
+
races, then **re-fetches the full ticket** from WJE `/tickets/{id_numeric}` (the webhook body omits
|
|
71
|
+
`group_id` and category detail). Resolves the category/subcategory/item hierarchy by **name** against
|
|
72
|
+
TOGA `TicketCategories`. Upserts the ticket (by `c_wjeTicketId`) and syncs conversation notes from
|
|
73
|
+
`/tickets/{id}/conversations` (by `c_wjeConversationId`).
|
|
74
|
+
4. **Asset path:** upserts the unit by asset tag `ASSET-{display_id}`, matches the contact by email when
|
|
75
|
+
`c_wjeUserId` is unavailable, and updates serial/tag/type/contact custom fields.
|
|
76
|
+
|
|
77
|
+
### Daily import (`import_supporting_records.php`)
|
|
78
|
+
Canned responses → `PredefinedReplies` (`c_wjeCannedResponseId`); requesters → `Contacts` (`c_wjeUserId`);
|
|
79
|
+
assets → `Units` (`c_wjeAssetId`, tag `ASSET-{id}`); groups → `TicketTeams` (`c_wjeGroupId`);
|
|
80
|
+
`ticket_form_fields` (field `13000126389`) → `TicketCategories` (`c_wjeCategoryId`, 3-level hierarchy).
|
|
81
|
+
|
|
82
|
+
### Reports & monitoring
|
|
83
|
+
`wje_common.php::generateWJEReport(...)` builds Excel metrics (SLA, first-response, resolution, reopens)
|
|
84
|
+
via PhpSpreadsheet for client 153 / dept 269; monthly cron emails `serviceops@togatech.com` +
|
|
85
|
+
`mhicks@togatech.com`. `App_SystemMonitor_WJE` checks every 5 min (300s window, 30-min reminders) and
|
|
86
|
+
alerts `devteam@togatech.com` on Freshservice/webhook responses > 300, reading both Logs 1.0 (`API`) and
|
|
87
|
+
Logs 2.0 (`Api` in `db_prod_logs_wje`).
|
|
88
|
+
|
|
89
|
+
## Data model
|
|
90
|
+
|
|
91
|
+
Models in `_underscore/Model/Wje/` extend the base `_Model_Client_*` and add WJE linkage fields
|
|
92
|
+
(all `self::FIELD_CHAR`):
|
|
93
|
+
|
|
94
|
+
| Model | Custom field(s) |
|
|
95
|
+
|-------|-----------------|
|
|
96
|
+
| `_Model_Wje_Ticket` | `c_wjeTicketId`, `c_wjeAgentId` |
|
|
97
|
+
| `_Model_Wje_TicketNote` | `c_wjeConversationId` (unique in DB) |
|
|
98
|
+
| `_Model_Wje_Contact` | `c_wjeUserId` |
|
|
99
|
+
| `_Model_Wje_Unit` | `c_wjeAssetId` |
|
|
100
|
+
| `_Model_Wje_TicketTeam` | `c_wjeGroupId` |
|
|
101
|
+
| `_Model_Wje_TicketCategory` | `c_wjeCategoryId` |
|
|
102
|
+
| `_Model_Wje_AssetType` | `c_wjeAssetTypeId` |
|
|
103
|
+
| `_Model_Wje_PredefinedReply` | `c_wjeCannedResponseId` |
|
|
104
|
+
|
|
105
|
+
Schema in `dbchanges2/Client_Wje/2024-10-04 - WjeOnboarding.sql` (+ `2024-10-23.sql`, `2024-10-25.sql`):
|
|
106
|
+
API record, the `c_wje*` CustomRecordFields, TicketStages (Open=2, Pending=3, Resolved=4, Closed=5),
|
|
107
|
+
asset type "Laptop", urgencies (Low/Normal/High/Critical), system params
|
|
108
|
+
`TOGADESK_LAST_TICKET_INTEGRATION_DATETIME` / `TOGA_LAST_TICKET_INTEGRATION_DATETIME`.
|
|
109
|
+
|
|
110
|
+
Hardcoded IDs: `CLIENT_UUID_WJE=c2f5180f-…`, `API_UUID_WJE=4750650f-…` (and `API_SECRET_WJE`) in
|
|
111
|
+
`worker2/Worker/Wje.php`; TOGaDesk client `153`, dept `269` (internal `290`); default group
|
|
112
|
+
`13000155420` / agent `13000771757`; category form field `13000126389`.
|
|
113
|
+
|
|
114
|
+
## Client variations
|
|
115
|
+
|
|
116
|
+
WJE-specific: single hardcoded tenant (no `company_id` resolution); requires a group on every ticket and
|
|
117
|
+
falls back to the default group/agent when none is assigned. Sister Freshservice client **Elite** uses a
|
|
118
|
+
parallel but separate `_Worker_Elite` handler — see related doc.
|
|
119
|
+
|
|
120
|
+
## Gotchas / known issues
|
|
121
|
+
|
|
122
|
+
- **W1** Payload is wrapped under `freshdesk_webhook`, not top-level. Decode with `json_decode($p, true)` then cast.
|
|
123
|
+
- **W2** Webhook body is thin — `ticket_category`/`group_id` often null; the handler re-fetches `/tickets/{id_numeric}`. Don't trust the webhook body for category/group.
|
|
124
|
+
- **W3** Two id forms: `ticket_id` (string "INC-46687", stored in `c_wjeTicketId`) vs `id_numeric` (46687, used in API calls). Mixing them → 404s / missed matches.
|
|
125
|
+
- **W4** Webhook resolves category by **name** against TOGA `TicketCategories`; the **daily import** is what populates `c_wjeCategoryId`. A WJE rename before the import runs breaks name matching — check the import cron first.
|
|
126
|
+
- **W5** Units keyed by asset tag `ASSET-{display_id}`, linked via `c_wjeAssetId`; asset webhook falls back to email matching when `c_wjeUserId` is absent.
|
|
127
|
+
- **W6** Asset import catches per-asset errors and continues; if WJE revokes `/assets` API access it silently skips assets — check logs.
|
|
128
|
+
- **W7** `c_wjeUserId` is the source of truth for contact identity; email is only opportunistically added.
|
|
129
|
+
- **W8** `_Component_Api_Wje` is production-only — local testing hits production WJE; be careful with writes.
|
|
130
|
+
- **W9** Credentials are duplicated in `_Component_Api_Wje` and `App_Api_Wje` — rotate **both**.
|
|
131
|
+
- **W10** Lazy-transaction rule: write back `c_wje*` identifiers via direct model `->save()`, not an internal API round trip inside an open transaction (cf. Startech G4).
|
|
132
|
+
- **W11** `json_decode($s, true)` + top-level `(object)` cast leaves nested fields as arrays — cast each level you traverse.
|
|
133
|
+
- **W12** TicketStages use Freshservice status codes (Open=2 … Closed=5); status sync depends on those matching live Freshservice statuses.
|
|
134
|
+
|
|
135
|
+
> Secrets (`API_SECRET_WJE`, the Freshservice password) are hardcoded in source — read them from the file
|
|
136
|
+
> when needed; never copy the values into docs or commits.
|
|
137
|
+
|
|
138
|
+
## Related docs
|
|
139
|
+
|
|
140
|
+
- `clients/wje/profile.md` — WJE client profile + the 2-min Togadesk bridge sync.
|
|
141
|
+
- `2.0/apps/worker2/features/elite-freshservice-sync.md` — sister Freshservice client.
|
|
142
|
+
- `2.0/apps/worker2/features/startech-webhook-handler.md` — the multi-client webhook pattern WJE predates.
|
|
143
|
+
- `1.0/apps/library/features/toga2-api-client-and-bridge.md` — `App_Api_Toga2::syncWithTogadesk`.
|
package/knowledge/INDEX.md
CHANGED
|
@@ -11,13 +11,13 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
11
11
|
- **webhook** (Webhook) — 1 doc(s) → [1.0/apps/webhook/INDEX.md](1.0/apps/webhook/INDEX.md)
|
|
12
12
|
- **walmarttechservices** (Walmart Tech Services) — 1 doc(s) → [1.0/apps/walmarttechservices/INDEX.md](1.0/apps/walmarttechservices/INDEX.md)
|
|
13
13
|
- **test** (Test) — 11 doc(s) → [1.0/apps/test/INDEX.md](1.0/apps/test/INDEX.md)
|
|
14
|
-
- **toga** (TOGa) —
|
|
14
|
+
- **toga** (TOGa) — 2 doc(s) → [1.0/apps/toga/INDEX.md](1.0/apps/toga/INDEX.md)
|
|
15
15
|
|
|
16
16
|
## 2.0 framework
|
|
17
17
|
|
|
18
18
|
- **_underscore** (_Underscore) _(framework core)_ — 11 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
19
|
-
- **worker2** (Worker) —
|
|
20
|
-
- **api2** (API) —
|
|
19
|
+
- **worker2** (Worker) — 12 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
20
|
+
- **api2** (API) — 6 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
21
21
|
- **dbchanges2** (Database Changes) _(framework core)_ — 2 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
22
22
|
- **toga2-supply** (TOGa Supply) — 3 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
23
23
|
- **saml** (SAML SSO Gateway) — 2 doc(s) → [2.0/apps/saml/INDEX.md](2.0/apps/saml/INDEX.md)
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Framework | Summary | Files |
|
|
4
4
|
|-----|-----------|---------|-------|
|
|
5
|
-
| [Wiss, Janney, Elstner Associates, Inc.](profile.md) | 2.0 | Wiss, Janney, Elstner Associates, Inc. | worker/crons/toga2/wje/sync_togasupply_wje.php |
|
|
5
|
+
| [Wiss, Janney, Elstner Associates, Inc.](profile.md) | 2.0 | Wiss, Janney, Elstner Associates, Inc. | worker/crons/toga2/wje/sync_togasupply_wje.php, worker2/Worker/Wje.php, worker/crons/toga2/wje/import_supporting_records.php |
|
|
@@ -12,19 +12,33 @@ updated: 2026-06-23
|
|
|
12
12
|
owners: [jcardinal]
|
|
13
13
|
files:
|
|
14
14
|
- worker/crons/toga2/wje/sync_togasupply_wje.php
|
|
15
|
+
- worker2/Worker/Wje.php
|
|
16
|
+
- worker/crons/toga2/wje/import_supporting_records.php
|
|
15
17
|
related:
|
|
16
18
|
- ../../1.0/apps/library/features/toga2-api-client-and-bridge.md
|
|
19
|
+
- ../../2.0/apps/worker2/features/wje-freshservice-sync.md
|
|
17
20
|
---
|
|
18
21
|
|
|
19
22
|
## Summary
|
|
20
23
|
|
|
21
|
-
Wiss, Janney, Elstner Associates, Inc. (WJE) is a
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
set of bridge sub-syncs (tickets both directions, ticket-notes, contacts→people, items/units→
|
|
25
|
-
assets, predefined-replies, ticket-teams→groups, ticket-categories) for TOGaDesk client id 153,
|
|
26
|
-
department 269 (plus internal-tickets department 290). WJE requires a group on every ticket and
|
|
27
|
-
falls back to a default group/agent when none is assigned.
|
|
24
|
+
Wiss, Janney, Elstner Associates, Inc. (WJE — "WJE IT") is a **Freshservice**-based help-desk
|
|
25
|
+
client (`wje.freshservice.com`). It is a **single hardcoded tenant** (no dynamic `company_id`
|
|
26
|
+
resolution). Data moves between WJE and TOGA on three paths:
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
1. **Real-time Freshservice webhook** → `_Worker_Wje::Webhook` (worker2) — creates/updates tickets,
|
|
29
|
+
contacts, assets, groups, categories, and conversation notes, keyed by `c_wje*` custom fields.
|
|
30
|
+
2. **Daily batch import** (`worker/crons/toga2/wje/import_supporting_records.php`) — imports canned
|
|
31
|
+
responses, requesters, assets, groups, and the 3-level category hierarchy.
|
|
32
|
+
3. **2-minute TOGaDesk bridge** (`App_Api_Toga2::syncWithTogadesk`) — the cron
|
|
33
|
+
`worker/crons/toga2/wje/sync_togasupply_wje.php` enables the **full** set of bridge sub-syncs
|
|
34
|
+
(tickets both directions, ticket-notes, contacts→people, items/units→assets, predefined-replies,
|
|
35
|
+
ticket-teams→groups, ticket-categories) for TOGaDesk client id 153, department 269 (plus
|
|
36
|
+
internal-tickets department 290).
|
|
37
|
+
|
|
38
|
+
WJE requires a group on every ticket and falls back to a default group/agent (`13000155420` /
|
|
39
|
+
`13000771757`) when none is assigned. No NetSuite TOGa Supply importer runs for WJE (it is a
|
|
40
|
+
help-desk integration, not a supply/procurement client).
|
|
41
|
+
|
|
42
|
+
See **`2.0/apps/worker2/features/wje-freshservice-sync.md`** for the full integration (webhook
|
|
43
|
+
handler, import cron, `c_wje*` models, API clients, schema, reports, monitoring, and gotchas), and
|
|
44
|
+
the bridge feature doc for the `syncWithTogadesk` mechanism.
|
package/package.json
CHANGED