toga-ai 1.0.314 → 1.0.316

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.
@@ -6,7 +6,7 @@ project: Library
6
6
  client: elite
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-06-15
9
+ updated: 2026-07-10
10
10
  owners: ["snaredla"]
11
11
  files:
12
12
  - library/app/api/toga2.php
@@ -165,5 +165,49 @@ Pass `true` for the dedup GET — errors must not silently empty the dedup array
165
165
  Each ticket sync is wrapped in `try/catch` — exceptions are sent to Sentry and the run
166
166
  continues with the next ticket. A single bad ticket does not abort the whole sync.
167
167
 
168
+ ## Elite-specific sync behaviour
169
+
170
+ ### Department assignment (TOGaDesk → TOGA 2)
171
+ Elite tickets use `c_togadeskDepartmentId` on `Client_Elite.TicketTeams` to reverse-lookup the correct Toga2 team from TOGaDesk's actual `departmentid`. CF84 (custom field storing team name) is **never** updated when an agent manually transfers a ticket between TOGaDesk departments via the UI — so it cannot be used as the sync source for team.
172
+
173
+ Lookup in `syncTogaDesk1TicketIntoToga2Ticket`:
174
+ ```php
175
+ $eliteTeamLookupOptions = [
176
+ 'where' => ['and' => [['c_togadeskDepartmentId' => ['=' => (int) $togadeskTicket->departmentid]]]]
177
+ ];
178
+ $eliteTeamResult = App_Api_Toga2::send(..., 'GET', '/ticket-teams', null, $eliteTeamLookupOptions, ...);
179
+ if ($eliteTeamResult->meta->totalRecordCount == 1) {
180
+ $payload['ticketTeam'] = ['uuid' => $eliteTeamResult->data->ticketTeams[0]->uuid];
181
+ }
182
+ ```
183
+
184
+ **Pending DB migration** — `c_togadeskDepartmentId INT NULL` must be added to `Client_Elite.TicketTeams` with team→dept mappings before this lookup works.
185
+
186
+ ### Timestamp guard (prevents overwriting Freshservice changes)
187
+ Before pushing any field update (priority, status, category, group, type) to Freshservice, the sync compares timestamps:
188
+ ```php
189
+ $eliteOriginalUpdatedAt = ... strtotime($eliteTicket->updated_at) ...; // captured BEFORE reply POSTs
190
+ $togadeskUpdatedAt = strtotime($togadeskLastModified);
191
+ $togadeskUpdateIsMoreRecent = ($togadeskUpdatedAt > $eliteOriginalUpdatedAt);
192
+ ```
193
+ Nothing is written to Freshservice unless TOGaDesk is strictly newer. Safe to reset `TOGA_LAST_TICKET_INTEGRATION_DATETIME` for re-sync — Freshservice changes are protected.
194
+
195
+ ### Reply status badge mapping
196
+ `tickets_replies.newStatus` is set from `$ticketNote->ticketStage->code` when a reply is synced in. Elite code → TOGaDesk status:
197
+
198
+ | Freshservice code | TOGaDesk status |
199
+ |---|---|
200
+ | 2 | Open |
201
+ | 3 | Awaiting User |
202
+ | 6 | In Progress |
203
+ | 4 | Resolved |
204
+ | 5 | Closed |
205
+
206
+ **Gotcha:** `case 2` was previously mapped to `STATUS__NEW_TICKET` (wrong) — fixed 2026-07-10. Replies from Freshservice with status "Open" were showing "New Ticket" badge in TOGaDesk.
207
+
208
+ ### Variable name pollution gotcha
209
+ The Elite team lookup block inside `syncTogaDesk1TicketIntoToga2Ticket` must use its own dedicated options variable (`$eliteTeamLookupOptions`) — never reuse `$method`, `$route`, `$payload`, or `$options`. Those shared variables are consumed by the `send()` call immediately after the block. Reusing them causes the wrong endpoint to be called, resulting in a Sentry crash: `Undefined property: stdClass::$tickets`.
210
+
168
211
  ## Change history
