toga-ai 1.0.1045 → 1.0.1047
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 -1
- package/knowledge/1.0/apps/library/features/startech-pcmaticb2b-sync.md +15 -2
- package/knowledge/2.0/apps/_underscore/INDEX.md +1 -0
- package/knowledge/2.0/apps/_underscore/features/sla-engine.md +65 -0
- package/knowledge/2.0/apps/api2/features/api-payload-interceptors.md +4 -1
- package/knowledge/2.0/apps/dbchanges2/features/rerunnable-additive-inserts.md +12 -1
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/imhouston/INDEX.md +1 -1
- package/knowledge/clients/imhouston/profile.md +6 -3
- package/knowledge/clients/pcmaticb2b/features/startech-ticket-sync.md +2 -2
- package/package.json +1 -1
|
@@ -32,6 +32,6 @@
|
|
|
32
32
|
| [NetSuite SuiteQL/REST API Reference](features/netsuite-suiteql-api-reference.md) | Working reference for the Agilant NetSuite REST/SuiteQL integration (`App_Api_Netsuite_Rest`): auth, SuiteQL mechanics, table schemas, type/status codes, perfor |
|
|
33
33
|
| [NetSuite SuiteQL/REST Shim — Field Semantics](features/netsuite-suiteql-rest-shim.md) | Non-obvious NetSuite SuiteQL/REST field semantics behind `App_Api_Netsuite_Rest` shims (signs, `tl.id`==line, `iscogs`, ShipItem cost, `createdFrom`, shim-shape |
|
|
34
34
|
| [NetSuite Sync Alert Monitor (App_SystemMonitor_NetSuiteIntegration)](features/netsuite-sync-alert-monitor.md) | 1.0 monitor `App_SystemMonitor_NetSuiteIntegration` — flags stale per-client NetSuite sync checkpoints (>48h) to ClickUp; open when a sync-stale alert is missin |
|
|
35
|
-
| [Startech PC Matic B2B Sync (library)](features/startech-pcmaticb2b-sync.md) | PC Matic B2B bidirectional ticket sync between TOGaDesk 1.0 and TOGA 2.0 (`App_Api_Toga2`); open for ticket-type maps, StarTech client onboarding,
|
|
35
|
+
| [Startech PC Matic B2B Sync (library)](features/startech-pcmaticb2b-sync.md) | PC Matic B2B bidirectional ticket sync between TOGaDesk 1.0 and TOGA 2.0 (`App_Api_Toga2`); open for ticket-type maps, StarTech client onboarding, the per-clien |
|
|
36
36
|
| [App_Api_Toga2 — TOGa2 API Client & 1.0↔2.0 Sync Bridge](features/toga2-api-client-and-bridge.md) | `App_Api_Toga2` — the 1.0-side client for the TOGa2/api2 REST API + cross-framework sync routines (`send`/`authenticate`, options DSL, `syncWithToga`/`syncWithT |
|
|
37
37
|
| [Transfer order ↔ PO link reconcile (both directions, header + line, from NetSuite)](features/transfer-order-purchase-order-link-reconcile.md) | `App_Api_Toga2::reconcileTransferOrderPurchaseOrderLinks()` makes one TO's PO links (upstream + downstream, header + line) match NetSuite; open before touching |
|
|
@@ -6,18 +6,21 @@ project: Library
|
|
|
6
6
|
client: pcmaticb2b
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-10-16
|
|
10
10
|
owners: [snaredla, mhammontree]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/api/toga2.php
|
|
13
13
|
- library/app/api/startechticket.php
|
|
14
|
+
- library/app/apitransaction.php
|
|
14
15
|
- worker/crons/toga2/startech/common_import_supporting_records.php
|
|
16
|
+
- worker/schedules/cron.worker.sync.json
|
|
15
17
|
related:
|
|
16
18
|
- clients/pcmaticb2b/features/startech-ticket-sync.md
|
|
17
19
|
- clients/pcmaticb2b/profile.md
|
|
20
|
+
- clients/imhouston/profile.md
|
|
18
21
|
---
|
|
19
22
|
|
|
20
|
-
PC Matic B2B bidirectional ticket sync between TOGaDesk 1.0 and TOGA 2.0 (`App_Api_Toga2`); open for ticket-type maps, StarTech client onboarding, and the V1 third leg.
|
|
23
|
+
PC Matic B2B bidirectional ticket sync between TOGaDesk 1.0 and TOGA 2.0 (`App_Api_Toga2`); open for ticket-type maps, StarTech client onboarding, the per-client StarTech login (all MSP clients), an empty "StarTech auth error" in Sentry, and the V1 third leg.
|
|
21
24
|
|
|
22
25
|
## Summary
|
|
23
26
|
|
|
@@ -85,12 +88,22 @@ Imports Startech (Easeedesk V3) config into TOGA 2.0 — users, groups, devices,
|
|
|
85
88
|
- **Groups** come from `dynamic_field_options->data->groups` (keys `ID`/`group_name`) — verified 2026-07-09 returning **16 groups** for company 24412. The earlier claim that this returns empty and groups must come from `/groups_dispatching` was **wrong**: the empty result was caused by **incorrect credentials** in `startechticket.php` (`agilant.api`), not the endpoint. Fixed by using the `pcmaticb2b@startechteam.ro` account. `/groups_dispatching` returned 0 and is not used.
|
|
86
89
|
- **Categories** — `App_Api_StarTechTicket::getCategories($companyId, $ticketTypeIds)` forwards a `ticket_type` filter alongside `company`. **Verified 2026-07-09**: `company=24412&ticket_type=50,54` returns 15 categories (the scoped set); the `ticket_type` param works as intended.
|
|
87
90
|
- Any single upsert failing aborts the run (all `send()` calls throw on API error); the block order is users → groups → devices → categories → ticket types.
|
|
91
|
+
- One wrapper per client (`import_supporting_records_<client>.php` + `sync_togadesk_startech_<client>.php`: pcmaticb2b, imhouston, towfoundation, craftex, apiary), all `*/2` in `cron.worker.sync.json` (~720 runs/day per client).
|
|
92
|
+
|
|
93
|
+
### StarTech login per client (all StarTech clients, verified 2026-10-16)
|
|
94
|
+
|
|
95
|
+
- The StarTech (Easeedesk/OptimumDesk) login comes from **`Core.PartnerApiIdentities`** (`Partners.name='Startech'`, `partnerIdentifier` = StarTech company id), read by `App_Api_Toga2::getPartnerApiIdentity()` over db link `db_toga2core`.
|
|
96
|
+
- `db_toga2core` = `[database_toga2core]` in worker `config.<env>.ini`. Present on worker/alpha/beta/demo/hotfix/sprint/stage/ui; **missing from every `config.dev-*` laptop config**, so the cron stops locally with "no API identity registered". Not a code bug.
|
|
97
|
+
- Company ids: **24412** PC Matic B2B (retail, own login); **24414** IM Houston, **24416** TOW Foundation, **24417** Craftex, **24418** Apiary — these four MSP clients **share ONE login**.
|
|
98
|
+
- Shared login ⇒ tenant isolation depends on sending the company id on every call that accepts it: library `dynamic_field_options` / categories / types send `company`; `_underscore` create_ticket, ticket update and `change_ticket_status` send `ticket_company`. Calls by ticket id (`ticket/{id}`, comments) and global lists (priorities, workflows, statuses) have no company field in the API. Any new StarTech call must pass the company id where the endpoint takes one.
|
|
99
|
+
- Credentials: see the open hardcoded-credentials gotcha in [startech-ticket-sync](../../../../clients/pcmaticb2b/features/startech-ticket-sync.md). Never copy values.
|
|
88
100
|
|
|
89
101
|
## Gotchas
|
|
90
102
|
|
|
91
103
|
- **Do not add a NULL early-return guard in this function.** NULL `c_escalateToStartech` means the ticket was created in TOGaDesk — it must still sync to TOGA 2.0. The Startech gate lives in `_Trait_Startech_Ticket::postPost`, not here.
|
|
92
104
|
- **`SUPPORT REQUEST` with a space** — `TOGA2_TICKET_TYPE_CODE__SUPPORT = 'SUPPORT REQUEST'`. Using `'SUPPORT'` silently breaks type matching.
|
|
93
105
|
- **`$isPcmaticb2bUuid` flag** is used for PC Matic B2B-specific branching throughout `syncToga2TicketIntoTogadesk1Ticket()`.
|
|
106
|
+
- **Sentry `IMPORT_SUPPORTING_RECORDS_<CLIENT>.PHP >>> StarTech auth error: ` with nothing after the colon = network failure, not bad credentials.** `App_Api_StarTechTicket::authenticate()` calls `execute(false)`, which returns false and keeps no cURL error text (10s `setTimeout` timeout, dropped connection, DNS), so response code and body are empty. A real refusal shows an HTTP code + body. These crons set `App_ApiTransaction::$isLoggingEnabled = false`, so there is no API log row either. A single event heals itself on the next 2-min run — no action. Decided 2026-10-16 (WORKER1-4DQ, IM Houston): **no login retry** for one-off blips. Only investigate if events repeat or show an HTTP code.
|
|
94
107
|
|
|
95
108
|
## Related
|
|
96
109
|
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
| [SO↔PO and TO↔PO bridge tables are TWO tables in OPPOSITE directions (upstream vs downstream)](features/sales-order-purchase-order-bridge-direction.md) | The two SO↔PO (and two TO↔PO) bridge tables mean opposite things (upstream = customer PO, downstream = vendor PO); open when an SO/TO↔PO query returns an empty |
|
|
56
56
|
| [Per-client sales-order status filter (Surface FILTER_SET → table meta `filterOptions`)](features/sales-order-status-filter-surface.md) | Per-client status-filter dropdown on the sales-orders table, driven by a Surface `FILTER_SET`; open when the offered statuses are wrong for a tenant or when onb |
|
|
57
57
|
| [Shipping Carriers & Carrier Account Numbers (records, matching, duplication, deletion)](features/shipping-carriers-and-accounts.md) | Per-client `ShippingCarriers` / `ShippingCarrierAccountNumbers` model, how the syncs match/create carrier rows, and the duplication + blank-carrier + pagination |
|
|
58
|
+
| [Ticket SLA engine (_Component_Sla) — two clocks, per-stage effects, client switch + operator recompute](features/sla-engine.md) | How 2.0 ticket SLA due times, pauses and the per-client switch are computed and written (`_Component_Sla`); open when an SLA countdown is wrong, a stage does no |
|
|
58
59
|
| [_String helpers — ASCII-safe HTML entity encoding (and the parseBetween trap)](features/string-html-entity-helpers.md) | `_String` static string utilities: use `encodeToAsciiHtmlEntities` for any dynamic text going into a 2.0 email; open when escaping email text or parsing substri |
|
|
59
60
|
| [Surface FIELD ↔ RecordField binding (maxLength / validationType / isWritable fold-in)](features/surface-field-recordfield-binding.md) | Resolver capability that folds `Core.RecordFields` input-contract metadata (maxLength/validationType/isWritable) onto a surface FIELD element; open when a 2.5 s |
|
|
60
61
|
| [DECLINED 2026-10-08 — re-key Client.SurfaceOverrides from numeric Core ids to uuids](features/surface-overrides-uuid-rekey.md) | **Decision 2026-10-08 (jcardinal): keep numeric ids; no uuid re-key.** The plan below is not scheduled; see the Layer boundary in [surface-resolver](surface-res |
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Ticket SLA engine (_Component_Sla) — two clocks, per-stage effects, client switch + operator recompute
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: _underscore
|
|
5
|
+
project: _Underscore
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-10-16
|
|
10
|
+
owners: [jcardinal]
|
|
11
|
+
files:
|
|
12
|
+
- _underscore/Component/Sla/Sla.php
|
|
13
|
+
- _underscore/Model/Client/Ticket.php
|
|
14
|
+
- _underscore/Model/Client/TicketStage.php
|
|
15
|
+
- _underscore/Model/Client/TicketNote.php
|
|
16
|
+
- _underscore/Model/Core/Client.php
|
|
17
|
+
- dbchanges2/Core/2026-10-16a - SlaReplyClockCore.sql
|
|
18
|
+
- dbchanges2/Client/2026-10-16a - SlaReplyClockFanout.sql
|
|
19
|
+
- dbchanges2/Client_True/2026-10-16a - SlaStageDataRepair.sql
|
|
20
|
+
- dbchanges2/Client_Elite/2026-10-16a - SlaReplyClockOpenStage.sql
|
|
21
|
+
related:
|
|
22
|
+
- ./ticket-status-from-stage.md
|
|
23
|
+
- ../../api2/features/api-payload-interceptors.md
|
|
24
|
+
- ../../api2/features/tickets-api.md
|
|
25
|
+
- ../../dbchanges2/features/rerunnable-additive-inserts.md
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
How 2.0 ticket SLA due times, pauses and the per-client switch are computed and written (`_Component_Sla`); open when an SLA countdown is wrong, a stage does not pause, or you need to recompute a client.
|
|
29
|
+
|
|
30
|
+
## Summary
|
|
31
|
+
|
|
32
|
+
`_Component_Sla` (`_underscore/Component/Sla/Sla.php`) is the ONLY code that writes `Tickets.sla*` / `dtSla*` / `dtFirstResponse`. It is static, shared by every 2.0 app, and called from api2 payload interceptors (not model save hooks). Two clocks — resolution and reply (first response) — each with its own pause bookkeeping, driven by per-stage effects stored as client data.
|
|
33
|
+
|
|
34
|
+
## How it works
|
|
35
|
+
|
|
36
|
+
- **Targets:** `TicketSlas (ticketTypeId, urgencyId)` → minutes (MINUTES ×1, HOURS ×60, DAYS ×1440). Any other unit (e.g. `BUSINESS_DAYS`) = no target. No business-day math.
|
|
37
|
+
- **Due:** `dtCreated + targetMinutes + pausedMinutes`, per clock.
|
|
38
|
+
- **Resolution clock:** effect = `TicketStages.slaEffect` (`RUN`/`PAUSE`/`STOP`, no stage = RUN). Columns `dtSlaPaused`, `slaPausedMinutes`. STOP time is charged like pause time; `_slaState` shows `STOPPED`.
|
|
39
|
+
- **Reply clock:** effect = `TicketStages.slaFirstResponseEffect` (`RUN`/`PAUSE`, column default `PAUSE`, no stage = RUN). Columns `dtSlaFirstResponsePaused`, `slaFirstResponsePausedMinutes`. Seeded RUN only for new / open / reopened stages. Keeps tracking after the first response so stamps stay right.
|
|
40
|
+
- **`resolvePauseState(params)`** is clock-agnostic and called once per clock. Reuse it for the Service Request SLA clocks — do not fork the math.
|
|
41
|
+
- **First response** = `MIN(TicketNotes.dtStamp)` of external notes by a contact other than the ticket's own contact. Authorless notes never count.
|
|
42
|
+
- **Read side:** `_slaTime`, `_slaState`, `_slaPausedAt` (FIELD_SQL on `_Model_Client_Ticket`). `_slaPausedAt` returns the paused stamp of the clock being shown (reply while unanswered and a reply due exists, else resolution). The Desk countdown reads it, not the raw columns.
|
|
43
|
+
- **Time = DB clock** (`SELECT NOW()` on the client writer), read once, then advanced by PHP elapsed time.
|
|
44
|
+
- **Client switch:** `Core.Clients.isSlaEnabled`. OFF = every SLA column NULL. Flipping it in `PUT /v2/clients/{uuid}` runs `applyClientSwitch` (backfill, skips STOP-stage tickets when turning on).
|
|
45
|
+
- **Operator recompute (no OFF/ON):** `PUT /v2/clients/{uuid}` body `{"recomputeSla": true}`. `_Model_Core_Client::prePut` strips the key (not a column), requires boolean `true`, parks it on `$api->passThrough`; `postPut` runs `recomputeClient` (all tickets, STOP included). Allowed only for callers who can WRITE `Clients.isSlaEnabled` (same field ACL as the V2 write gate). A body that is empty after the strip becomes `{}` in V2.php and `postPut` still runs. Pattern: [command keys in interceptors](../../api2/features/api-payload-interceptors.md#passing-state-from-a-pre-hook-to-a-post-hook).
|
|
46
|
+
- **One run per client:** the shared backfill takes `GET_LOCK('sla-recompute-<clientId>', 0)` on the SAME connection as its writes, `RELEASE_LOCK` in `finally`. Lock busy → `_Exception_Validation` 400. Any failure throws, so the API write rolls back. Reuse this shape for any "only one at a time per client" operation.
|
|
47
|
+
- **Cross-client:** the switch/recompute path uses `DB_TARGET_CLIENT` (`registerTargetClientDatabase`), never the caller's `DB_CLIENT`. Core and client data are never joined.
|
|
48
|
+
|
|
49
|
+
## Gotchas
|
|
50
|
+
|
|
51
|
+
- **⚠ A stage that "never pauses" is usually data, not code.** Check `TicketStages.slaEffect` / `slaFirstResponseEffect` per stage in that environment first. QA alpha `Client_True` (2026-10-16): every stage stayed RUN because the `Client/2026-09-29d` seed matched by slug before the slugs existed; fixed by `Client_True/2026-10-16a - SlaStageDataRepair.sql`. See [fan-out seed keyed on a slug](../../dbchanges2/features/rerunnable-additive-inserts.md#gotchas).
|
|
52
|
+
- **⚠ The lock is released before api2 commits.** A second recompute started right after can overlap the first's uncommitted writes. Accepted (operator-only, rare) — do not copy this gap into a high-traffic operation; hold the lock past commit there.
|
|
53
|
+
- **No worker job for big clients** (decided). The recompute runs inside the request, so a very large client can hit the 300 s request cap. Paged at 500 tickets.
|
|
54
|
+
- **`FIELD_LIST` does not validate ENUMs** — `TicketStage::save()` checks both effects explicitly. Keep that check when adding a value.
|
|
55
|
+
- **Changing a stage's effect does not move existing tickets** until their next write or a recompute. Run `{"recomputeSla": true}` after a stage-effect data fix.
|
|
56
|
+
|
|
57
|
+
## Related
|
|
58
|
+
|
|
59
|
+
- [Ticket status from stage](./ticket-status-from-stage.md)
|
|
60
|
+
- [API payload interceptors](../../api2/features/api-payload-interceptors.md)
|
|
61
|
+
- [Tickets API](../../api2/features/tickets-api.md)
|
|
62
|
+
- [Re-runnable additive migrations](../../dbchanges2/features/rerunnable-additive-inserts.md)
|
|
63
|
+
|
|
64
|
+
## Change history
|
|
65
|
+
- 2026-10-16 — Reply clock gets its own per-stage effect (`slaFirstResponseEffect`, pauses outside new/open/reopened); operator recompute via `{"recomputeSla": true}`; per-client named lock (TRUE-82849). (jcardinal)
|
|
@@ -6,7 +6,7 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-10-
|
|
9
|
+
updated: 2026-10-16
|
|
10
10
|
owners: ["mhammontree", "dfranks", "bala", "snaredla", "jcardinal", "rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
@@ -254,6 +254,8 @@ A `postPut`/`postPost` runs after the save, so it cannot compare old vs new valu
|
|
|
254
254
|
|
|
255
255
|
Main use: **fire a POST side effect only on a REAL field change.** Edit forms resend unchanged fields (TOGa Commerce resends `contact` on every order save), so "key present in `$api->httpPayload`" is not "value changed". In `prePut`, read the current DB value, compare to the payload, store a bool; `postPut` acts only when it is true. Both hooks need their own interceptor row — if the PRE row is missing, the key is absent, so fall back to the old check rather than silently skipping. Reference: `_Model_Compass_SalesOrder::rememberWhetherOrderForPersonIsChanging()` (TRUE-82727, see [Compass approval-decision flow](../../../clients/compass-usa/features/approval-decision-flow.md)). Also used to carry a non-column field past PRE: Rate's `PASSTHROUGH_KEY_FULFILLMENT_PREFERENCE` ([AIG contract creation](../../../clients/rate/features/aig-contract-creation.md)).
|
|
256
256
|
|
|
257
|
+
**Command keys** (a non-column payload key that asks for an action): `prePut` strips the key (api2 would reject an unknown column), validates the exact value, parks it on `$api->passThrough`; `postPut` runs the action. Gate it on write access to a real field (field ACL). A body that is empty after the strip becomes `{}` in V2.php and `postPut` still runs. Reference: `{"recomputeSla": true}` on `PUT /v2/clients/{uuid}` ([SLA engine](../../_underscore/features/sla-engine.md)).
|
|
258
|
+
|
|
257
259
|
### Own-row guards in a PRE interceptor
|
|
258
260
|
|
|
259
261
|
A PRE hook that limits a user to their own rows must close four bypasses — each found by review on the `user-global-settings` guard (TRUE-82613):
|
|
@@ -322,3 +324,4 @@ Refuse API-key callers when the rule is "the signed-in user" (no user on the tok
|
|
|
322
324
|
- 2026-10-08 — `internalApiRequest` "[]" throw also fires on the OUTER request's warnings (e.g. WO-1); throw site now ~L2436; Core + client duplicate rows fire twice. (rgirish)
|
|
323
325
|
- 2026-10-09 — Own-row guards in PRE hooks (alias keys + parent-route bypasses, PRE before field ACL); user-global-settings reference; known open holes. (jcardinal)
|
|
324
326
|
- 2026-10-15 — PRE → POST state hand-off via `$api->passThrough`; detect a real field change, not a resent one (TRUE-82727). (bala)
|
|
327
|
+
- 2026-10-16 — Command keys: strip in `prePut`, park on `passThrough`, act in `postPut`; empty body → `{}` (TRUE-82849). (jcardinal)
|
|
@@ -6,7 +6,7 @@ project: Database Changes
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-10-
|
|
9
|
+
updated: 2026-10-16
|
|
10
10
|
owners: [bala, kyalamarthi, apeterson, ajean, jcardinal, rgirish]
|
|
11
11
|
files:
|
|
12
12
|
- dbchanges2/Client_Compass/2026-08-26a - CompassCreativeStudioPersona.sql
|
|
@@ -18,7 +18,9 @@ files:
|
|
|
18
18
|
- dbchanges2/Client_Compass/2026-09-21a - BackfillUpstreamItemFulfillmentUnits.sql
|
|
19
19
|
- dbchanges2/Client_Compass/2026-09-23a - BundlesTableViewFieldFlags.sql
|
|
20
20
|
- dbchanges2/Client_Compass/2026-09-24a - DeduplicateBundleItemPrices.sql
|
|
21
|
+
- dbchanges2/Client_True/2026-10-16a - SlaStageDataRepair.sql
|
|
21
22
|
related:
|
|
23
|
+
- ../../_underscore/features/sla-engine.md
|
|
22
24
|
- ../architecture.md
|
|
23
25
|
- ../../worker2/features/netsuite-salesorder-open-orders-sync.md
|
|
24
26
|
- ../../_underscore/features/acl-permission-chain.md
|
|
@@ -534,12 +536,21 @@ an INSERT that names only `uuid`/`tableViewId`/`recordFieldId`/`isVisible`/`inde
|
|
|
534
536
|
and a nullable FK column takes it, leaving a structurally broken row that looks inserted. Prefer a
|
|
535
537
|
`JOIN` (so a missing lookup inserts **no row** at all) or add the lookup to the guard's
|
|
536
538
|
`WHERE EXISTS`.
|
|
539
|
+
- **⚠ A `Client/` fan-out seed keyed on a slug that ANOTHER folder sets can silently no-op.** Files in
|
|
540
|
+
`Client/` and `Client_<Name>/` run in no fixed order across folders, so a fan-out `UPDATE … WHERE slug
|
|
541
|
+
= '…'` that runs before the client file back-fills the slugs matches zero rows and reports success.
|
|
542
|
+
Found 2026-10-16: `Client/2026-09-29d` left every QA alpha `Client_True` stage `slaEffect = RUN`
|
|
543
|
+
(Resolved/Closed slugs NULL); fixed forward by `Client_True/2026-10-16a - SlaStageDataRepair.sql`.
|
|
544
|
+
Rule: in the same file, first back-fill the slug from the stable code (guarded), then seed by slug;
|
|
545
|
+
end with a verify SELECT and check it per environment. See [SLA engine](../../_underscore/features/sla-engine.md).
|
|
537
546
|
- **`Core.RecordFields` has NO `sql` column.** A calculated field's SQL is **not** stored in
|
|
538
547
|
metadata — it lives in the `_underscore` model as a `FIELD_SQL` static method. Do not go looking
|
|
539
548
|
for a migration to change a calculated field's expression; there is none. See
|
|
540
549
|
[FIELD_SQL calculated fields](../../_underscore/features/calculated-sql-fields.md).
|
|
541
550
|
|
|
542
551
|
## Change history
|
|
552
|
+
- 2026-10-16 — Fan-out seed keyed on a slug another folder sets can silently no-op; back-fill slugs
|
|
553
|
+
first, verify per environment (TRUE-82849). (jcardinal)
|
|
543
554
|
- 2026-10-01 — Added the `_Model` + column-DEFAULT trap: `_Model` writes every declared field, so an
|
|
544
555
|
explicit NULL skips a `NOT NULL DEFAULT` clause and the insert dies 1048; a unique key over a
|
|
545
556
|
nullable column enforces nothing, so the model default must ship with the ALTER
|
package/knowledge/INDEX.md
CHANGED
|
@@ -21,7 +21,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
21
21
|
|
|
22
22
|
## 2.0 framework
|
|
23
23
|
|
|
24
|
-
- **_underscore** (_Underscore) _(framework core)_ —
|
|
24
|
+
- **_underscore** (_Underscore) _(framework core)_ — 100 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
25
25
|
- **worker2** (Worker) — 87 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
26
26
|
- **api2** (API) — 26 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
27
27
|
- **dbchanges2** (Database Changes) _(framework core)_ — 22 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Framework | Summary |
|
|
4
4
|
|-----|-----------|---------|
|
|
5
|
-
| [IM Houston](profile.md) | 1.0 | IM Houston TOGa Desk client (togadesk `clients.id` 167, `CLIENT_IMHOUSTON`); open for its togadesk-specific rules. |
|
|
5
|
+
| [IM Houston](profile.md) | 1.0 | IM Houston TOGa Desk client (togadesk `clients.id` 167, `CLIENT_IMHOUSTON`); open for its togadesk-specific rules and its StarTech MSP sync. |
|
|
@@ -4,25 +4,28 @@ framework: "1.0"
|
|
|
4
4
|
apps:
|
|
5
5
|
- togadesk
|
|
6
6
|
- library
|
|
7
|
+
- worker
|
|
7
8
|
project: TOGa Desk
|
|
8
9
|
client: imhouston
|
|
9
10
|
type: profile
|
|
10
11
|
status: active
|
|
11
|
-
updated: 2026-10-
|
|
12
|
-
owners: ["mhammontree"]
|
|
12
|
+
updated: 2026-10-16
|
|
13
|
+
owners: ["mhammontree", "snaredla"]
|
|
13
14
|
files: []
|
|
14
15
|
related:
|
|
15
16
|
- ../../1.0/apps/togadesk/features/ticket-assign-to-lists.md
|
|
16
17
|
- ../../1.0/apps/togadesk/features/per-client-host-restriction.md
|
|
18
|
+
- ../../1.0/apps/library/features/startech-pcmaticb2b-sync.md
|
|
17
19
|
---
|
|
18
20
|
|
|
19
|
-
IM Houston TOGa Desk client (togadesk `clients.id` 167, `CLIENT_IMHOUSTON`); open for its togadesk-specific rules.
|
|
21
|
+
IM Houston TOGa Desk client (togadesk `clients.id` 167, `CLIENT_IMHOUSTON`); open for its togadesk-specific rules and its StarTech MSP sync.
|
|
20
22
|
|
|
21
23
|
## Summary
|
|
22
24
|
|
|
23
25
|
- TOGa Desk (1.0) client: `TOGaDeskSupport.clients.id` **167**, constant `CLIENT_IMHOUSTON` in `desk/includes/functions.php`.
|
|
24
26
|
- Has its own branded TOGa Desk host — see [per-client-host-restriction](../../1.0/apps/togadesk/features/per-client-host-restriction.md).
|
|
25
27
|
- Also a 2.0 tenant (`Client_Imhouston`); 2.0 scope not yet added to `apps`.
|
|
28
|
+
- StarTech MSP client: company id **24414**, worker crons `toga2/startech/*_imhouston.php`; shares one StarTech login with the other MSP clients — see [StarTech login per client](../../1.0/apps/library/features/startech-pcmaticb2b-sync.md).
|
|
26
29
|
|
|
27
30
|
## Rules
|
|
28
31
|
|
|
@@ -5,7 +5,7 @@ project: TOGa
|
|
|
5
5
|
client: pcmaticb2b
|
|
6
6
|
type: client-feature
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-10-
|
|
8
|
+
updated: 2026-10-16
|
|
9
9
|
owners: [snaredla, jcardinal, mhammontree]
|
|
10
10
|
files:
|
|
11
11
|
- worker/crons/toga2/startech/sync_togadesk_startech_pcmaticb2b.php
|
|
@@ -177,7 +177,7 @@ const TOGADESK_TICKET_TYPE__TOGADESK_PCS = 'TOGaDesk-PCS';
|
|
|
177
177
|
- **Webhook must set `c_escalateToStartech=0`** — without this, the `postPost` gate evaluates `!null = true` and would skip Startech for webhook-originated tickets on any subsequent PUT.
|
|
178
178
|
- **ticketType uses `c_startechTicketTypeId` dynamically** — stored on `TicketTypes`, not hardcoded in the worker.
|
|
179
179
|
|
|
180
|
-
- **⚠ OPEN (found 2026-10-05,
|
|
180
|
+
- **⚠ OPEN (found 2026-10-05, still open 2026-10-16): Startech credentials are exposed.** (1) `_underscore/Component/Api/Startech/Startech.php:6-7` **and** `library/app/api/startechticket.php:6-7` hardcode `STARTECH_API_USERNAME` / `STARTECH_API_PASSWORD` (shared fallback) in committed source — rotate, then move to Secrets Manager. Deleting the constants alone is not enough (a cleanup PR was drafted 2026-10-16 and dropped). (2) `Core.PartnerApiIdentities.partnerApiPassword` holds the Startech password in plaintext and is a STRING RecordField on record 305, so the records API can read it. New partners use Secrets Manager instead (see [8x8 survey import](../../../2.0/apps/worker2/features/8x8-post-call-survey-import.md)).
|
|
181
181
|
|
|
182
182
|
### Sync prerequisites & gotchas (2026-07-06)
|
|
183
183
|
- **The entitlement gate blocks the 2.0 create.** A TOGaDesk ticket only syncs up to TOGA 2.0 (and can then reach Startech) if its email maps to an existing 2.0 contact **with an active entitlement**. `_Model_Pcmaticb2b_Ticket::prePost` rejects the create otherwise, so the desk ticket never gets a `referenceId` — and the escalate handler shows "Ticket is not linked to TOGa 2.0".
|
package/package.json
CHANGED