toga-ai 1.0.302 → 1.0.303

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@
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
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
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, desk/template/modals/central/addTracking.php, library/app/model/togadesk/repairorder.php, library/app/model/togadesk/repairordertracking.php, library/app/api/carrier/ups.php |
8
+ | [Managed Service Order Create Flow (New MSO modal → tickets/add)](features/managed-service-order-create.md) | The **Managed Service Order (MSO) create flow** is how a TOGa Desk user manually creates a managed service order: pick an End User (or a Client Location), choos | desk/includes/controllers/actions/tickets/add.php, desk/template/modals/tickets/addNew.php, desk/template/pages/managed.php, desk/template/pages/getinfo.php, desk/template/footer.php, library/app/model/togadesk/repairorder.php |
8
9
  | [Ticket Email Notifications (notifications table)](features/notifications.md) | Which TOGa Desk emails fire for a given client is driven **entirely by data**, not code: the `TOGaDeskSupport.notifications` table holds one row per `(clientid, | desk/includes/classes/class.notification.php, crons/tickets.php, crons/tickets_prod.php |
9
10
  | [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 |
10
11
  | [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 |
@@ -0,0 +1,109 @@
1
+ ---
2
+ title: Managed Service Order Create Flow (New MSO modal → tickets/add)
3
+ framework: "1.0"
4
+ repo: togadesk
5
+ project: TOGa Desk
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-07-09
10
+ owners: ["mhammontree"]
11
+ files:
12
+ - desk/includes/controllers/actions/tickets/add.php
13
+ - desk/template/modals/tickets/addNew.php
14
+ - desk/template/pages/managed.php
15
+ - desk/template/pages/getinfo.php
16
+ - desk/template/footer.php
17
+ - library/app/model/togadesk/repairorder.php
18
+ related:
19
+ - 1.0/apps/togadesk/architecture.md
20
+ - 1.0/apps/togadesk/features/field-service-dispatch.md
21
+ - 1.0/apps/togadesk/features/ticket-lifecycle.md
22
+ ---
23
+
24
+ ## Summary
25
+ The **Managed Service Order (MSO) create flow** is how a TOGa Desk user manually creates a
26
+ managed service order: pick an End User (or a Client Location), choose a Contract, add one or
27
+ more assets, and submit. The server builds a `ManagedServiceOrder`, creates its assets, then
28
+ one `App_Model_TogaDesk_RepairOrder` per asset. This is the **entry/creation** side; the
29
+ downstream repair-order lifecycle (dispatch, scheduling, status) lives in
30
+ [Field-Service Dispatch](./field-service-dispatch.md).
31
+
32
+ ## Key files / entry points
33
+ - **Entry link:** `desk/template/pages/managed.php` — the "NEW MANAGED SERVICE ORDER" link opens
34
+ modal `tickets/addNew`.
35
+ - **Modal:** `desk/template/modals/tickets/addNew.php` — the New MSO form. It has **no own
36
+ `<form>` tag**; it relies on the global `#formModal` wrapper (rendered in
37
+ `desk/template/footer.php`) and `$("form").submit()`. Contains the Summary field, an optional
38
+ inline **new End User** sub-form (name/email/phone + a `#new-user-password` field), an inline
39
+ **new address** sub-form, and select2 dropdowns `#enduser`, `#userlocation`, `#location`.
40
+ - **Server action:** `desk/includes/controllers/actions/tickets/add.php` — builds the
41
+ `ManagedServiceOrder`, creates assets, then a `RepairOrder` per asset. `ticketType`
42
+ `INSTALLATION` maps the repair order to `App_Model_TogaDesk_RepairOrder::TYPE_ONSITESERVICE`.
43
+ - **Address AJAX:** `desk/template/pages/getinfo.php` — populates the End User Address dropdown.
44
+
45
+ ## How it works
46
+ 1. `managed.php` opens the `tickets/addNew` modal into the global `#formModal`.
47
+ 2. The user selects an existing End User (`#enduser` → `userid`) or creates a new one inline;
48
+ selects an End User Address (`#userlocation`) or a Client Location (`#location`); picks a
49
+ Contract; and adds assets.
50
+ 3. Submit runs the modal's client-side validator, then POSTs `?action=…` to `add.php`.
51
+ 4. `add.php` builds the `ManagedServiceOrder`, creates each asset, and for each asset builds a
52
+ `RepairOrder`. In the `TYPE_ONSITESERVICE` branch it reads optional per-asset fields
53
+ (`installation`, `access-instructions`) — see the getVar gotcha below.
54
+
55
+ ### Address sourcing — per-person vs. client location (important)
56
+ There are **two** address sources, and they come from different tables:
57
+ - **End User Address dropdown (`#userlocation`)** is per **person**. `getinfo.php` (uid branch,
58
+ ~L152–171) runs `SELECT … FROM people_addresses INNER JOIN addresses ON addresses.id =
59
+ people_addresses.addressid WHERE people_addresses.peopleid = <uid>`. Addresses are stored
60
+ **per person** in the `addresses` table + `people_addresses` junction (`peopleid` → `addressid`)
61
+ in the togadesk DB (`TOGaDeskSupport` / `db_togadesk`).
62
+ - **An empty End User Address dropdown is a legitimate data state**, not a bug — it just means
63
+ that person has no `people_addresses` row. The **designed fallback** is the modal's inline
64
+ "add new address" form (`addNewAddress()` sets the `#newAddressCount` hidden field), which
65
+ `add.php` (~L119–148) fully supports creating.
66
+ - **Client Location (`#location`)** is the alternative: client/site addresses live in the
67
+ `locations` table keyed by `clientid`, reachable via the modal's "Select either End User or
68
+ Client Location" toggle (`#checkbox` → `#servicelocation`/`#location`), enabled once a Contract
69
+ is chosen.
70
+
71
+ History: the per-person-only End User query dates to a **2021-12-06** schema migration
72
+ (`repair_order_addresses`/`primaryaddress` → `addresses`/`isprimary`, commit `bf1abaa06`, Jeff).
73
+ The older query — still commented in `getinfo.php` ~L95–148 — UNIONed client `locations` into the
74
+ End User dropdown. Dropping that UNION is **long-standing, not a recent regression**.
75
+
76
+ ## Gotchas / known issues
77
+ - **`App_Page::getVar($var)` THROWS when the request var is absent** — it is **not** a
78
+ null-returning getter. In the `TYPE_ONSITESERVICE` branch, `add.php` (~L353–354) read the
79
+ optional `installation` and `access-instructions` fields; the manual MSO form doesn't post
80
+ `installation`, so the whole create aborted with a Sentry exception. **Fix:** use
81
+ `App_Page::getVarIfSet($var)` (returns `null`) — or guard with `App_Page::varIsSet()` — for any
82
+ **optional** request field. This is a framework-wide 1.0 `App_` gotcha, not togadesk-specific
83
+ (`library/app/page.php` — `getVar` L354 throws, `getVarIfSet` L382 returns null,
84
+ `varIsSet` L404).
85
+ - **Chrome autofill silently breaks the modal.** The modal nests a new-user sub-form (including a
86
+ password field) inside `#formModal`, which had no `autocomplete` attribute. Chrome's
87
+ password/profile autofill then filled Summary + address fields **without firing jQuery
88
+ change/input events and without syncing the select2 widgets**, so validation saw empty values
89
+ (e.g. "Please select an address" on a field that looked filled). Manual typing worked because it
90
+ fires the events. **Fix:** `autocomplete="off"` on `#formModal` (and the new-user/new-address/
91
+ Summary/select2 fields) plus `autocomplete="new-password"` on the password field (Chrome ignores
92
+ form-level `off` for password fields).
93
+ - **No-saved-address submit deadlock.** When an existing End User (`userid != 0`) had no saved
94
+ address, the validator required `#userlocation != 0` ("Please select an address."), but the
95
+ dropdown was empty and the modal auto-opened the inline add-address form — an unresolvable
96
+ deadlock. **Fix:** the validator now also reads `newAddressCount = parseInt($('#newAddressCount')
97
+ .val())||0` and only blocks when `selectedValue == 0 AND newAddressCount == 0`, since the server
98
+ already supports the inline address.
99
+ - The addNew modal has **no `<form>` tag of its own** — it depends entirely on the global
100
+ `#formModal` in `footer.php` and `$("form").submit()`. Anything that alters that global wrapper
101
+ (attributes, nesting) affects this flow.
102
+
103
+ ## Change history
104
+ - 2026-07-09 — TRUE-80100: documented the MSO create flow (entry, server action, per-person vs.
105
+ client-location address sourcing) and folded in three fixes: `getVar` → `getVarIfSet` for
106
+ optional request vars in the `TYPE_ONSITESERVICE` branch (server crash); `autocomplete` on
107
+ `#formModal` + password field to stop Chrome autofill bypassing select2/events; and the
108
+ validator now honors `#newAddressCount` so a no-address End User can submit an inline address.
109
+ (mhammontree)
@@ -5,8 +5,8 @@ project: Library
5
5
  client: shared