169
212
  - 2026-06-10 — Documented `App_Api_Toga2::syncWithTogadesk` orchestration (bidirectional TOGA 2 ↔ TOGaDesk ticket/note/file sync, watermarks, `ticketNoteFiles` gotcha). (snaredla)
213
+ - 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)
@@ -6,7 +6,7 @@
6
6
  | [Cart Bundle Submission & the bundleUuid Identity Contract](features/cart-bundle-submission-and-identity.md) | How cart **bundles** (kits) are turned into `SalesOrderItems` when a cart is submitted or an existing order is edited, and the **identity-field contract** every | src/api/syncSalesOrderItemsFromLocalStorageCartToApi.ts, src/utils/formatSalesOrderBundlesFromApi.ts, src/stores/useCartStoreZu.ts, src/pages/OrderDetails/helpers/formatSalesOrderDataFromLocalStorage.ts, src/pages/OrderDetails/view/components/OrderItems.tsx |
7
7
  | [Cart Notification Emails — duplicate prevention](features/cart-notification-emails.md) | On the cart "Notifications" section a user can add CC email addresses to an order. | src/pages/Cart/CartPage.tsx, src/pages/Cart/view/cartForm/CartForm.tsx, src/stores/useEmailOptionsStore.ts, src/stores/useCartSalesQuoteZu.ts, src/pages/Cart/viewModel/FIELDS/*/*/*/CARTPAGE.ts |
8
8
  | [Cart Page — config-driven form architecture (current state + planned refactor)](features/cart-page-config-architecture.md) | The Cart page (`src/pages/Cart/`) is the most config-heavy page in `toga2-commerce`. | src/pages/Cart/CartPage.tsx, src/pages/Cart/view/cartForm/CartForm.tsx, src/pages/Cart/view/cartForm/CartFormSection.tsx, src/pages/Cart/view/cartForm/CartFormRenderer.tsx, src/pages/Cart/view/EditCart.tsx, src/pages/Cart/view/EditOrder.tsx, src/pages/Cart/viewModel/useEditOrderOrEditCartViewModel.ts, src/pages/Cart/viewModel/FIELDS/*/*/*/CARTPAGE.ts, src/hooks/useAssignClientFields.ts |
9
- | [Client Fields — per-tenant / language / role content & config](features/client-fields.md) | Almost no user-facing text, field layout, or page config is hard-coded in `toga2-commerce`. | src/fieldsConfig/index.ts, src/fieldsConfig/getClientLoginFields.ts, src/fieldsConfig/clientFields/COMPASS.json, src/fieldsConfig/clientFields/COMPASSCANADA.json, src/fieldsConfig/clientFields/QUAD.json, src/hooks/useAssignClientFields.ts, src/hooks/useDynamicConditionalFieldOptions.ts, src/stores/useFieldsStore.ts, src/components/BaseDetailField/BaseDetailField.tsx |
9
+ | [Client Fields — per-tenant / language / role content & config](features/client-fields.md) | Almost no user-facing text, field layout, or page config is hard-coded in `toga2-commerce`. | src/fieldsConfig/index.ts, src/fieldsConfig/getClientLoginFields.ts, src/fieldsConfig/clientFields/COMPASS.json, src/fieldsConfig/clientFields/COMPASSCANADA.json, src/fieldsConfig/clientFields/QUAD.json, src/pages/Cart/api/CartApi.ts, src/hooks/useAssignClientFields.ts, src/hooks/useDynamicConditionalFieldOptions.ts, src/stores/useFieldsStore.ts, src/components/BaseDetailField/BaseDetailField.tsx |
10
10
  | [Config-Driven Expedited Shipping Gating (Cart)](features/expedited-shipping-gating.md) | On the toga2-commerce **Cart** page, expedited shipping options (**"2nd Day EOB"** and **"Next Day Air"**) are only offered in the *Shipping Method* dropdown wh | toga2-commerce/src/pages/Cart/helpers/shippingOptionGates.ts, toga2-commerce/src/pages/Cart/viewModel/FIELDS/shared/shippingOptionGates.ts, toga2-commerce/src/pages/Cart/view/cartForm/CartForm.tsx, toga2-commerce/src/pages/Cart/CartPage.tsx |
