toga-ai 1.0.71 → 1.0.73
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/togadesk/INDEX.md +7 -4
- package/knowledge/1.0/apps/togadesk/architecture.md +133 -14
- package/knowledge/1.0/apps/togadesk/features/email-to-ticket-intake.md +66 -0
- package/knowledge/1.0/apps/togadesk/features/field-service-dispatch.md +76 -0
- package/knowledge/1.0/apps/togadesk/features/rest-api.md +87 -0
- package/knowledge/1.0/apps/togadesk/features/smb-contract-editing.md +5 -5
- package/knowledge/1.0/apps/togadesk/features/ticket-lifecycle.md +3 -3
- package/knowledge/1.0/apps/togadesk/workflows/standalone-test-scripts.md +3 -3
- package/knowledge/1.0/apps/togaview/INDEX.md +5 -4
- package/knowledge/1.0/apps/togaview/architecture.md +108 -11
- package/knowledge/1.0/apps/togaview/features/login-flows.md +2 -2
- package/knowledge/1.0/apps/togaview/features/msp-dashboard.md +3 -3
- package/knowledge/1.0/apps/togaview/features/retail-commerce-lifecycle.md +75 -0
- package/knowledge/1.0/apps/togaview/features/ticket-detail-page.md +1 -1
- package/knowledge/INDEX.md +2 -2
- package/knowledge/clients/compass-usa/INDEX.md +1 -1
- package/knowledge/clients/compass-usa/features/item-fulfillment-tracking-tableview.md +30 -9
- package/knowledge/clients/nycdoe/features/servicenow-integration.md +8 -8
- package/knowledge/clients/nycdoe/profile.md +2 -2
- package/knowledge/registry.json +2 -2
- package/package.json +1 -1
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
# togadesk (
|
|
1
|
+
# togadesk (TOGa Desk) — 1.0 knowledge
|
|
2
2
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
|
-
| [
|
|
5
|
+
| [TOGa Desk Architecture](architecture.md) | TOGa Desk is the staff-facing support desk **and field-service platform** (analysts work at `/desk/`). | desk/index.php, desk/includes/loader.php, desk/config.php, desk/includes/controllers/actions.php, desk/includes/controllers/data.php, desk/includes/controllers/modals.php, desk/includes/classes/class.app.php, desk/includes/classes/class.ticket.php, desk/api/index.php, desk/api/resources/tickets.php, crons/tickets.php |
|
|
6
|
+
| [Email-to-Ticket Intake (crons/tickets.php)](features/email-to-ticket-intake.md) | TOGa Desk ingests support email into tickets through a cron-driven IMAP poller (`crons/tickets.php`) plus a postfix pipe variant (`crons/pipe.php`). | crons/tickets.php, crons/tickets_prod.php, crons/pipe.php, desk/includes/classes/class.ticket.php |
|
|
7
|
+
| [Field-Service Dispatch (central / repair orders)](features/field-service-dispatch.md) | The **central** subsystem is TOGa Desk's field-service dispatch domain: repair-order lifecycle, technician scheduling, onsite vs depot service, parts, and shipm | desk/includes/controllers/actions/central/, desk/includes/classes/class.repair.php, desk/includes/classes/class.repairhistory.php, desk/_/browser/datatable/central.php, desk/template/pages/central.php, desk/template/pages/central/view.php |
|
|
8
|
+
| [REST API (RPC-over-POST) & API-Key Auth](features/rest-api.md) | TOGa Desk exposes a programmatic API at `desk/api/`. | desk/api/index.php, desk/api/resources/tickets.php, desk/api/resources/assets.php, desk/api/resources/authenticate.php, desk/includes/classes/class.apikey.php, desk/includes/functions.php |
|
|
6
9
|
| [SMB Contract Editing & the clientMspId Corruption Trap](features/smb-contract-editing.md) | The SMB contracts page (`/desk/?route=toga/smbcontracts&togaClientId=<id>`) edits `TOGA_*.SMBContracts` rows via a modal. | desk/template/modals/toga/smbcontracts/smbContract.php, desk/includes/controllers/modals/toga/smbcontracts/smbContract.php, desk/includes/controllers/actions/toga/smbcontracts/smbContract.php, desk/includes/controllers/actions/toga/smbcontracts/edit.php |
|
|
7
|
-
| [Ticket Lifecycle (class.ticket.php)](features/ticket-lifecycle.md) | All
|
|
8
|
-
| [Standalone PHP Test Script Bootstrap (
|
|
10
|
+
| [Ticket Lifecycle (class.ticket.php)](features/ticket-lifecycle.md) | All TOGa Desk ticket creation and reply handling funnels through `Ticket` in `desk/includes/classes/class.ticket.php`. | desk/includes/classes/class.ticket.php, desk/includes/controllers/actions.php, desk/api/resources/tickets.php, crons/tickets.php, desk/includes/controllers/actions/tickets/merge.php |
|
|
11
|
+
| [Standalone PHP Test Script Bootstrap (TOGa Desk)](workflows/standalone-test-scripts.md) | How to write a standalone CLI PHP script that bootstraps the TOGa Desk framework for read-only testing of desk classes (e.g. | |
|
|
@@ -1,38 +1,137 @@
|
|
|
1
1
|
---
|
|
2
|
-
title:
|
|
2
|
+
title: TOGa Desk Architecture
|
|
3
3
|
framework: "1.0"
|
|
4
4
|
repo: togadesk
|
|
5
|
-
project:
|
|
5
|
+
project: TOGa Desk
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
10
|
-
owners: ["mhammontree"]
|
|
9
|
+
updated: 2026-06-15
|
|
10
|
+
owners: ["mhammontree", "jcardinal"]
|
|
11
11
|
files:
|
|
12
|
-
- desk/
|
|
12
|
+
- desk/index.php
|
|
13
|
+
- desk/includes/loader.php
|
|
14
|
+
- desk/config.php
|
|
13
15
|
- desk/includes/controllers/actions.php
|
|
16
|
+
- desk/includes/controllers/data.php
|
|
17
|
+
- desk/includes/controllers/modals.php
|
|
18
|
+
- desk/includes/classes/class.app.php
|
|
19
|
+
- desk/includes/classes/class.ticket.php
|
|
20
|
+
- desk/api/index.php
|
|
14
21
|
- desk/api/resources/tickets.php
|
|
15
22
|
- crons/tickets.php
|
|
16
23
|
related:
|
|
17
24
|
- 1.0/apps/togaview/architecture.md
|
|
18
25
|
- 1.0/apps/togadesk/features/ticket-lifecycle.md
|
|
19
26
|
- 1.0/apps/togadesk/features/smb-contract-editing.md
|
|
27
|
+
- 1.0/apps/togadesk/features/rest-api.md
|
|
28
|
+
- 1.0/apps/togadesk/features/field-service-dispatch.md
|
|
29
|
+
- 1.0/apps/togadesk/features/email-to-ticket-intake.md
|
|
30
|
+
- 1.0/apps/togadesk/workflows/standalone-test-scripts.md
|
|
20
31
|
---
|
|
21
32
|
|
|
22
33
|
## Summary
|
|
23
|
-
|
|
24
|
-
(`App_`) app bootstrapped from the `library` core (`_.php`).
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
TOGa Desk is the staff-facing support desk **and field-service platform** (analysts work at
|
|
35
|
+
`/desk/`). It is a framework 1.0 (`App_`) app bootstrapped from the `library` core (`_.php`).
|
|
36
|
+
Beyond ticketing it covers asset/license/inventory management, repair-order dispatch (depot +
|
|
37
|
+
onsite technicians), a knowledge base, host/service monitoring, RBAC, a REST-ish API, and
|
|
38
|
+
IMAP email intake. The on-disk repo root contains legacy top-level code; **the live
|
|
39
|
+
application root is `desk/`** — e.g. the live `class.ticket.php` is
|
|
40
|
+
`desk/includes/classes/class.ticket.php`, while the root `includes/classes/class.ticket.php`
|
|
41
|
+
is an ancient variant that must never be extended.
|
|
42
|
+
|
|
43
|
+
Primary database `db_togadesk` (schema `TOGaDeskSupport`); also `db_common` and per-client
|
|
44
|
+
databases. TOGa Desk shares `TOGaDeskSupport` with **TOGa View** (the client-facing portal,
|
|
45
|
+
sibling repo `togaview`).
|
|
46
|
+
|
|
47
|
+
## Entry points & request pipeline
|
|
48
|
+
- **Web:** `desk/index.php` → `desk/includes/loader.php`. The repo-root `index.php` / `ajax.php`
|
|
49
|
+
are thin wrappers around the `desk/` versions.
|
|
50
|
+
- **Bootstrap order** (`loader.php`): local `functions.php` → `config.php` → register the two
|
|
51
|
+
custom autoloaders `vendorClassAutoload` (`desk/vendor/classes/`) and `appClassAutoload`
|
|
52
|
+
(`desk/includes/classes/`) → Composer autoload → `require _.php` + `App_Framework_TOGaDesk::initialize()`
|
|
53
|
+
(starts session with `HttpOnly`/`Secure`, inits Sentry, XSS-protects `$_REQUEST`/`$_GET`/`$_POST`)
|
|
54
|
+
→ `App_Page::validateCrossSiteRequestForgery()` → Medoo `$database` instantiated from config →
|
|
55
|
+
resolve logged-in user `$liu` via `people.sessionid` → unserialize the user's role `perms`.
|
|
56
|
+
- **`$route`** comes from `$_GET['route']` (defaults to `dashboard`) and is sanitized against
|
|
57
|
+
path traversal.
|
|
58
|
+
- **Sequential controller pipeline**, each stage gated on a query param:
|
|
59
|
+
| Stage | Trigger | File | Role |
|
|
60
|
+
|---|---|---|---|
|
|
61
|
+
| general | always | `controllers/general.php` | signin / signout / password reset |
|
|
62
|
+
| modals | `?modal=` | `controllers/modals.php` | assemble modal form data → `template/modals/<modal>.php` |
|
|
63
|
+
| quickactions | `?qa=` | `controllers/quickactions.php` | small AJAX/JSON/file-stream ops, then `exit` |
|
|
64
|
+
| actions | `?action=` | `controllers/actions.php` | CRUD; `isAuthorized()` → domain class → status code → `reroute()` |
|
|
65
|
+
| data | always | `controllers/data.php` | populate page context; route-specific `controllers/data/<route>.php` |
|
|
66
|
+
- **Render** (when no modal/qa): `template/header.php` + `template/pages/<route>.php` + `template/footer.php`.
|
|
67
|
+
- Unmatched `?action=` values fall back to `controllers/actions/<action>.php` — so actions live
|
|
68
|
+
either inline in `actions.php` or as files under `controllers/actions/<domain>/`.
|
|
69
|
+
|
|
70
|
+
## Server-rendered UI model
|
|
71
|
+
Classic **page ← data + modal + action** loop:
|
|
72
|
+
1. A page (`template/pages/<route>.php`) instantiates a **DataTable**
|
|
73
|
+
(`desk/_/browser/datatable/<domain>.php`, extends `Browser_Datatable_TogaDesk`) that builds
|
|
74
|
+
the grid SQL/joins and renders row action buttons.
|
|
75
|
+
2. A button calls `showM("?modal=<domain>/<action>&id=…")`, which XHR-loads the modal; its modal
|
|
76
|
+
controller (`controllers/modals/<domain>/<action>.php`) assembles dropdowns/dependent data and
|
|
77
|
+
the modal template renders a form with hidden `routeid` / `id` / `reroute` fields.
|
|
78
|
+
3. The form POSTs `?action=…` → a domain class static method → a numeric **status code** →
|
|
79
|
+
`reroute()` redirects back to the originating page. Status codes map to user-facing messages
|
|
80
|
+
via the `statuscodes` table (`10`=add, `20`=edit, `30`=delete, etc.).
|
|
81
|
+
|
|
82
|
+
## Domain class layer
|
|
83
|
+
- ~41 thin classes in `desk/includes/classes/class.<name>.php` with **plain names** (`Ticket`,
|
|
84
|
+
`Asset`, `Repair`, `Client`…), all extending an **empty `App` base** (`class.app.php`) —
|
|
85
|
+
behavior lives in **static facade methods** (`Ticket::add($data)`), not inheritance or DI.
|
|
86
|
+
- Persistence pairs these with **`App_Model_TogaDesk_*` ORM models** (defined in `library`),
|
|
87
|
+
instantiated by id (`new App_Model_TogaDesk_Ticket($id)`).
|
|
88
|
+
- **DB access is mixed:** the Medoo global `$database` (`->select/insert/get`) for simple work,
|
|
89
|
+
and `App_Database::query()` / `App_Database::fetchRow()` for complex SQL. Link names:
|
|
90
|
+
`db_togadesk` (main), `db_common` (shared: States, carriers, ASN), and per-client databases via
|
|
91
|
+
`App_Model_Client::changeDbLinkToClientDatabase()`.
|
|
92
|
+
- **Functional groups:**
|
|
93
|
+
- **Ticketing/support** — `Ticket` (the ~2,200-line core: creation, the `addReply()` status
|
|
94
|
+
switch, `deriveCustomerId()`, Talos AI, policy tasks), `TicketHistory`, `Comment`,
|
|
95
|
+
`Resolution`, `Preply`.
|
|
96
|
+
- **Asset & inventory** — `Asset`, `License`, `Category`, `Attribute`, `CustomField`,
|
|
97
|
+
`Certificate`, `Credential` (encrypted).
|
|
98
|
+
- **Repair & dispatch** — `Repair` (depot + onsite work orders), `RepairHistory`.
|
|
99
|
+
- **Client/contact** — `Client`, `ClientDepartment`, `ClientPortal`, `Contact`.
|
|
100
|
+
- **Users & RBAC** — `User`/`Staff` (both in `people`, by `type`), `Profile`, `Role`,
|
|
101
|
+
`ApiKey`, `Policy`. Permissions are stored **serialized** in `roles.perms`.
|
|
102
|
+
- **Knowledge base** — `Kb` (articles/docs/FAQs; uploads to S3 `asifiles`).
|
|
103
|
+
- **Projects** — `Project`, `Issue`, `Milestone`. **Monitoring** — `Monitoring`, `Diagnostic`.
|
|
104
|
+
- **Comms** — `Notification`, `ChatWidget`, `Timelog`. **Data** — `Import`, `File`.
|
|
105
|
+
|
|
106
|
+
## REST-ish API
|
|
107
|
+
`desk/api/index.php` is an **RPC-over-POST** dispatcher (NOT verb/URL REST): params `key`,
|
|
108
|
+
`method` (get/add/edit/delete/attach/detach), `resource`, `filters`, `data`, `id` route to one
|
|
109
|
+
of ~37 handlers in `desk/api/resources/<resource>.php`. API-key auth resolves a role → perms;
|
|
110
|
+
each op calls `isAuthorizedApi(...)`. Responses use the `{status, status_message, result}`
|
|
111
|
+
envelope. See the **REST API** feature doc.
|
|
112
|
+
|
|
113
|
+
## Background jobs & integrations
|
|
114
|
+
Crons live at the **repo root** (`crons/`), bootstrapped via the library framework +
|
|
115
|
+
`App_Framework::cronInitialization()`, with per-environment wrappers (`*_prod.php` etc.):
|
|
116
|
+
- `tickets.php` — IMAP **email→ticket** intake from O365 (OAuth2 in prod). See the
|
|
117
|
+
**email-to-ticket intake** feature doc.
|
|
118
|
+
- `monitoring.php` — `Monitoring::runAll()` host/port checks + up/down alerts.
|
|
119
|
+
- `pipe.php` — postfix stdin push variant; `general.php` — stub.
|
|
120
|
+
|
|
121
|
+
External integrations: **Talos/TogaIQ** AI (ticket summaries / improved replies), **Syncro**
|
|
122
|
+
PSA status push (when `tickets.referenceId='SYNCRO'`), **S3** (`asifiles`,
|
|
123
|
+
`App_Cloud::copyFileToS3()`), **Sentry** (client-side JS), **PHPMailer** SMTP, and **NetSuite**
|
|
124
|
+
customer id captured on SMB contracts. The `controllers/actions/toga/` and
|
|
125
|
+
`controllers/data/toga/` subsystems bridge to the **TOGA** database (inventory items/levels/orders,
|
|
126
|
+
SMB contracts, customer renewals).
|
|
28
127
|
|
|
29
128
|
## System topology
|
|
30
129
|
| System | Serves | DB connections |
|
|
31
130
|
|---|---|---|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
131
|
+
| TOGa Desk (`desk/`) | Staff/analysts | `db_togadesk` (TOGaDeskSupport), `db_toga` (TOGA), `db_client_aig`, per-client by name |
|
|
132
|
+
| TOGa View (sibling repo) | Clients at `<client>.togaview.com` | same cluster, same link names |
|
|
34
133
|
|
|
35
|
-
- **
|
|
134
|
+
- **TOGa Desk and TOGa View share the `TOGaDeskSupport` database** — tickets, clients,
|
|
36
135
|
contacts, departments, ClientMsp all live there. A change to ticket data semantics affects
|
|
37
136
|
both apps.
|
|
38
137
|
- All legacy databases (TOGaDeskSupport, TOGA, TOGA_*, RetailServices) are on the same MySQL
|
|
@@ -78,10 +177,30 @@ Invariants:
|
|
|
78
177
|
(`crons/tickets.php` IMAP poll → `emailToTicket()`), merge. See ticket-lifecycle feature doc.
|
|
79
178
|
- SMB contract administration edits TOGA_* `SMBContracts` from `/desk/?route=toga/smbcontracts`.
|
|
80
179
|
See smb-contract-editing feature doc.
|
|
180
|
+
- Field-service dispatch (repair orders, technician scheduling, onsite/depot, tracking) runs
|
|
181
|
+
through `controllers/actions/central/` + `class.repair.php`. See field-service-dispatch doc.
|
|
81
182
|
|
|
82
183
|
## Key decisions
|
|
83
184
|
- `Ticket::deriveCustomerId()` (June 2026) resolves `tickets.customerid` at creation so
|
|
84
|
-
staff/API/email tickets are visible in the
|
|
185
|
+
staff/API/email tickets are visible in the TOGa View MSP portal; it returns null rather than
|
|
85
186
|
guess when ambiguous, and swallows (logs) exceptions so email intake is never blocked.
|
|
86
187
|
- DB access for tooling: legacy cluster via the TOGa Database Integration MCP, environment
|
|
87
188
|
`legacy`; schemas `TOGaDeskSupport`, `TOGA`, `TOGA_True`, etc.
|
|
189
|
+
|
|
190
|
+
## Gotchas / known issues
|
|
191
|
+
- `ENVIRONMENT` defaults to `worker` when unset → CLI scripts read the wrong config file.
|
|
192
|
+
- Heavy **hardcoded client-ID branching** in `central` dispatch and `tickets/add` (Walmart,
|
|
193
|
+
WJE, Compass/ODP, AIG, RUMCSI) — verify the client arm before changing shared logic.
|
|
194
|
+
- **Two `class.ticket.php` files** — only `desk/includes/classes/` is live; the root variant is
|
|
195
|
+
dead (random ticket numbers, stale schema).
|
|
196
|
+
- The API has **no pagination** — `get` returns full result sets; Medoo `filters` pass straight
|
|
197
|
+
to the WHERE clause.
|
|
198
|
+
- The prod O365 OAuth secret used by `crons/tickets_prod.php` has a **hard expiry (~2026-12-20)**.
|
|
199
|
+
|
|
200
|
+
## Related docs
|
|
201
|
+
- [TOGa View architecture](../togaview/architecture.md)
|
|
202
|
+
- [Ticket lifecycle](features/ticket-lifecycle.md)
|
|
203
|
+
- [REST API](features/rest-api.md)
|
|
204
|
+
- [Field-service dispatch (central)](features/field-service-dispatch.md)
|
|
205
|
+
- [Email-to-ticket intake](features/email-to-ticket-intake.md)
|
|
206
|
+
- [SMB contract editing](features/smb-contract-editing.md)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Email-to-Ticket Intake (crons/tickets.php)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: togadesk
|
|
5
|
+
project: TOGa Desk
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-15
|
|
10
|
+
owners: ["jcardinal"]
|
|
11
|
+
files:
|
|
12
|
+
- crons/tickets.php
|
|
13
|
+
- crons/tickets_prod.php
|
|
14
|
+
- crons/pipe.php
|
|
15
|
+
- desk/includes/classes/class.ticket.php
|
|
16
|
+
related:
|
|
17
|
+
- 1.0/apps/togadesk/architecture.md
|
|
18
|
+
- 1.0/apps/togadesk/features/ticket-lifecycle.md
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Summary
|
|
22
|
+
TOGa Desk ingests support email into tickets through a cron-driven IMAP poller
|
|
23
|
+
(`crons/tickets.php`) plus a postfix pipe variant (`crons/pipe.php`). Both funnel into
|
|
24
|
+
`Ticket::emailToTicket()`, which threads replies onto existing tickets or creates new ones.
|
|
25
|
+
|
|
26
|
+
## Key files / entry points
|
|
27
|
+
- `crons/tickets.php` — the IMAP poll loop. Per-environment wrappers (`tickets_prod.php`,
|
|
28
|
+
`tickets_alpha.php`, `tickets_beta.php`) set `$env`/`$processName` and `require` it; the base
|
|
29
|
+
script refuses to run unwrapped.
|
|
30
|
+
- `crons/pipe.php` — reads a single message from stdin (postfix pipe delivery) and calls the
|
|
31
|
+
same intake; minimal autoload bootstrap (no full framework include path).
|
|
32
|
+
- `Ticket::emailToTicket()` in `desk/includes/classes/class.ticket.php` — the parser/router.
|
|
33
|
+
|
|
34
|
+
## How it works
|
|
35
|
+
1. **Bootstrap:** cron wrappers load the `library` framework + `App_Framework::cronInitialization()`,
|
|
36
|
+
set `$env`, and register the IMAP (`Imap2`) helper. `tickets_prod.php` additionally performs
|
|
37
|
+
**OAuth2/MSAL** token acquisition for the O365 mailbox.
|
|
38
|
+
2. **Poll:** connect to `outlook.office365.com:993` INBOX and loop for ~22 minutes (header
|
|
39
|
+
comment suggests running every ~30 min), processing each message then deleting it.
|
|
40
|
+
3. **Parse & route (`emailToTicket()`):** extract from/to/cc/subject/body (with encoding fixes);
|
|
41
|
+
match an existing ticket via `In-Reply-To` + `Message-ID` headers and **plus-addressing**
|
|
42
|
+
(`support+<ticketnum>@…`). Matched ⇒ `addReply()`; otherwise ⇒ `add()`.
|
|
43
|
+
4. **Side effects:** auto-create a user when the sender domain maps to an existing client; assign
|
|
44
|
+
to a department via the department email lookup; save attachments locally then push to **S3**
|
|
45
|
+
(`App_Cloud::copyFileToS3()`, bucket `asifiles`); optionally generate Talos/TogaIQ summaries.
|
|
46
|
+
5. **Post-pass:** `Ticket::processRules()` (escalation) and `Ticket::autoClose()` (SLA auto-close)
|
|
47
|
+
run after the mailbox sweep.
|
|
48
|
+
|
|
49
|
+
## Data model
|
|
50
|
+
Writes `tickets`, `tickets_replies`, attachment `files`; reads config from the `config` table
|
|
51
|
+
(`tickets_defaultdepartment`, `tickets_defaultclient`, `auto_close_tickets`,
|
|
52
|
+
`email_from_address`, `timezone`). Department routing keys on `tickets_departments.email`.
|
|
53
|
+
|
|
54
|
+
## Gotchas / known issues
|
|
55
|
+
- **`$processName` guard:** `tickets.php` throws if executed directly instead of via an env
|
|
56
|
+
wrapper — and `ENVIRONMENT`/`$env` must be set or it loads the wrong config.
|
|
57
|
+
- **OAuth2 expiry:** the prod O365 app secret in `tickets_prod.php` has a hard expiry
|
|
58
|
+
(~2026-12-20); rotate before then or intake silently stops.
|
|
59
|
+
- **Loop prevention** only excludes FROM==department-TO addresses; CC/reply-all across multiple
|
|
60
|
+
department addresses can still loop.
|
|
61
|
+
- **Auto-reply filters** are partly hardcoded (e.g. ignore `TechHub@compass-usa.com`
|
|
62
|
+
"Automatic reply:" messages).
|
|
63
|
+
- Attachments are deleted locally immediately after the S3 upload with **no retry** if S3 fails.
|
|
64
|
+
|
|
65
|
+
## Change history
|
|
66
|
+
- 2026-06-15 — documented from a source read of `crons/tickets*.php` and `emailToTicket()` (jcardinal)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Field-Service Dispatch (central / repair orders)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: togadesk
|
|
5
|
+
project: TOGa Desk
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-15
|
|
10
|
+
owners: ["jcardinal"]
|
|
11
|
+
files:
|
|
12
|
+
- desk/includes/controllers/actions/central/
|
|
13
|
+
- desk/includes/classes/class.repair.php
|
|
14
|
+
- desk/includes/classes/class.repairhistory.php
|
|
15
|
+
- desk/_/browser/datatable/central.php
|
|
16
|
+
- desk/template/pages/central.php
|
|
17
|
+
- desk/template/pages/central/view.php
|
|
18
|
+
related:
|
|
19
|
+
- 1.0/apps/togadesk/architecture.md
|
|
20
|
+
- 1.0/apps/togadesk/features/ticket-lifecycle.md
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Summary
|
|
24
|
+
The **central** subsystem is TOGa Desk's field-service dispatch domain: repair-order lifecycle,
|
|
25
|
+
technician scheduling, onsite vs depot service, parts, and shipment tracking. It is the largest
|
|
26
|
+
action domain (~23 files under `controllers/actions/central/`) and is heavily client-aware.
|
|
27
|
+
|
|
28
|
+
## Key files / entry points
|
|
29
|
+
- Action controllers: `desk/includes/controllers/actions/central/*.php` — repair-order create
|
|
30
|
+
/clone, status transitions (`awaitingDispatch.php`, `awaitingDiagnostic.php`,
|
|
31
|
+
`installStarted.php`, `reviewInstallation.php`, `reworkAwaitingScheduling.php`), technician
|
|
32
|
+
assignment & scheduling (`assignTechnicians.php`, `scheduleDispatch.php`, `setSchedule.php`),
|
|
33
|
+
onsite state (`technicianEnRoute.php`, `technicianOnsite.php`), parts/tracking
|
|
34
|
+
(`part.php`, `addTracking.php`, `voidshipment.php`), and bulk ops
|
|
35
|
+
(`bulkCloseInstallTickets.php`, `bulkEditOnsiteServiceTickets.php`).
|
|
36
|
+
- Domain class: `desk/includes/classes/class.repair.php` (`Repair`) with the
|
|
37
|
+
`App_Model_TogaDesk_RepairOrder` / `RepairOrderCustomer` / `RepairOrderAddress` models.
|
|
38
|
+
- Grid: `desk/_/browser/datatable/central.php` — joins `repair_orders`
|
|
39
|
+
+ `repair_order_customers` + `ServiceRequests` + departments, renders status badges and the
|
|
40
|
+
per-row action buttons that open the central modals.
|
|
41
|
+
- Pages: `desk/template/pages/central.php` (list) and `central/view.php` (detail).
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
A page renders the central DataTable; row buttons `showM("?modal=central/<action>&id=…")` open a
|
|
45
|
+
modal whose controller (`controllers/modals/central/<action>.php`) assembles technicians,
|
|
46
|
+
contracts, vendor POs, schedule data, etc. The modal form POSTs `?action=<central action>`,
|
|
47
|
+
which resolves to the inline `actions.php` switch or to
|
|
48
|
+
`controllers/actions/central/<action>.php`, mutates `repair_orders` / `repair_order_technicians`
|
|
49
|
+
and calls `Repair`/`RepairOrder::updateStatus()` to recompute state, then `reroute()`s back to
|
|
50
|
+
the originating page. Status changes are audited through `RepairHistory` (`repair_history`).
|
|
51
|
+
|
|
52
|
+
**State model (typical):** awaiting diagnostic → awaiting dispatch → scheduled → en-route →
|
|
53
|
+
onsite/install started → review → closed; depot repairs add parts/tracking steps. Bulk actions
|
|
54
|
+
exist for closing installs and editing onsite tickets.
|
|
55
|
+
|
|
56
|
+
## Data model
|
|
57
|
+
`repair_orders`, `repair_order_customers`, `repair_order_addresses`, `repair_order_technicians`,
|
|
58
|
+
`repair_history` (in `db_togadesk`), joined to TOGA `ServiceRequests` for managed-service-order
|
|
59
|
+
context. One repair order generally maps to one serviced unit.
|
|
60
|
+
|
|
61
|
+
## Client variations
|
|
62
|
+
Dispatch flows branch on hardcoded client ids (e.g. Walmart / Walmart Plus, WJE, Office
|
|
63
|
+
Depot / Compass, AIG, RUMCSI) for department mapping, scheduling rules, and notification copy —
|
|
64
|
+
always confirm the client arm before changing shared central logic.
|
|
65
|
+
|
|
66
|
+
## Gotchas / known issues
|
|
67
|
+
- Actions are split across two locations: the inline `actions.php` switch **and**
|
|
68
|
+
`controllers/actions/central/*.php` (the default fallback). Check both when tracing an action.
|
|
69
|
+
- Status recomputation is centralized in `updateStatus()` — write paths that bypass it can leave
|
|
70
|
+
an order in an inconsistent state.
|
|
71
|
+
- Cross-database context: some central data controllers switch the DB link to a client database
|
|
72
|
+
(`App_Model_Client::changeDbLinkToClientDatabase()`).
|
|
73
|
+
|
|
74
|
+
## Change history
|
|
75
|
+
- 2026-06-15 — documented from a source read of `controllers/actions/central/` and
|
|
76
|
+
`class.repair.php` (jcardinal)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: REST API (RPC-over-POST) & API-Key Auth
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: togadesk
|
|
5
|
+
project: TOGa Desk
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-15
|
|
10
|
+
owners: ["jcardinal"]
|
|
11
|
+
files:
|
|
12
|
+
- desk/api/index.php
|
|
13
|
+
- desk/api/resources/tickets.php
|
|
14
|
+
- desk/api/resources/assets.php
|
|
15
|
+
- desk/api/resources/authenticate.php
|
|
16
|
+
- desk/includes/classes/class.apikey.php
|
|
17
|
+
- desk/includes/functions.php
|
|
18
|
+
related:
|
|
19
|
+
- 1.0/apps/togadesk/architecture.md
|
|
20
|
+
- 1.0/apps/togadesk/features/ticket-lifecycle.md
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Summary
|
|
24
|
+
TOGa Desk exposes a programmatic API at `desk/api/`. Despite the name it is **not** verb/URL
|
|
25
|
+
REST — it is an **RPC-over-POST** dispatcher: every request is a POST to `desk/api/index.php`
|
|
26
|
+
carrying a `resource` and a `method` in the body. ~37 resource handlers under
|
|
27
|
+
`desk/api/resources/` delegate to the same domain classes the web UI uses.
|
|
28
|
+
|
|
29
|
+
## Key files / entry points
|
|
30
|
+
- `desk/api/index.php` — the single entry point: authenticates, validates the request shape,
|
|
31
|
+
and `include`s the matching `resources/<resource>.php` (handlers run in index.php scope).
|
|
32
|
+
- `desk/api/resources/*.php` — one file per resource (`tickets`, `ticket_replies`,
|
|
33
|
+
`ticket_departments`, `assets`, `asset_categories`, `clients`, `contacts`, `credentials`,
|
|
34
|
+
`licenses`, `users`, `staff`, `roles`, `kb_articles`, `monitoring_hosts`,
|
|
35
|
+
`monitoring_checks`, `time_log`, `files`, `qrcodes`, lookup/reference resources, etc.).
|
|
36
|
+
- `desk/includes/classes/class.apikey.php` — API key generation/storage.
|
|
37
|
+
- `isAuthorizedApi()` and `signInApi()` in `desk/includes/functions.php`.
|
|
38
|
+
|
|
39
|
+
## How it works
|
|
40
|
+
**Request (POST to `desk/api/index.php`):**
|
|
41
|
+
- `key` — the API secret (required).
|
|
42
|
+
- `method` — one of `get | add | edit | delete | attach | detach`.
|
|
43
|
+
- `resource` — e.g. `tickets`, `assets`, `users`.
|
|
44
|
+
- `filters` — array, for `get` (passed to Medoo as the WHERE clause).
|
|
45
|
+
- `data` — array, for `add`/`edit`/`attach`/`detach`.
|
|
46
|
+
- `id` — string, for `delete`/`detach`.
|
|
47
|
+
|
|
48
|
+
**Auth & authorization:**
|
|
49
|
+
1. `index.php` looks up `api_keys` by `secretkey`; invalid ⇒ status `903`.
|
|
50
|
+
2. The key's `roleid` loads `roles.perms` (unserialized PHP array).
|
|
51
|
+
3. Each handler calls `isAuthorizedApi("<permission>")` (e.g. `viewTickets`, `addAsset`,
|
|
52
|
+
`manageSettings`); missing permission ⇒ status `905` + `exit`.
|
|
53
|
+
4. `resources/authenticate.php` is a **separate** path: user/password login via `signInApi()`
|
|
54
|
+
(not API-key auth).
|
|
55
|
+
|
|
56
|
+
**Response envelope (always JSON):**
|
|
57
|
+
```json
|
|
58
|
+
{ "status": 1, "status_message": "Success!...", "result": [ ... ] }
|
|
59
|
+
```
|
|
60
|
+
Handlers post-process rows: serialized fields (`tickets.ccs`, `roles.perms`) are unserialized
|
|
61
|
+
and encrypted fields (license `serial`) are decrypted before returning.
|
|
62
|
+
|
|
63
|
+
**Status codes:** `1` success, `2` generic error; `901` unknown, `902` key missing, `903` auth
|
|
64
|
+
failed, `904` resource not found, `905` not authorized, `906` method not found, `907` method not
|
|
65
|
+
allowed for resource, `908–912` malformed `filters`/`data`/`id`. Domain-class return codes
|
|
66
|
+
(`10` add / `20` edit / `30` delete) are mapped to `1`/`2` in the handler.
|
|
67
|
+
|
|
68
|
+
**Delegation:** handlers call the same static facades as the UI — e.g. `tickets.php` →
|
|
69
|
+
`Ticket::add()/edit()/delete()`, `ticket_replies.php` → `Ticket::addReply()` (add-only),
|
|
70
|
+
`files.php` → `File::upload($data, $_FILES)`, `qrcodes.php` uses `attach`/`detach`. For `get`,
|
|
71
|
+
many resources query Medoo directly (`$database->select(...)`).
|
|
72
|
+
|
|
73
|
+
## Data model
|
|
74
|
+
Backed by the same `db_togadesk` (`TOGaDeskSupport`) tables as the UI, plus `api_keys`
|
|
75
|
+
(`name`, `secretkey`, `roleid`) and `roles` (`perms` serialized).
|
|
76
|
+
|
|
77
|
+
## Gotchas / known issues
|
|
78
|
+
- **No pagination.** `get` returns the full result set; large tables (tickets, assets) can be
|
|
79
|
+
expensive. `filters` is passed straight into the Medoo WHERE clause.
|
|
80
|
+
- **RPC, not REST.** Clients send `method`/`resource` in the POST body — there are no per-verb
|
|
81
|
+
HTTP semantics and no `/api/tickets/123` URLs.
|
|
82
|
+
- **Per-resource permission strings** must exist in the API key's role `perms`, or every call
|
|
83
|
+
returns `905`.
|
|
84
|
+
- Minimal request validation in `index.php`; real validation lives in the domain classes.
|
|
85
|
+
|
|
86
|
+
## Change history
|
|
87
|
+
- 2026-06-15 — documented from a full source read of `desk/api/` (jcardinal)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
title: SMB Contract Editing & the clientMspId Corruption Trap
|
|
3
3
|
framework: "1.0"
|
|
4
4
|
repo: togadesk
|
|
5
|
-
project:
|
|
5
|
+
project: TOGa Desk
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
@@ -22,7 +22,7 @@ related:
|
|
|
22
22
|
The SMB contracts page (`/desk/?route=toga/smbcontracts&togaClientId=<id>`) edits
|
|
23
23
|
`TOGA_*.SMBContracts` rows via a modal. Because multiple TOGA clients can share one TOGA_*
|
|
24
24
|
database, this page historically corrupted `clientMspId` on save — the root cause of MSP
|
|
25
|
-
portal users seeing "You have no assigned clients" in
|
|
25
|
+
portal users seeing "You have no assigned clients" in TOGa View. Fixed June 2026.
|
|
26
26
|
|
|
27
27
|
## Key files / entry points
|
|
28
28
|
- Modal template: `desk/template/modals/toga/smbcontracts/smbContract.php`
|
|
@@ -40,7 +40,7 @@ The bug pattern (now guarded against):
|
|
|
40
40
|
2. The modal's MSP dropdown was scoped to client A's `ClientMsp` rows; the contract's real MSP
|
|
41
41
|
wasn't an option; no option got `selected`; **the browser silently auto-selects the first
|
|
42
42
|
option**; saving wrote the wrong `clientMspId`.
|
|
43
|
-
3.
|
|
43
|
+
3. TOGa View msp_dashboard's `SMBContracts WHERE clientMspId = <session clientMspId>` then
|
|
44
44
|
matched nothing for the real MSP's users.
|
|
45
45
|
|
|
46
46
|
Fixes in place (June 2026):
|
|
@@ -80,11 +80,11 @@ client 20 (`TOGA_True`), SMBContract 230 (Customer 932297).
|
|
|
80
80
|
## Gotchas / known issues
|
|
81
81
|
- `dateContractEnd` in the past is display-only — it does not hide tickets in the MSP portal.
|
|
82
82
|
- `ClientMsp.ticketDepartmentId` holds only ONE department even when the client has several —
|
|
83
|
-
never build department logic on it (see
|
|
83
|
+
never build department logic on it (see TOGa View msp-dashboard doc).
|
|
84
84
|
|
|
85
85
|
## Change history
|
|
86
86
|
- 2026-06-12 — documented; modal + action guards shipped to togadesk dev branch (mhammontree)
|
|
87
87
|
|
|
88
88
|
## Related docs
|
|
89
89
|
- [Ticket lifecycle](ticket-lifecycle.md)
|
|
90
|
-
- [
|
|
90
|
+
- [TOGa View MSP dashboard](../../togaview/features/msp-dashboard.md)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
title: Ticket Lifecycle (class.ticket.php)
|
|
3
3
|
framework: "1.0"
|
|
4
4
|
repo: togadesk
|
|
5
|
-
project:
|
|
5
|
+
project: TOGa Desk
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
@@ -20,7 +20,7 @@ related:
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
22
|
## Summary
|
|
23
|
-
All
|
|
23
|
+
All TOGa Desk ticket creation and reply handling funnels through `Ticket` in
|
|
24
24
|
`desk/includes/classes/class.ticket.php`. This doc covers the creation paths, the
|
|
25
25
|
`deriveCustomerId()` resolution added June 2026, and the `addReply()` status-transition
|
|
26
26
|
switch — including its known gaps.
|
|
@@ -99,4 +99,4 @@ backfill (e.g. craftex:
|
|
|
99
99
|
|
|
100
100
|
## Related docs
|
|
101
101
|
- [SMB contract editing](smb-contract-editing.md)
|
|
102
|
-
- [
|
|
102
|
+
- [TOGa View MSP dashboard](../../togaview/features/msp-dashboard.md)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Standalone PHP Test Script Bootstrap (
|
|
2
|
+
title: Standalone PHP Test Script Bootstrap (TOGa Desk)
|
|
3
3
|
framework: "1.0"
|
|
4
4
|
repo: togadesk
|
|
5
|
-
project:
|
|
5
|
+
project: TOGa Desk
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
@@ -14,7 +14,7 @@ related:
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
## Summary
|
|
17
|
-
How to write a standalone CLI PHP script that bootstraps the
|
|
17
|
+
How to write a standalone CLI PHP script that bootstraps the TOGa Desk framework for
|
|
18
18
|
read-only testing of desk classes (e.g. `Ticket::deriveCustomerId()`), without running the
|
|
19
19
|
web stack. Example harness: `C:\WWW\test\@Mark\TOGaDeskSupport\test_derive_customer_id.php`
|
|
20
20
|
(local path — adapt per machine).
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
# togaview (
|
|
1
|
+
# togaview (TOGa View) — 1.0 knowledge
|
|
2
2
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
|
-
| [
|
|
6
|
-
| [
|
|
7
|
-
| [MSP Dashboard & Ticket Visibility Rules](features/msp-dashboard.md) | Why tickets "disappear" in the
|
|
5
|
+
| [TOGa View Architecture](architecture.md) | TOGa View is the client-facing support portal, served per client at `<client>.togaview.com`. | index.php, reset.php, _/app/framework.php, _/app/frameworkindex.php, _/browser/pagination.php, mvc/login/post.php, mvc/ticket/get.php |
|
|
6
|
+
| [TOGa View Login Flows & Session Variables](features/login-flows.md) | `mvc/login/post.php` tries login flows in order; the first match wins. | mvc/login/post.php, _/app/framework.php |
|
|
7
|
+
| [MSP Dashboard & Ticket Visibility Rules](features/msp-dashboard.md) | Why tickets "disappear" in the TOGa View client portal: different pages scope tickets **differently**, and the MSP pages depend on `tickets.customerid` and `SMB | common/togaview/msp_dashboard.php, mvc/msp_client_dashboard, mvc/enterprise_dashboard, mvc/support/support.php |
|
|
8
|
+
| [Retail Commerce Lifecycle (winback, purchase, claims, activation)](features/retail-commerce-lifecycle.md) | Beyond support, TOGa View hosts the **retail tech-support commerce flows**: subscription purchase/renewal (with payment), service activation, and insurance-styl | mvc/winback_landing/get.php, mvc/winback_payment/get.php, mvc/winback_payment/post.php, mvc/purchase/get.php, mvc/service_activation/get.php, mvc/claims/get.php, mvc/techsupport_app/get.php |
|
|
8
9
|
| [Ticket Detail Page Security (common/togaview/ticket.php)](features/ticket-detail-page.md) | `common/togaview/ticket.php` is the ticket detail page for nearly ALL hosts — only towfoundation/newcenturyholdingsllc have their own variants; every other clie | common/togaview/ticket.php |
|
|
@@ -1,37 +1,60 @@
|
|
|
1
1
|
---
|
|
2
|
-
title:
|
|
2
|
+
title: TOGa View Architecture
|
|
3
3
|
framework: "1.0"
|
|
4
4
|
repo: togaview
|
|
5
|
-
project:
|
|
5
|
+
project: TOGa View
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
10
|
-
owners: ["mhammontree"]
|
|
9
|
+
updated: 2026-06-15
|
|
10
|
+
owners: ["mhammontree", "jcardinal"]
|
|
11
11
|
files:
|
|
12
12
|
- index.php
|
|
13
|
+
- reset.php
|
|
13
14
|
- _/app/framework.php
|
|
15
|
+
- _/app/frameworkindex.php
|
|
16
|
+
- _/browser/pagination.php
|
|
14
17
|
- mvc/login/post.php
|
|
18
|
+
- mvc/ticket/get.php
|
|
15
19
|
related:
|
|
16
20
|
- 1.0/apps/togadesk/architecture.md
|
|
17
21
|
- 1.0/apps/togaview/features/login-flows.md
|
|
18
22
|
- 1.0/apps/togaview/features/msp-dashboard.md
|
|
19
23
|
- 1.0/apps/togaview/features/ticket-detail-page.md
|
|
24
|
+
- 1.0/apps/togaview/features/retail-commerce-lifecycle.md
|
|
20
25
|
---
|
|
21
26
|
|
|
22
27
|
## Summary
|
|
23
|
-
|
|
28
|
+
TOGa View is the client-facing support portal, served per client at `<client>.togaview.com`.
|
|
24
29
|
Framework 1.0 (`App_`) app bootstrapped from the `library` core (`_.php`). It shares the
|
|
25
|
-
`TOGaDeskSupport` database with
|
|
26
|
-
— see the
|
|
30
|
+
`TOGaDeskSupport` database with TOGa Desk (tickets, clients, contacts, departments, ClientMsp)
|
|
31
|
+
— see the TOGa Desk architecture doc for the cluster topology and the full MSP data model;
|
|
27
32
|
those invariants apply here unchanged.
|
|
28
33
|
|
|
34
|
+
Architecturally it is a **multi-tenant, single-codebase MVC portal**: one deploy serves every
|
|
35
|
+
client, and `$_SERVER['HTTP_HOST']` selects a per-client *stylePath* (templates/branding) at
|
|
36
|
+
bootstrap. Pages are procedural `mvc/<page>/{get,post}.php` scripts; most clients render the
|
|
37
|
+
generic `common/togaview/*.php` templates, while a handful have full per-client overrides.
|
|
38
|
+
|
|
39
|
+
## Entry points & bootstrap
|
|
40
|
+
- **Entry:** `index.php` → `require _.php` (autoloader from `library`) →
|
|
41
|
+
`App_Framework_TogaView::initialize()` → `renderIndex()` (in `_/app/framework.php`).
|
|
42
|
+
`reset.php` is a small session-clear utility.
|
|
43
|
+
- **`initialize()`** (extends `App_Framework`): resolves `ENVIRONMENT` (defaults `worker`),
|
|
44
|
+
loads `config.<env>.ini` via `App_Config`, registers DB links (`db_togadesk` →
|
|
45
|
+
`[database_togadesk]`, plus `db_toga`, `db_retail`, `db_log`, per-client), XSS-protects
|
|
46
|
+
superglobals, starts the session (`HttpOnly`/`Secure`), inits Sentry, and registers the
|
|
47
|
+
NetSuite toolkit + SAML.
|
|
48
|
+
- **`frameworkindex.php`** is the page-shell renderer (CSS/JS registration, page title).
|
|
49
|
+
**`framework_DOA.php`** is a near-duplicate variant of `framework.php`.
|
|
50
|
+
|
|
29
51
|
## Page routing (stylePath model)
|
|
30
52
|
`index.php → App_Framework_TogaView::renderIndex()` (in `_/app/framework.php`):
|
|
31
53
|
|
|
32
54
|
1. A giant `switch ($_SERVER['HTTP_HOST'])` sets `$_SESSION['stylePath']` (and sometimes
|
|
33
55
|
`$_SESSION['host']`). Client subdomains are **hardcoded** — a new client subdomain must be
|
|
34
56
|
added to this switch (e.g. `craftex.togaview.com` → stylePath `togatechnology`, no `host`).
|
|
57
|
+
The default stylePath is `true`.
|
|
35
58
|
2. Page files: `mvc/<page>/get.php` typically resolves the template as:
|
|
36
59
|
- host in `HOSTS_USING_TOGA_TECH_STYLE` (= `['towfoundation','newcenturyholdingsllc']`)
|
|
37
60
|
→ `common/<host>/<page>.php`;
|
|
@@ -44,6 +67,24 @@ those invariants apply here unchanged.
|
|
|
44
67
|
**Implication:** a change to `common/togaview/<page>.php` affects nearly every client at
|
|
45
68
|
once; a per-client behavior change belongs in a host/stylePath variant or an inline branch.
|
|
46
69
|
|
|
70
|
+
## MVC request lifecycle
|
|
71
|
+
- `App_MVC::parseRoute()` (in `library`) turns the request URI into route parts and resolves
|
|
72
|
+
`mvc/<route…>/<method>.php`, where `<method>` is `get` or `post` (from the HTTP verb,
|
|
73
|
+
overridable via a `_method` POST param). It matches longest-to-shortest, so overlapping
|
|
74
|
+
paths can surprise — keep route folders distinct.
|
|
75
|
+
- **GET** (`get.php`) selects a template via the fallback chain above and passes data to it
|
|
76
|
+
(no central master-layout system — each `common/<…>/<page>.php` is self-contained).
|
|
77
|
+
- **POST** (`post.php`) reads input with `App_Page::getVar()/getVarIfSet()`, does the work,
|
|
78
|
+
then `header("Location: …"); exit;`. Login/auth, ticket creation, user management live here.
|
|
79
|
+
- AJAX requests dispatch through `App_Framework::ajaxHandler()`; `?fileId=` downloads route
|
|
80
|
+
through `App_MVC::loadFile()`. `mvc/_TEMPLATE/` is the scaffold for new pages.
|
|
81
|
+
|
|
82
|
+
## Browser subsystem (grids & modals)
|
|
83
|
+
`_/browser/`: `Browser_Datatable_*` classes (`initialize()` registers fields/joins/DB link via
|
|
84
|
+
`setDatabaseLinkString()` / `setTableJoins()`, `Browser_DataTableField` for cell rendering),
|
|
85
|
+
`Browser_Modal_*` classes (capture HTML via `App_Page::startCapture()/stopCapture()`, e.g.
|
|
86
|
+
`Browser_Modal_CreateNewTicket`, `CreatenewUser`), and `Browser_Pagination_TogaView`.
|
|
87
|
+
|
|
47
88
|
## Session / identity model
|
|
48
89
|
Login (`mvc/login/post.php`) tries flows in order — Retail, MSP contact, SMB admin, end user,
|
|
49
90
|
plus inline client branches (staplesprotection) and SAML clients handled in `framework.php`
|
|
@@ -52,12 +93,49 @@ staplesprotection sets `$_SESSION['email']` (not `emailAddress`) and no `togades
|
|
|
52
93
|
Full matrix: login-flows feature doc. Any page reading session identity must tolerate the
|
|
53
94
|
divergent shapes (read `emailAddress` with `email` fallback).
|
|
54
95
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
96
|
+
**SAML** (rumcsi, towfoundation, newcenturyholdingsllc) is handled in `framework.php`
|
|
97
|
+
(`meta`/`acs`/`sls`); newcenturyholdingsllc and towfoundation **auto-create `people` rows** for
|
|
98
|
+
unknown SAML users. rumcsi additionally accepts a legacy `?hash=<base64 email>` auto-login.
|
|
99
|
+
|
|
100
|
+
## Dashboards
|
|
101
|
+
Five dashboards, one per audience/business model, each delegating to a client override or the
|
|
102
|
+
generic template:
|
|
103
|
+
- **retail_dashboard** — retail warranty/subscription end users (Walmart+, ODP): profile,
|
|
104
|
+
software locker, support app download, service activation status.
|
|
105
|
+
- **msp_dashboard** — MSP admins managing many SMB customers: per-customer ticket metrics,
|
|
106
|
+
seat/contract capacity, Excel/Charts export (see msp-dashboard feature doc).
|
|
107
|
+
- **msp_client_dashboard** — end users under an MSP-managed SMB contract: seat capacity, add-user
|
|
108
|
+
(disabled when oversubscribed), resend welcome.
|
|
109
|
+
- **enterprise_dashboard** — enterprise/insurance clients (rumcsi, staplesprotection,
|
|
110
|
+
towfoundation): client-specific logic incl. claims and asset tracking.
|
|
111
|
+
- **wb2b_dashboard** — Walmart B2B (hardcoded `TOGA_WalmartB2B`, clientId 13): managed services
|
|
112
|
+
+ activation status.
|
|
113
|
+
|
|
114
|
+
## Self-service identity lifecycle
|
|
115
|
+
`signup` / `register` / `activate` (retail, token + encrypted `clientAccountId` link, password
|
|
116
|
+
policy), `forgot_password` / `reset_password`, and the MSP/enterprise side: `create_account`,
|
|
117
|
+
`manage_users`, `msp_user`, `user_account`, `activation_admin` (WalmartBusiness bulk admin
|
|
118
|
+
creation). Models: `App_Model_RetailServices_ClientAccount`, `App_Model_Toga_Contact`,
|
|
119
|
+
`App_Model_Customer`.
|
|
120
|
+
|
|
121
|
+
## Knowledge / content surface
|
|
122
|
+
Read-only client-facing KB sourced from TOGa Desk's `db_togadesk` KB tables, gated behind
|
|
123
|
+
`$_SESSION['togadeskClientId']`: `article` / `article_list` (`kb_articles`), `faq` / `faq_list`
|
|
124
|
+
(`kb_faqs`), `doc` / `doc_list` (`kb_documents`, file download via `?fileId=`).
|
|
125
|
+
|
|
126
|
+
## Support / ticketing
|
|
127
|
+
`support` (ticket hub + `Browser_Modal_CreateNewTicket`), `ticket` (detail; ownership-checked —
|
|
128
|
+
see ticket-detail-page feature doc), `task_list` (`issues`), and `service_desk` (Walmart B2B
|
|
129
|
+
service activation against `db_toga` `ServiceRequests`). The MSP portal depends on
|
|
130
|
+
`tickets.customerid` being populated (TOGA `Customers.id`), which TOGa Desk's
|
|
59
131
|
`Ticket::deriveCustomerId()` now sets at creation.
|
|
60
132
|
|
|
133
|
+
## Commerce / retail lifecycle
|
|
134
|
+
`winback_landing` → `winback_payment` → `winback_activating` (ODP renewal), `purchase` /
|
|
135
|
+
`purchase_service`, `service_activation`, `claims` (Staples Protection / AIG), and
|
|
136
|
+
`techsupport_app` (Optimum Desk app download). Payment via **Braintree**. See the
|
|
137
|
+
retail-commerce-lifecycle feature doc.
|
|
138
|
+
|
|
61
139
|
## Key decisions
|
|
62
140
|
- The ticket detail page `common/togaview/ticket.php` enforces an ownership check (clientid
|
|
63
141
|
match OR requester-email match) on both ticket view and attachment download; the email arm
|
|
@@ -65,3 +143,22 @@ depends on `tickets.customerid` being populated (TOGA `Customers.id`), which Tog
|
|
|
65
143
|
semantics require it). See ticket-detail-page feature doc.
|
|
66
144
|
- New client onboarding touches at minimum: the HTTP_HOST switch in `framework.php`, a login
|
|
67
145
|
flow (or SAML branch), and possibly the `smbContractSkus` whitelist in `login/post.php`.
|
|
146
|
+
|
|
147
|
+
## Gotchas / known issues
|
|
148
|
+
- **Template fallback is per-page, not central.** Each `get.php` re-implements the
|
|
149
|
+
host→stylePath→togaview chain; it's easy to miss a host-specific branch.
|
|
150
|
+
- **`common/togaview/*` is the blast radius** — editing a generic page changes nearly every
|
|
151
|
+
client at once.
|
|
152
|
+
- **Divergent session shapes** across login flows (see Session model) — always use the
|
|
153
|
+
`emailAddress`/`email` fallback and tolerate a missing `togadeskClientId`.
|
|
154
|
+
- **Hardcoded Braintree credentials (including a private key) are committed under
|
|
155
|
+
`mvc/winback_payment/`** — these should be rotated and moved to `config.<env>.ini`
|
|
156
|
+
secret management. Flagged 2026-06-15; not yet remediated.
|
|
157
|
+
- `ENVIRONMENT` defaults to `worker` when unset → wrong config file.
|
|
158
|
+
|
|
159
|
+
## Related docs
|
|
160
|
+
- [TOGa Desk architecture](../togadesk/architecture.md)
|
|
161
|
+
- [Login flows & session variables](features/login-flows.md)
|
|
162
|
+
- [MSP dashboard & ticket visibility](features/msp-dashboard.md)
|
|
163
|
+
- [Ticket detail page security](features/ticket-detail-page.md)
|
|
164
|
+
- [Retail commerce lifecycle](features/retail-commerce-lifecycle.md)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
title: MSP Dashboard & Ticket Visibility Rules
|
|
3
3
|
framework: "1.0"
|
|
4
4
|
repo: togaview
|
|
5
|
-
project:
|
|
5
|
+
project: TOGa View
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
@@ -20,7 +20,7 @@ related:
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
22
|
## Summary
|
|
23
|
-
Why tickets "disappear" in the
|
|
23
|
+
Why tickets "disappear" in the TOGa View client portal: different pages scope tickets
|
|
24
24
|
**differently**, and the MSP pages depend on `tickets.customerid` and `SMBContracts`
|
|
25
25
|
integrity. Covers the per-page filters and the June 2026 multi-department stats fix.
|
|
26
26
|
|
|
@@ -80,5 +80,5 @@ category resolution).
|
|
|
80
80
|
|
|
81
81
|
## Related docs
|
|
82
82
|
- [Login flows](login-flows.md)
|
|
83
|
-
- [
|
|
83
|
+
- [TOGa Desk ticket lifecycle](../../togadesk/features/ticket-lifecycle.md)
|
|
84
84
|
- [SMB contract editing](../../togadesk/features/smb-contract-editing.md)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Retail Commerce Lifecycle (winback, purchase, claims, activation)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: togaview
|
|
5
|
+
project: TOGa View
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-15
|
|
10
|
+
owners: ["jcardinal"]
|
|
11
|
+
files:
|
|
12
|
+
- mvc/winback_landing/get.php
|
|
13
|
+
- mvc/winback_payment/get.php
|
|
14
|
+
- mvc/winback_payment/post.php
|
|
15
|
+
- mvc/purchase/get.php
|
|
16
|
+
- mvc/service_activation/get.php
|
|
17
|
+
- mvc/claims/get.php
|
|
18
|
+
- mvc/techsupport_app/get.php
|
|
19
|
+
related:
|
|
20
|
+
- 1.0/apps/togaview/architecture.md
|
|
21
|
+
- 1.0/apps/togaview/features/login-flows.md
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Summary
|
|
25
|
+
Beyond support, TOGa View hosts the **retail tech-support commerce flows**: subscription
|
|
26
|
+
purchase/renewal (with payment), service activation, and insurance-style claims. These are
|
|
27
|
+
client-specific surfaces (chiefly ODP/Office Depot, Walmart B2B, and Staples Protection/AIG)
|
|
28
|
+
that mostly bypass the generic dashboards.
|
|
29
|
+
|
|
30
|
+
## Key files / entry points
|
|
31
|
+
- **Winback (renewal):** `mvc/winback_landing/get.php` → `mvc/winback_payment/get.php` +
|
|
32
|
+
`post.php` → `mvc/winback_activating/`. ODP tech-support renewal (1-year subscription).
|
|
33
|
+
Returning customers arrive via an encrypted email link.
|
|
34
|
+
- **Purchase:** `mvc/purchase/get.php` (routes to `mvc/purchase/pts/`) and
|
|
35
|
+
`mvc/purchase_service/` — subscription purchase.
|
|
36
|
+
- **Service activation:** `mvc/service_activation/get.php` — finalizes remote/on-site service
|
|
37
|
+
against `TOGA_WalmartB2B`; surfaces the desktop support app download and per-`SMBContract`
|
|
38
|
+
support phone.
|
|
39
|
+
- **Claims:** `mvc/claims/get.php` — Staples Protection Plan (AIG) claim submission; looks up
|
|
40
|
+
asset serial / contract, auto-creates a ticket + asset when not found, routes printer vs.
|
|
41
|
+
general equipment.
|
|
42
|
+
- **techsupport_app:** `mvc/techsupport_app/get.php` — Optimum Desk (`dws.optimumdesk.com`)
|
|
43
|
+
download link, service type via query params.
|
|
44
|
+
|
|
45
|
+
## How it works
|
|
46
|
+
The winback flow validates the customer (new vs. returning via encrypted `clientAccountId`),
|
|
47
|
+
collects a Braintree payment nonce, runs the transaction, and on success writes the TOGA
|
|
48
|
+
`Customer` / `Contact` / `ClientAccountService` records that grant the subscription. Claims and
|
|
49
|
+
service-activation flows read/write the relevant per-client TOGA database and create TOGa Desk
|
|
50
|
+
tickets/assets as needed. All are reached as standalone `mvc/` pages, not via a dashboard tab.
|
|
51
|
+
|
|
52
|
+
## Data model
|
|
53
|
+
- Retail: `App_Model_RetailServices_ClientAccount`, `ClientAccountService` (`db_retail`).
|
|
54
|
+
- TOGA-side: `Customers`, `Contacts`, `SMBContracts`/`SMBContractItems`, `ServiceRequests`
|
|
55
|
+
(per-client `TOGA_*` / `TOGA_WalmartB2B`).
|
|
56
|
+
- Claims also touch `db_togadesk` `assets` / `tickets`.
|
|
57
|
+
|
|
58
|
+
## External integrations
|
|
59
|
+
- **Braintree** payment gateway (`assets/braintree/` and `assets/braintree_latest_version/`),
|
|
60
|
+
used by `mvc/winback_payment/`. Two SDK copies are vendored.
|
|
61
|
+
- **Optimum Desk** remote-support app download links.
|
|
62
|
+
|
|
63
|
+
## Gotchas / known issues
|
|
64
|
+
- **SECURITY — hardcoded payment credentials:** Braintree merchant/public/**private** keys are
|
|
65
|
+
committed directly in `mvc/winback_payment/get.php` rather than read from config. Treat the
|
|
66
|
+
committed keys as compromised: rotate them and move all three into `config.<env>.ini`
|
|
67
|
+
(`[external]`/secret section). Flagged 2026-06-15; not yet remediated.
|
|
68
|
+
- **Two vendored Braintree SDKs** (`assets/braintree/` vs `assets/braintree_latest_version/`) —
|
|
69
|
+
confirm which the live flow includes before upgrading.
|
|
70
|
+
- Encrypted email-link parameters (`clientAccountId`) gate returning-customer flows — a bad/old
|
|
71
|
+
link silently falls back to the new-customer path.
|
|
72
|
+
|
|
73
|
+
## Change history
|
|
74
|
+
- 2026-06-15 — documented from a source read of the `mvc/` commerce pages; flagged the
|
|
75
|
+
committed Braintree credentials for rotation (jcardinal)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -6,8 +6,8 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
6
6
|
|
|
7
7
|
- **library** (Library) _(framework core)_ — 4 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
8
|
- **worker** (Worker) — 4 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
|
|
9
|
-
- **togadesk** (
|
|
10
|
-
- **togaview** (
|
|
9
|
+
- **togadesk** (TOGa Desk) — 7 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
|
10
|
+
- **togaview** (TOGa View) — 5 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
|
|
11
11
|
|
|
12
12
|
## 2.0 framework
|
|
13
13
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
| Doc | Framework | Summary | Files |
|
|
4
4
|
|-----|-----------|---------|-------|
|
|
5
5
|
| [Compass ASN → ItemFulfillment Auto-Creation](features/asn-to-item-fulfillment.md) | 2.0 | For Compass USA, posting an AdvanceShippingNotice (ASN) auto-creates the ItemFulfillment (IF) on the upstream SalesOrder. | _underscore/Model/Compass/AdvanceShippingNotice.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client_Compass/2026-06-11 - AsnItemTrackingNumberAcl.sql |
|
|
6
|
-
| [Compass: Item-Fulfillment TableViews (for-sales-order-items & for-sales-orders, tracking via bridge)](features/item-fulfillment-tracking-tableview.md) | 2.0 | Two sibling Compass TableViews in `Client_Compass` display fulfilled items in toga2-supply, both driven by `TableViews` / `TableViewJoins` / `TableViewFields` c | dbchanges2/Client_Compass/2026-06-10 - ItemFulfillmentsForSalesOrderItemsTableView.sql, dbchanges2/Client_Compass/2026-06-11 - ItemFulfillmentsForSalesOrdersTableView.sql |
|
|
6
|
+
| [Compass: Item-Fulfillment TableViews (for-sales-order-items & for-sales-orders, tracking via bridge)](features/item-fulfillment-tracking-tableview.md) | 2.0 | Two sibling Compass TableViews in `Client_Compass` display fulfilled items in toga2-supply, both driven by `TableViews` / `TableViewJoins` / `TableViewFields` c | dbchanges2/Client_Compass/2026-06-10 - ItemFulfillmentsForSalesOrderItemsTableView.sql, dbchanges2/Client_Compass/2026-06-11 - ItemFulfillmentsForSalesOrdersTableView.sql, dbchanges2/Client_Compass/2026-06-15a - FixItemFulfillmentTrackingNumberJoins.sql |
|
|
7
7
|
| [Compass MITS PO → SO Item Linking](features/mits-po-to-so-item-linking.md) | 2.0 | MITS sends Compass inbound Purchase Orders (`POST /v2/purchase-orders`) against a Sales Order (`mitsSalesOrder`). | _underscore/Model/Compass/PurchaseOrder.php |
|
|
8
8
|
| [Compass USA](profile.md) | 2.0 | Compass USA is a TOGA client running a multi-tier supply-chain commerce operation. | |
|
|
9
9
|
| [Compass Order Lifecycle & Data-Integrity Invariants](workflows/order-lifecycle-and-data-integrity.md) | 2.0 | End-to-end map of how a Compass order flows through the `Client_Compass` (2.0) database and the **expected raw-data shape** at each link/ASN/IF level. | |
|
|
@@ -5,11 +5,12 @@ project: _Underscore
|
|
|
5
5
|
client: compass-usa
|
|
6
6
|
type: client-feature
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-06-
|
|
8
|
+
updated: 2026-06-15
|
|
9
9
|
owners: ["jcardinal"]
|
|
10
10
|
files:
|
|
11
11
|
- dbchanges2/Client_Compass/2026-06-10 - ItemFulfillmentsForSalesOrderItemsTableView.sql
|
|
12
12
|
- dbchanges2/Client_Compass/2026-06-11 - ItemFulfillmentsForSalesOrdersTableView.sql
|
|
13
|
+
- dbchanges2/Client_Compass/2026-06-15a - FixItemFulfillmentTrackingNumberJoins.sql
|
|
13
14
|
related:
|
|
14
15
|
- ../../../2.0/apps/_underscore/features/tracking-number-bridges.md
|
|
15
16
|
---
|
|
@@ -35,8 +36,9 @@ Both views are re-rooted at **`ItemFulfillmentItems` (record 29)** so every fulf
|
|
|
35
36
|
with the unit/tracking chain made **OUTER** so missing units no longer drop rows.
|
|
36
37
|
|
|
37
38
|
**View 13 — `item-fulfillments-for-sales-order-items`:**
|
|
38
|
-
- INNER `SalesOrderItems`; OUTER `ItemFulfillmentItemUnits` → OUTER `Units`
|
|
39
|
-
|
|
39
|
+
- INNER `SalesOrderItems`; OUTER `ItemFulfillmentItemUnits` → OUTER `Units` (kept for Serial #
|
|
40
|
+
/ Asset Tag columns); OUTER `ItemFulfillmentItems_TrackingNumbers` (record **318**) joined
|
|
41
|
+
directly onto `ItemFulfillmentItems` → OUTER `TrackingNumbers` → OUTER `ShippingCarriers`.
|
|
40
42
|
- Columns: **Sales Order Line Number, Quantity Fulfilled (`ItemFulfillmentItems.quantity`),
|
|
41
43
|
Outbound Tracking #, Carrier, Serial #, Asset Tag.**
|
|
42
44
|
|
|
@@ -48,14 +50,19 @@ with the unit/tracking chain made **OUTER** so missing units no longer drop rows
|
|
|
48
50
|
- Default sort re-pointed to the line-number field.
|
|
49
51
|
|
|
50
52
|
Stable Core ids used: records 29 IFI, 30 IFIU, 31 Units, 15 SalesOrderItems, 14 SalesOrders,
|
|
51
|
-
28 ItemFulfillments, 21 Items,
|
|
53
|
+
28 ItemFulfillments, 21 Items, **318 IFI-tracking bridge** (item-level), 62 TrackingNumbers,
|
|
54
|
+
9 ShippingCarriers. (Record 319, the IFIU/unit-level bridge, is no longer used for tracking —
|
|
55
|
+
see Change history 2026-06-15.)
|
|
52
56
|
|
|
53
57
|
## Data model
|
|
54
58
|
|
|
55
59
|
Join keys (RecordField ids): 60 SOI.id / 368 IFI.salesOrderItemId; 360 IF.id / 367 IFI.itemFulfillmentId;
|
|
56
60
|
59 SO.id / 254 SOI.salesOrderId; 105 Item.id / 66 SOI.itemId; 374 IFIU.itemFulfillmentItemId / 365 IFI.id;
|
|
57
|
-
236 Unit.id / 375 IFIU.unitId;
|
|
58
|
-
336 TN.id /
|
|
61
|
+
236 Unit.id / 375 IFIU.unitId; **2178 IFI-bridge.itemFulfillmentItemId / 365 IFI.id**;
|
|
62
|
+
336 TN.id / **2179 IFI-bridge.trackingNumberId**; 21 SC.id / 613 TN.shippingCarrierId.
|
|
63
|
+
(Record 318 fields: 2176 id, 2178 itemFulfillmentItemId, 2179 trackingNumberId, 2180
|
|
64
|
+
returnTrackingNumberId. Superseded unit-level keys: 2183 IFIU-bridge.itemFulfillmentItemUnitId
|
|
65
|
+
/ 372 IFIU.id; 2184 IFIU-bridge.trackingNumberId.)
|
|
59
66
|
|
|
60
67
|
## Gotchas / known issues
|
|
61
68
|
|
|
@@ -64,9 +71,22 @@ Join keys (RecordField ids): 60 SOI.id / 368 IFI.salesOrderItemId; 360 IF.id / 3
|
|
|
64
71
|
ItemFulfillmentItemUnits / 0 Units** → view 12 showed "0 records" while the re-rooted sibling
|
|
65
72
|
view 13 showed the items fine. Fix is structural (re-root + OUTER chain), independent of the
|
|
66
73
|
missing-unit data issue.
|
|
67
|
-
- Tracking
|
|
68
|
-
|
|
69
|
-
(
|
|
74
|
+
- **Tracking must be sourced at the ITEM level** via `ItemFulfillmentItems_TrackingNumbers`
|
|
75
|
+
(record 318), joined directly onto `ItemFulfillmentItems` (rec 29). The original re-root
|
|
76
|
+
(2026-06-10/11) left tracking on the **unit-level** bridge `ItemFulfillmentItemUnits_TrackingNumbers`
|
|
77
|
+
(record 319), which is empty for **non-serialized** lines (no `ItemFulfillmentItemUnits` rows) →
|
|
78
|
+
the OUTER join yielded NULL and the tracking column rendered blank even though the data was
|
|
79
|
+
correct. Symptom: SO **MR243437** (id 105730) showed tracking at the order level but not the
|
|
80
|
+
item level; item 266006 had 0 unit rows but a valid `ItemFulfillmentItems_TrackingNumbers` row
|
|
81
|
+
(→ TN 67248). Fixed 2026-06-15.
|
|
82
|
+
- **Compass imports exactly one tracking number per item fulfillment** (NetSuite does not support
|
|
83
|
+
multiple tracking numbers at the unit level; a different tracking number arrives as a separate
|
|
84
|
+
item fulfillment). So the item-level bridge (318) is always the complete source and re-pointing
|
|
85
|
+
away from the unit-level bridge (319) loses nothing.
|
|
86
|
+
- Re-pointing the bridge is **not** a one-column swap: records 318 and 319 have different
|
|
87
|
+
`RecordFields` ids, so `joinRecordId` (319→318), `joinOnRecordFieldId` (2183→2178),
|
|
88
|
+
`parentRecordFieldId` (372→365), and the downstream `TrackingNumbers` join's parent field
|
|
89
|
+
(2184→2179) all change in lockstep.
|
|
70
90
|
- `TableViews.sortPrimaryTableViewFieldId` is a FK to `TableViewFields`; set it NULL before deleting
|
|
71
91
|
the old fields, then re-point via a multi-table UPDATE (not a self-subquery) to avoid MySQL 1093.
|
|
72
92
|
- `TableViewFields` must be deleted before `TableViewJoins` (`tableViewJoinId` FK).
|
|
@@ -78,6 +98,7 @@ Join keys (RecordField ids): 60 SOI.id / 368 IFI.salesOrderItemId; 360 IF.id / 3
|
|
|
78
98
|
Compass-only. The underlying bridge model is shared (see the _underscore feature doc).
|
|
79
99
|
|
|
80
100
|
## Change history
|
|
101
|
+
- 2026-06-15 — Re-pointed tracking from the unit-level bridge (319) to the item-level bridge `ItemFulfillmentItems_TrackingNumbers` (318) in views 12 & 13; fixes blank item-level tracking on non-serialized lines (e.g. SO MR243437). Migration `2026-06-15a`. (jcardinal)
|
|
81
102
|
- 2026-06-11 — Re-rooted `item-fulfillments-for-sales-orders` (id 12) from Units to ItemFulfillmentItems with OUTER unit/tracking chain; fixes 0-records on orders lacking unit breakdown (e.g. SA132740). (jcardinal)
|
|
82
103
|
- 2026-06-10 — Re-rooted `item-fulfillments-for-sales-order-items` (id 13) and moved tracking to the bridge join. (jcardinal)
|
|
83
104
|
|
|
@@ -38,7 +38,7 @@ related:
|
|
|
38
38
|
## Summary
|
|
39
39
|
|
|
40
40
|
The NYCDOE/ServiceNow integration mirrors DOE's ServiceNow tickets (Incidents + RITMs) into
|
|
41
|
-
local tables, turns vendor shipment notices into NetSuite Sales Orders and
|
|
41
|
+
local tables, turns vendor shipment notices into NetSuite Sales Orders and TOGa Desk
|
|
42
42
|
installation repair orders (the **ASN pipeline** — the heart of the integration), and pushes
|
|
43
43
|
ticket state / ETA / proof-of-delivery back to ServiceNow. A parallel EDI surface exchanges
|
|
44
44
|
850 POs and invoices over DOE's SFTP.
|
|
@@ -57,7 +57,7 @@ ticket state / ETA / proof-of-delivery back to ServiceNow. A parallel EDI surfac
|
|
|
57
57
|
| `NYCDOELocations` | Site-id → address mirror (backs ASN ship-to resolution) |
|
|
58
58
|
| `AdvanceShippingNoticeQueue` | Stage-1 landing zone; **UNIQUE index `idx_dedupeKey`** |
|
|
59
59
|
| `AdvanceShippingNotices` / `…Items` / `…Units` | ASN header (vendor+PO) → per-part items (`qtyOrder`) → individual units |
|
|
60
|
-
| `managed_service_orders`, `repair_orders` (`db_togadesk`) |
|
|
60
|
+
| `managed_service_orders`, `repair_orders` (`db_togadesk`) | TOGa Desk-side fulfillment (one MSO per ASN; `nycDoeTicketId` links back to the RITM) |
|
|
61
61
|
|
|
62
62
|
## Integration surfaces (live schedule)
|
|
63
63
|
|
|
@@ -66,11 +66,11 @@ ticket state / ETA / proof-of-delivery back to ServiceNow. A parallel EDI surfac
|
|
|
66
66
|
| Incidents in | `import_inc.php` (500/batch into `NYCDOETickets`) | every 15 min |
|
|
67
67
|
| RITMs in (ASN Stage 1a) | `import_asn.php` via `App_Api_NYCDOEV2::listRequestItems(500)` | every 15 min |
|
|
68
68
|
| Vendor SFTP in (ASN Stage 1b) | `legacy_import_asn.php` — "Will NOT be turned off" | every 5 min |
|
|
69
|
-
| Incident →
|
|
69
|
+
| Incident → TOGa Desk ticket | `process_tickets.php` (client 16; throws if >200 unprocessed — flood valve) | every 15 min |
|
|
70
70
|
| Queue → ASN records (Stage 2) | `legacy_process_asn_queue.php` | every 15 min |
|
|
71
71
|
| NetSuite SO create (Stage 3, **freeze**) | `1_send_asn_to_netsuite.php` | every 2 h at :07 |
|
|
72
72
|
| NetSuite PO/serials (Stage 4) | `2_send_serials_to_netsuite.php` | every 2 h at :27 |
|
|
73
|
-
|
|
|
73
|
+
| TOGa Desk repair orders (Stage 5) | `3_create_installation_ticket.php` | every 5 min |
|
|
74
74
|
| NetSuite Item Fulfillment (Stage 6) | `4_create_ns_item_fulfillment.php` (**paused**, `active: 0`); `5_DOA_…` unscheduled | not running |
|
|
75
75
|
| ETA → ServiceNow | `send_ticket_updates.php` | every 6 min |
|
|
76
76
|
| RITM state → ServiceNow | `send_request_item_updates.php` | every 5 min |
|
|
@@ -98,11 +98,11 @@ Vendor SFTP ───(legacy_import_asn.php, ser+non-ser)─┘ [UNIQUE ded
|
|
|
98
98
|
│ │ │
|
|
99
99
|
│ 1_send_asn_to_netsuite.php 3_create_installation_ticket.php
|
|
100
100
|
│ ▼ ▼
|
|
101
|
-
│ NetSuite Sales Order
|
|
101
|
+
│ NetSuite Sales Order TOGa Desk repair orders (1 per Unit)
|
|
102
102
|
│ ★ FREEZE qtyOrder ★
|
|
103
103
|
│ │
|
|
104
104
|
▼ 2_send_serials_to_netsuite.php
|
|
105
|
-
|
|
105
|
+
TOGa Desk "Receiving Summary" → "#received / #ordered" (denominator = SUM(qtyOrder))
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
- **Stage 1a (API):** vendor detected by string match on `u_asset_mfg` (apple=1, acer=2,
|
|
@@ -128,8 +128,8 @@ Vendor SFTP ───(legacy_import_asn.php, ser+non-ser)─┘ [UNIQUE ded
|
|
|
128
128
|
- **Stage 4:** reconciles the NetSuite **PO** (`netSuiteInternalPurchaseOrderId` — not the
|
|
129
129
|
SO id) and sends serial inventory assignments.
|
|
130
130
|
- **Stage 5:** for units `WHERE togadeskRepairOrderId IS NULL`, reads serials from NetSuite
|
|
131
|
-
item receipts (so
|
|
132
|
-
creates/reuses
|
|
131
|
+
item receipts (so TOGa Desk RO serials are **driven by NetSuite**, not the ASN unit),
|
|
132
|
+
creates/reuses TOGa Desk manufacturers/models/assets/serial-numbers/MSO and creates **a
|
|
133
133
|
new repair_orders row unconditionally per loop iteration** — one RO per Unit (a 50-cable
|
|
134
134
|
line = 1 unit = 1 RO). Existing-serial lookup is scoped **per item row** (`$asnItemId`).
|
|
135
135
|
- **"#/N received" UI** (`togadesk/desk/template/pages/managedserviceorders/view.php`):
|
|
@@ -17,14 +17,14 @@ NYC DOE (New York City Department of Education) is a TOGA client whose entire in
|
|
|
17
17
|
runs in the **1.0 worker tier** (~30 cron scripts under `worker/crons/sync/nycdoe/` and
|
|
18
18
|
`worker/crons/notifications/nycdoe/`, on the `sync` and `notification` worker roles).
|
|
19
19
|
ServiceNow is DOE's system of record for tickets (Incidents and RITMs); TOGA mirrors those
|
|
20
|
-
locally, drives fulfillment through
|
|
20
|
+
locally, drives fulfillment through TOGa Desk and NetSuite, and pushes status / ETA /
|
|
21
21
|
proof-of-delivery back to ServiceNow. A parallel EDI relationship (850 POs in, invoices out)
|
|
22
22
|
runs over DOE's SFTP.
|
|
23
23
|
|
|
24
24
|
## Platforms & data
|
|
25
25
|
- **1.0 worker tier only** — no 2.0 footprint. Local mirror tables live in `db_common`
|
|
26
26
|
(`NYCDOETickets`, `NYCDOELocations`, `AdvanceShippingNotice*`).
|
|
27
|
-
- **
|
|
27
|
+
- **TOGa Desk:** DOE is client id **16** (`db_togadesk`) — repair orders, managed service
|
|
28
28
|
orders, and the "#received / #ordered" Receiving Summary UI.
|
|
29
29
|
- **NetSuite:** Sales Orders, PO reconciliation, item receipts, Item Fulfillments, invoices
|
|
30
30
|
(SuiteTalk toolkit in `library`).
|
package/knowledge/registry.json
CHANGED
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
{ "repo": "toga2-supply", "project": "TOGa Supply", "framework": "2.0", "role": "app", "dependsOn": ["api2"] },
|
|
9
9
|
{ "repo": "saml", "project": "SAML SSO Gateway", "framework": "2.0", "role": "app", "dependsOn": [] },
|
|
10
10
|
{ "repo": "toga2-view", "project": "TOGa View Frontend", "framework": "2.0", "role": "app", "dependsOn": ["api2"] },
|
|
11
|
-
{ "repo": "togadesk", "project": "
|
|
12
|
-
{ "repo": "togaview", "project": "
|
|
11
|
+
{ "repo": "togadesk", "project": "TOGa Desk", "framework": "1.0", "role": "app", "dependsOn": [] },
|
|
12
|
+
{ "repo": "togaview", "project": "TOGa View", "framework": "1.0", "role": "app", "dependsOn": [] }
|
|
13
13
|
]
|
package/package.json
CHANGED