6
6
  type: standard
7
7
  status: active
8
- updated: 2026-07-06
9
- owners: [jcardinal, rgirish]
8
+ updated: 2026-07-09
9
+ owners: [jcardinal, rgirish, mhammontree]
10
10
  files: []
11
11
  related:
12
12
  - ../apps/library/architecture.md
@@ -429,6 +429,26 @@ gracefully instead of dropping unrelated work or crashing the loop.
429
429
 
430
430
  * Validate and sanitize all user input (see SQL Injection Prevention and HTML escaping above).
431
431
 
432
+ ### Request variable access — `getVar` throws, `getVarIfSet` returns null
433
+
434
+ `App_Page::getVar($var)` is **not** a null-returning getter — it **throws**
435
+ `Exception("Variable '$var' does not exist in the request headers.")` when the request var is
436
+ absent (`library/app/page.php` L354). Use it only for fields you *know* are always posted.
437
+
438
+ For any **optional** request field, use `App_Page::getVarIfSet($var)` (returns `null` when
439
+ missing, L382) or guard first with `App_Page::varIsSet($var)` (L404). Reading an optional field
440
+ with `getVar()` aborts the whole request with an uncaught exception — this is a recurring 1.0
441
+ crash class (e.g. TRUE-80100: an unconditional `getVar('installation')` in a create action
442
+ crashed every submit that omitted the field).
443
+
444
+ ```php
445
+ // WRONG — throws when 'installation' isn't posted
446
+ $order->location = App_Page::getVar('installation');
447
+
448
+ // CORRECT — optional field
449
+ $order->location = App_Page::getVarIfSet('installation');
450
+ ```
451
+
432
452
  ### Secure Communication