11
11
  | [Multi-Tenant Resolution & Theming](features/multi-tenant-theming.md) | `toga2-commerce` serves multiple clients from one codebase. | src/themeConfig/themes.json, src/themeConfig/ThemeContext.tsx, src/themeConfig/types.ts, src/components/ThemeSwitcher/ThemeSwitcher.tsx, src/components/AuthLayout/AuthLayout.tsx, src/api/axiosInstance.ts, src/contexts/AuthContext.tsx, tailwind.config.js |
12
12
  | [AWS Amplify Build & Deploy (non-prod environments)](workflows/amplify-build-and-deploy.md) | How `toga2-commerce` (React + Vite, "commerce2-react") builds and deploys on **AWS Amplify**. | toga2-commerce/amplify.yml, toga2-commerce/.gitattributes, toga2-commerce/package.json, toga2-commerce/.github/workflows/sync-stage-environments.yml |
@@ -6,14 +6,15 @@ project: TOGa Commerce
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-23
10
- owners: ["apeterson"]
9
+ updated: 2026-07-10
10
+ owners: ["apeterson", "tcox"]
11
11
  files:
12
12
  - src/fieldsConfig/index.ts
13
13
  - src/fieldsConfig/getClientLoginFields.ts
14
14
  - src/fieldsConfig/clientFields/COMPASS.json
15
15
  - src/fieldsConfig/clientFields/COMPASSCANADA.json
16
16
  - src/fieldsConfig/clientFields/QUAD.json
17
+ - src/pages/Cart/api/CartApi.ts
17
18
  - src/hooks/useAssignClientFields.ts
18
19
  - src/hooks/useDynamicConditionalFieldOptions.ts
19
20
  - src/stores/useFieldsStore.ts
@@ -43,7 +44,8 @@ resolved per tenant, per language, and per role**. There are **two layers**:
43
44
  ```json
44
45
  {
45
46
  "languageSwitcher": { "uuid": "1", "isEnabled": true|false },
46
- "userApiFields": ["firstName", "lastName", "email", "uuid", "_isAdmin", ...]
47
+ "userApiFields": ["firstName", "lastName", "email", "uuid", "_isAdmin", ...],
48
+ "fetchUsersApiFields": ["firstName", "lastName", "email", "uuid", ...]
47
49
  }
48
50
  ```
49
51
 
@@ -52,11 +54,31 @@ resolved per tenant, per language, and per role**. There are **two layers**:
52
54
  - `userApiFields` — the allow-list of user columns fetched at login; importantly it includes the
53
55
  tenant's **role flags**: COMPASS/COMPASSCANADA carry `_isAdmin`, `_isSupervisor`, `_isSuperAdmin`;
54
56
  QUAD carries `_isGlobalAdmin`, `_isItShopper`, `_isBuyer`. All include `_personaIds`.
57
+ - `fetchUsersApiFields` — the allow-list of user columns fetched by the **view-as user search**
58
+ (`fetchUsers()` in `src/pages/Cart/api/CartApi.ts`, driven from `NavIconList.tsx`). Distinct from
59
+ `userApiFields` (which is the login fetch). COMPASS/COMPASSCANADA use the 14-field default list;
60
+ **QUAD uses an 8-field list that intentionally omits `supervisorUser.*`** (see gotcha).
55
61
 
56
62
  Resolved by **`src/fieldsConfig/getClientLoginFields.ts`** — a `switch(client)` returning the JSON;