433
453
 
434
454
  * Always use HTTPS to encrypt data in transit.
@@ -7,7 +7,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
7
7
  - **library** (Library) _(framework core)_ — 11 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
8
8
  - **worker** (Worker) — 14 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
9
9
  - **worker1.5** (Worker 1.5) — 0 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
10
- - **togadesk** (TOGa Desk) — 8 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
10
+ - **togadesk** (TOGa Desk) — 9 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
11
11
  - **togaview** (TOGa View) — 6 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
12
12
  - **webhook** (Webhook) — 1 doc(s) → [1.0/apps/webhook/INDEX.md](1.0/apps/webhook/INDEX.md)
13
13
  - **walmarttechservices** (Walmart Tech Services) — 1 doc(s) → [1.0/apps/walmarttechservices/INDEX.md](1.0/apps/walmarttechservices/INDEX.md)
@@ -3,5 +3,5 @@
3
3
  | Doc | Framework | Summary | Files |
4
4
  |-----|-----------|---------|-------|
5
5
  | [NYCDOE Ticket Hold-Status Sync (ServiceNow ⇄ TOGaDesk)](features/hold-status-sync.md) | 1.0 | DOE ticket **hold** status must round-trip between ServiceNow (SNOW) and TOGaDesk and **stay held** — holds are SLA-bearing in both systems. | worker/crons/sync/nycdoe/send_ticket_updates.php, worker/crons/sync/nycdoe/process_tickets.php, worker/crons/sync/nycdoe/send_request_item_updates.php, library/app/model/togadesk/repairorder.php, library/app/api/nycdoev2.php, togadesk/desk/includes/classes/class.repair.php |