57
63
  **default is `COMPASS`** (not DEFAULT). Called from `AuthContext` as `getClientLoginFields(host)`.
58
64
  The `languageSwitcher.isEnabled` value feeds the login-settings branch in `getLoginSettings.ts`.
59
65
 
66
+ ### `fetchUsersApiFields` and the config-driven view-as search (C6)
67
+
68
+ `fetchUsers()` in `src/pages/Cart/api/CartApi.ts` no longer hardcodes its field list. It resolves it
69
+ via `getClientLoginFields(fieldKey ?? "").fetchUsersApiFields`, replacing a former
70
+ `if (fieldKey === "QUAD")` client-name branch — this honors the Cart "no `if (client === ...)`,
71
+ drive client behavior from config" rule (**C6** in
72
+ [cart-page-config-architecture](cart-page-config-architecture.md)). An unknown/missing `fieldKey`
73
+ falls back to COMPASS inside `getClientLoginFields` (same as the old `else` branch). Because the
74
+ tenant JSONs are **statically imported**, a tenant missing `fetchUsersApiFields` is a TypeScript
75
+ compile error — not a silent empty fetch — so the list is client-editable via config without
76
+ touching Cart code.
77
+
78
+ **Follow-up candidate (not yet done):** `fetchUser()` (singular, same `CartApi.ts`) still holds the
79
+ same hardcoded QUAD-vs-default field-list pattern and is a candidate for the identical config
80
+ treatment in a future ticket.
81
+
60
82
  ## Layer B — the per-page FIELDS registry
61
83
 
62
84
  `src/fieldsConfig/index.ts` (~1100 lines) imports every page's per-tenant field constants (which
@@ -179,10 +201,22 @@ Common field attributes: `uuid`, `label`, `valueKey` (path into the data object)
179
201
  - Role is derived from string `"1"` flags on the user (`user?._isAdmin === "1"`), not booleans.
180
202
  - `getClientLoginFields` defaults to **COMPASS** for an unknown host, which can mask a
181
203
  misconfigured tenant in local dev.
204
+ - **QUAD's `fetchUsersApiFields` omits `supervisorUser.*` on purpose — do not "fix" it to match
205
+ COMPASS.** A code-review bot flagged the mismatch, but it is correct: the original hardcoded QUAD
206
+ `fetchUsers` branch never requested supervisor fields; only `fetchUser()` (the detail fetch) does,
207
+ and the sole `fetchUsers` caller (view-as search in `NavIconList.tsx`) gets full detail from
208
+ `fetchUser()` after selection. QUAD also uses the `_isGlobalAdmin`/`_isBuyer`/`_isItShopper` role
209
+ model, not the supervisor hierarchy. Aligning the two lists would smuggle a behavior change into a
210
+ refactor.
182
211
 
183
212
  ## Change history
184
213
  - 2026-06-23 — Initial: documented the two-layer field system (global `clientFields/<TENANT>.json`
185
214
  + the per-page `FIELDS` registry), the `tenant→language→role` resolution in `useAssignClientFields`,
186
215
  the `en`/`fr` vs `ENGLISH`/`FRENCH` distinction, role derivation per tenant, and field JSON shapes
187
216
  (apeterson)
217
+ - 2026-07-10 — Added the Layer A `fetchUsersApiFields` key: moved the hardcoded `fetchUsers()`
218
+ field lists out of `CartApi.ts` into each tenant JSON (COMPASS/COMPASSCANADA = 14-field default,
219
+ QUAD = 8-field), replacing the `if (fieldKey === "QUAD")` branch with a config lookup (C6). Noted
220
+ the deliberate QUAD `supervisorUser.*` omission and the `fetchUser()` follow-up candidate. Refactor
221
+ only, no behavior change; `tsc --noEmit` clean. (tcox)
188
222
  </content>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.314",
3
+ "version": "1.0.316",
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",