6
- | [NYCDOE ServiceNow / ASN Integration](features/servicenow-integration.md) | 1.0 | The NYCDOE/ServiceNow integration mirrors DOE's ServiceNow tickets (Incidents + RITMs) into local tables, turns vendor shipment notices into NetSuite Sales Orde | worker/crons/sync/nycdoe/import_asn.php, worker/crons/sync/nycdoe/import_inc.php, worker/crons/sync/nycdoe/legacy_import_asn.php, worker/crons/sync/nycdoe/legacy_process_asn_queue.php, worker/crons/sync/nycdoe/process_tickets.php, worker/crons/sync/nycdoe/1_send_asn_to_netsuite.php, worker/crons/sync/nycdoe/2_send_serials_to_netsuite.php, worker/crons/sync/nycdoe/3_create_installation_ticket.php, worker/crons/sync/nycdoe/send_ticket_updates.php, worker/crons/sync/nycdoe/send_request_item_updates.php, worker/crons/sync/nycdoe/send_nycdoe_proof_of_delivery.php, worker/crons/sync/nycdoe/sync_nycdoe_locations.php, worker/crons/sync/nycdoe/receive_edi_purchase_orders.php, worker/crons/sync/nycdoe/send_edi_open_invoices.php, worker/crons/notifications/nycdoe/, worker/schedules/cron.worker.sync.json, worker/schedules/cron.worker.notification.json, library/app/api/nycdoe.php, library/app/api/nycdoev2.php, library/app/asnprocessor/manufacturer.php, library/app/asnprocessor/apple.php, library/app/asnprocessor/lenovo.php, library/app/asnprocessor/lexmark.php, library/app/asnprocessor/acer.php, library/app/edi.php |
6
+ | [NYCDOE ServiceNow / ASN Integration](features/servicenow-integration.md) | 1.0 | The NYCDOE/ServiceNow integration mirrors DOE's ServiceNow tickets (Incidents + RITMs) into local tables, turns vendor shipment notices into NetSuite Sales Orde | worker/crons/sync/nycdoe/import_asn.php, worker/crons/sync/nycdoe/import_inc.php, worker/crons/sync/nycdoe/legacy_import_asn.php, worker/crons/sync/nycdoe/legacy_process_asn_queue.php, worker/crons/sync/nycdoe/process_tickets.php, worker/crons/sync/nycdoe/1_send_asn_to_netsuite.php, worker/crons/sync/nycdoe/2_send_serials_to_netsuite.php, worker/crons/sync/nycdoe/3_create_installation_ticket.php, worker/crons/sync/nycdoe/test_multi_po_receipt_resolution.php, worker/crons/sync/nycdoe/send_ticket_updates.php, worker/crons/sync/nycdoe/send_request_item_updates.php, worker/crons/sync/nycdoe/send_nycdoe_proof_of_delivery.php, worker/crons/sync/nycdoe/sync_nycdoe_locations.php, worker/crons/sync/nycdoe/receive_edi_purchase_orders.php, worker/crons/sync/nycdoe/send_edi_open_invoices.php, worker/crons/notifications/nycdoe/, worker/schedules/cron.worker.sync.json, worker/schedules/cron.worker.notification.json, library/app/api/nycdoe.php, library/app/api/nycdoev2.php, library/app/asnprocessor/manufacturer.php, library/app/asnprocessor/apple.php, library/app/asnprocessor/lenovo.php, library/app/asnprocessor/lexmark.php, library/app/asnprocessor/acer.php, library/app/edi.php, library/app/netsuite.php |
7
7
  | [New York City Department of Education](profile.md) | 1.0 | NYC DOE (New York City Department of Education) is a TOGA client whose entire integration runs in the **1.0 worker tier** (~30 cron scripts under `worker/crons/ | |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.302",
3
+ "version": "1.0.303",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",