toga-ai 1.0.543 → 1.0.545
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/2.0/apps/_underscore/INDEX.md +1 -0
- package/knowledge/2.0/apps/_underscore/features/page-meta-context-field-settings.md +75 -0
- package/knowledge/2.0/apps/_underscore/workflows/local-db-refresh-from-beta.md +14 -0
- package/knowledge/2.0/apps/toga2-supply/INDEX.md +1 -1
- package/knowledge/2.0/apps/toga2-supply/workflows/client-host-scoping.md +88 -1
- package/knowledge/2.0/apps/worker2/INDEX.md +1 -0
- package/knowledge/2.0/apps/worker2/features/all-client-email-queue-monitor.md +1 -0
- package/knowledge/2.0/apps/worker2/features/netsuite-integrations-monitor.md +139 -0
- package/knowledge/2.0/apps/worker2/features/oneuptime-worker2-monitoring.md +23 -0
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/elite/INDEX.md +1 -1
- package/knowledge/clients/elite/features/supply2-scope.md +101 -37
- package/knowledge/clients/elite/features/supply2-tableview-config-drift.md +30 -2
- package/knowledge/clients/elite/profile.md +13 -4
- package/package.json +1 -1
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
| [_Model magic-field access (__get without __isset)](features/model-magic-field-access.md) | `_Model` exposes DB columns as "magic" properties via `__get()`, but it defines **no** `__isset()`. | _underscore/Model/Core/Model.php, _underscore/Model.php, _underscore/Model/Rate/Subscription.php |
|
|
28
28
|
| [_Model::save() vs raw _Query — no atomic conditional update](features/model-save-vs-query-atomic-update.md) | `_Model::save()` is a plain load-then-write ORM primitive and **cannot express an atomic conditional update** (an optimistic-concurrency / row-claim guard such | _underscore/Model.php, _underscore/Query.php, _underscore/Model/Rate/Subscription.php |
|
|
29
29
|
| [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | `_Component_Api_Netsuite` is the **2.0 `_underscore` NetSuite REST client** — the shared primitive every worker2/api2 NetSuite caller uses for record GETs, Suit | _underscore/Component/Api/Netsuite/Netsuite.php |
|
|
30
|
+
| [Legacy page meta (Page::meta) & context-scoped ClientRecordFieldSettings](features/page-meta-context-field-settings.md) | `_Model_Core_Page::meta()` is the **legacy** page-meta resolver behind `GET /pages/meta?slug=<slug>` — still the live path for `toga2-supply` and other pre-Surf | _underscore/Model/Core/Page.php |
|
|
30
31
|
| [Per-Client Database Connections & the Local Logs Trap](features/per-client-database-connections.md) | When `_underscore` serves a request for a client it opens **three distinct per-client database connections**, not one. | _underscore/Database.php, _underscore/Query.php, _underscore/ApiRequest.php, _underscore/Model/Client/Logs/Api.php, api2/Controller/Index.php |
|
|
31
32
|
| [Persona Name Translation (PersonaTranslations sidecar)](features/persona-name-translation.md) | Serves Persona **names** in multiple languages by adding a per-language **sidecar** table `PersonaTranslations`, reusing the platform's existing metadata-driven | _underscore/Model/Client/PersonaTranslation.php, dbchanges2/Client/2026-07-22b - PersonaTranslations.sql, dbchanges2/Core/2026-07-22a - PersonaTranslationsRecord.sql, dbchanges2/Client/2026-07-22c - PersonaTranslationsAcl.sql, dbchanges2/Client_CompassCanada/2026-07-22 - PersonaTranslationsFrench.sql, toga2-commerce/src/pages/Account/view/MySettingsView.tsx |
|
|
32
33
|
| [Recursive Item Fulfillments (upstream mirroring)](features/recursive-item-fulfillments.md) | In a multi-tier supply chain a sales order (SO) spawns a purchase order (PO) that becomes another SO downstream, and so on. | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillmentItem.php, _underscore/Model/Client/ItemFulfillmentItemUnit.php, _underscore/Model/Client/ItemFulfillmentPackage.php, _underscore/Model/Compass/AdvanceShippingNotice.php, dbchanges2/Core/2026-02-13 - 75601 - RecursiveItemFulfillmentCreation.sql, dbchanges2/Core/2026-06-04 - RecursiveItemFulfillmentPut.sql, dbchanges2/Client_Compass/2026-07-02a - FixSA133377TrackingSerialAndDuplicateIF.sql |
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Legacy page meta (Page::meta) & context-scoped ClientRecordFieldSettings
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: _underscore
|
|
5
|
+
project: _Underscore
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-08-10
|
|
10
|
+
owners: [tcox]
|
|
11
|
+
files:
|
|
12
|
+
- _underscore/Model/Core/Page.php
|
|
13
|
+
related:
|
|
14
|
+
- surface-resolver.md
|
|
15
|
+
- error-reporting-issue-event.md
|
|
16
|
+
- ../../../clients/elite/features/supply2-tableview-config-drift.md
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Summary
|
|
20
|
+
|
|
21
|
+
`_Model_Core_Page::meta()` is the **legacy** page-meta resolver behind `GET /pages/meta?slug=<slug>`
|
|
22
|
+
— still the live path for `toga2-supply` and other pre-Surface frontends. It is being replaced by
|
|
23
|
+
[`_Model_Core_Surface::resolve`](surface-resolver.md), but must not be deleted while consumers
|
|
24
|
+
remain. One part of it — **context-scoped `ClientRecordFieldSettings`** — carries a live framework
|
|
25
|
+
bug that takes down *unrelated* pages for any client that uses the feature.
|
|
26
|
+
|
|
27
|
+
## What context-scoped field settings are
|
|
28
|
+
|
|
29
|
+
`Client_*.ClientRecordFieldSettings` lets a client override a field's presentation (e.g. its label)
|
|
30
|
+
**in the context of another field**: a row carries the target `recordFieldId` plus a
|
|
31
|
+
`contextRecordFieldId`, meaning "when field X is shown *through* field Y, label it differently."
|
|
32
|
+
|
|
33
|
+
Example (Elite): row id 1 relabels field **58** to **"Total"** in the context of field **190**
|
|
34
|
+
(`ServiceRequests.id`) — authored for the `service-requests` view.
|
|
35
|
+
|
|
36
|
+
## The bug — "Undefined array key \<contextRecordFieldId\>" 500
|
|
37
|
+
|
|
38
|
+
> **Status: reported to the backend, unfixed as of 2026-08-10.**
|
|
39
|
+
|
|
40
|
+
`Page::meta()` fetches a client's field-settings rows by **target field id only**, so Elite's row is
|
|
41
|
+
returned for **any** page whose field set contains field 58 — not just the page that owns field 190.
|
|
42
|
+
It then resolves `contextRecordFieldId` against a lookup array built **only from the current page's
|
|
43
|
+
fields**. When the context field is not on that page, PHP raises *"Undefined array key 190"* at
|
|
44
|
+
`Model/Core/Page.php:824`.
|
|
45
|
+
|
|
46
|
+
Historically that was a warning and the page still rendered. The **strict Sentry error handler**
|
|
47
|
+
deployed to beta on 2026-08-07 promotes it to a **fatal**, so it now surfaces as a hard 500 — Elite's
|
|
48
|
+
`/pages/meta?slug=inventory` (a page with nothing to do with service requests) began 500ing purely
|
|
49
|
+
because the service-requests row exists.
|
|
50
|
+
|
|
51
|
+
**Fix:** an `isset()` guard around `Page.php` ~L822–829 — skip any context setting whose context
|
|
52
|
+
field is not present on the requested page.
|
|
53
|
+
|
|
54
|
+
**Blast radius:** any client that uses context-scoped field settings can hit this on **every page
|
|
55
|
+
that shares the base field**. The failure looks unrelated to the page you are debugging, and it can
|
|
56
|
+
appear long after the settings row was authored — a redeploy that tightens error handling, not a
|
|
57
|
+
data change, is what makes it fatal.
|
|
58
|
+
|
|
59
|
+
## Gotchas
|
|
60
|
+
|
|
61
|
+
- **A page-meta 500 is not necessarily about that page.** Trace it to the offending
|
|
62
|
+
`ClientRecordFieldSettings` row via the array key in the message (it is a `Core.RecordFields` id),
|
|
63
|
+
not via the slug you requested.
|
|
64
|
+
- **Error-handler strictness is a deploy-time behavior change.** Warnings that were survivable for
|
|
65
|
+
years become 500s the moment the strict handler ships; expect a cluster of "new" failures in old
|
|
66
|
+
code after such a deploy.
|
|
67
|
+
- Do not "fix" this by deleting the client's settings row — that removes a real client
|
|
68
|
+
customization. The guard belongs in the framework.
|
|
69
|
+
|
|
70
|
+
## Change history
|
|
71
|
+
- 2026-08-10 — First capture: documented context-scoped `ClientRecordFieldSettings` and the
|
|
72
|
+
`Page.php:824` *"Undefined array key \<contextRecordFieldId\>"* fatal (context field not on the
|
|
73
|
+
requested page), which the 2026-08-07 strict Sentry handler turned from a warning into a 500 on
|
|
74
|
+
Elite's `/pages/meta?slug=inventory`. Reported to the backend; recommended `isset()` guard at
|
|
75
|
+
~L822–829. (tcox)
|
|
@@ -111,9 +111,23 @@ Order matters — dump first so a failed dump never wipes a local schema it can'
|
|
|
111
111
|
`Core` change-sets (e.g. the Surface-layer seeds), so a later replay of *committed* changes is
|
|
112
112
|
largely a no-op. Only ad-hoc changes that were never committed to dbchanges2 need re-applying
|
|
113
113
|
after a reset.
|
|
114
|
+
- **⚠ Beta itself gets rebuilt from PRODUCTION — treat every beta hand-insert as temporary.** A
|
|
115
|
+
`Client_*` schema on dev-sandbox can be re-imported from prod at any time (e.g. `Client_Elite`,
|
|
116
|
+
2026-08-07 11:55). Everything hand-inserted on beta and never committed to `dbchanges2` is gone:
|
|
117
|
+
a hand-built TableView, a seeded `devteam@togatech.com` user (it returns as a row with an **empty
|
|
118
|
+
password**, which reads as "wrong password", not "missing user"), ad-hoc config rows. **Row ids
|
|
119
|
+
also shift on re-import**, so notes or scripts that identify a row by id stop matching — key on
|
|
120
|
+
the natural columns instead (for `TableViewJoins`: `tableViewId` + `joinRecordId` +
|
|
121
|
+
`joinOnRecordFieldId`). If a beta-only row matters, ship it as a `dbchanges2` migration. Worked
|
|
122
|
+
example: [Elite TableView config
|
|
123
|
+
drift](../../../clients/elite/features/supply2-tableview-config-drift.md).
|
|
114
124
|
|
|
115
125
|
## Change history
|
|
116
126
|
|
|
127
|
+
- 2026-08-10 — Added the **beta-is-rebuilt-from-prod** gotcha: a `Client_*` schema can be re-imported
|
|
128
|
+
from production at any time, discarding every uncommitted hand-insert (hand-built TableViews, the
|
|
129
|
+
seeded `devteam` user — which returns with an empty password) and **shifting row ids**, so match
|
|
130
|
+
rows by natural columns and ship beta-only rows as `dbchanges2` migrations. (tcox)
|
|
117
131
|
- 2026-08-06 — Recorded that environment **`beta` resolves to the dev-sandbox cluster** in
|
|
118
132
|
`Core.DatabaseHosts` for `Client_*` schemas, so an app pointed at `api.beta.togahub.com` is
|
|
119
133
|
reading dev-sandbox data and the DB MCP environment to query is `dev-sandbox` (not
|
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
| [TOGa Supply (toga2-supply) Architecture](architecture.md) | `toga2-supply` is the **React + Vite frontend** for TOGa Supply — warehouse fulfillment tooling (shipment selection, fulfill & ship against carrier APIs, NetSui | toga2-supply/src/api/toga.ts, toga2-supply/src/pages/ShipmentItems/view/ShipmentItemsPage.tsx, toga2-supply/src/pages/EditShipment/view/EditShipmentPage.tsx, toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts, toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx |
|
|
6
6
|
| [Fulfill & Ship](features/fulfill-and-ship.md) | Fulfill & Ship lets a warehouse user select sales-order line items, enter serials, pick a carrier/method, and in one action: create the Item Fulfillment records | _underscore/Model/Client/Measure.php, _underscore/Model/Client/TrackingNumber.php, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/getEditShipmentFormOptions.ts, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/validateFormOnSubmit.ts, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/checkDimensions.ts, toga2-supply/src/components/ui/Tables/BasicTable/BasicTable.tsx, toga2-supply/src/components/ui/Tables/types.ts, toga2-supply/src/components/ui/GoogleMapsLink.tsx, toga2-supply/src/pages/ShipmentItems/view/forms/ShipmentItemsTable.tsx, toga2-supply/src/pages/ShipmentItems/api/ShipmentItemsApi.ts, toga2-supply/src/pages/ShipmentItems/types.ts, toga2-supply/src/pages/EditShipment/viewModel/FIELDS/RETURNLABELFIELDS.json, toga2-supply/src/pages/EditShipment/view/components/forms/EditShipmentForm.tsx, toga2-supply/src/components/ui/BaseInput/UnitSelect.tsx, toga2-supply/src/pages/EditShipment/view/modals/SerialNumbersModal.tsx, toga2-supply/src/pages/ShipmentItems/view/ShipmentItemsPage.tsx, toga2-supply/src/pages/EditShipment/view/EditShipmentPage.tsx, toga2-supply/src/pages/EditShipment/view/components/forms/EditShipmentForm.tsx, toga2-supply/src/pages/EditShipment/view/components/SelectedShipmentItemsTable.tsx, toga2-supply/src/pages/EditShipment/view/helpers/ShipmentDetailsForm/renderEditShipmentFormInput.tsx, toga2-supply/src/pages/EditShipment/viewModel/FIELDS/DUMMYUPDATESHIPMENTFIELDS.json, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/renderEditShipmentFormInput.tsx, toga2-supply/tailwind.config.cjs, toga2-supply/src/pages/EditShipment/view/modals/ReturnShippingModal.tsx, toga2-supply/src/styles/index.scss, toga2-supply/src/components/ui/BaseInput/BaseInput.tsx, toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts, toga2-supply/src/pages/EditShipment/viewModel/signatureTypes.ts, toga2-supply/src/pages/EditShipment/view/modals/SelectReturnAddressModal.tsx, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/formatShipmentData.ts, toga2-supply/src/pages/EditShipment/types.ts, toga2-supply/src/pages/Shipments/view/ShipmentsPage.tsx, toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx, toga2-supply/src/pages/Shipments/api/ShipmentsApi.ts, toga2-supply/src/pages/Shipments/types.ts, toga2-supply/src/pages/FulfilledShipments/view/FulfilledShipmentsPage.tsx, toga2-supply/src/components/ui/CardTable/CardTable.tsx, toga2-supply/src/components/ui/CardTable/types.ts, toga2-supply/src/assets/pen-line.svg, _underscore/Model/Client/ItemFulfillment.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Component/Library/Carriers/Ups/Ups.php |
|
|
7
7
|
| [AWS Amplify Build & Deploy (non-prod environments)](workflows/amplify-build-and-deploy.md) | How `toga2-supply` (React + Vite) builds and deploys on **AWS Amplify**. | toga2-supply/amplify.yml, toga2-supply/.gitattributes, toga2-supply/.github/workflows/sync-stage-environments.yml, toga2-supply/.env.qc-security |
|
|
8
|
-
| [Onboarding a client to the supply2 frontend (host-scoping)](workflows/client-host-scoping.md) | How a new tenant becomes a "client" in the `toga2-supply` frontend. | toga2-supply/src/stores/useHostNameStore.ts, toga2-supply/src/utils/resolveClientHostName.ts, toga2-supply/src/hooks/useHostName.tsx, toga2-supply/src/hooks/usePageDetails.tsx, toga2-supply/src/hooks/usePageListDetails.tsx, toga2-supply/src/components/layout/SlideMenu/SlideMenu.tsx, toga2-supply/src/utils/handleClientAuthentication.ts, toga2-supply/src/App.tsx, toga2-supply/src/pages/Inventory/viewModel/FIELDS/DUMMYGROUPOPTIONS.ts, toga2-supply/src/pages/Inventory/viewModel/FIELDS/INVENTORYPAGEFIELDS.ts, toga2-supply/src/pages/Inventory/listing/InventoryPage.tsx, toga2-supply/src/pages/Inventory/listing/InventorySubTablePage.tsx, toga2-supply/src/api/toga.ts, toga2-supply/package.json |
|
|
8
|
+
| [Onboarding a client to the supply2 frontend (host-scoping)](workflows/client-host-scoping.md) | How a new tenant becomes a "client" in the `toga2-supply` frontend. | toga2-supply/src/stores/useHostNameStore.ts, toga2-supply/src/utils/resolveClientHostName.ts, toga2-supply/src/hooks/useHostName.tsx, toga2-supply/src/hooks/usePageDetails.tsx, toga2-supply/src/hooks/usePageListDetails.tsx, toga2-supply/src/components/layout/SlideMenu/SlideMenu.tsx, toga2-supply/src/utils/handleClientAuthentication.ts, toga2-supply/src/App.tsx, toga2-supply/src/pages/Inventory/viewModel/FIELDS/DUMMYGROUPOPTIONS.ts, toga2-supply/src/pages/Inventory/viewModel/FIELDS/INVENTORYPAGEFIELDS.ts, toga2-supply/src/pages/Inventory/listing/InventoryPage.tsx, toga2-supply/src/pages/Inventory/listing/InventoryRouter.tsx, toga2-supply/src/pages/Inventory/listing/InventorySubTablePage.tsx, toga2-supply/src/pages/Orders/OrdersPage.tsx, toga2-supply/src/pages/Orders/viewModel/useOrdersPageViewModel.ts, toga2-supply/src/pages/Orders/api/OrdersApi.ts, toga2-supply/src/pages/Orders/view/OrderView/viewModel/useOrderDetailsViewModel.ts, toga2-supply/src/components/ui/Tables/PrimaryTable/helperFunctions/renderModalContent.tsx, toga2-supply/src/utils/convertConstructorColumnTitles.ts, toga2-supply/src/api/toga.ts, toga2-supply/package.json |
|
|
@@ -6,7 +6,7 @@ project: TOGa Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-10
|
|
10
10
|
owners: [apeterson, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga2-supply/src/stores/useHostNameStore.ts
|
|
@@ -20,7 +20,14 @@ files:
|
|
|
20
20
|
- toga2-supply/src/pages/Inventory/viewModel/FIELDS/DUMMYGROUPOPTIONS.ts
|
|
21
21
|
- toga2-supply/src/pages/Inventory/viewModel/FIELDS/INVENTORYPAGEFIELDS.ts
|
|
22
22
|
- toga2-supply/src/pages/Inventory/listing/InventoryPage.tsx
|
|
23
|
+
- toga2-supply/src/pages/Inventory/listing/InventoryRouter.tsx
|
|
23
24
|
- toga2-supply/src/pages/Inventory/listing/InventorySubTablePage.tsx
|
|
25
|
+
- toga2-supply/src/pages/Orders/OrdersPage.tsx
|
|
26
|
+
- toga2-supply/src/pages/Orders/viewModel/useOrdersPageViewModel.ts
|
|
27
|
+
- toga2-supply/src/pages/Orders/api/OrdersApi.ts
|
|
28
|
+
- toga2-supply/src/pages/Orders/view/OrderView/viewModel/useOrderDetailsViewModel.ts
|
|
29
|
+
- toga2-supply/src/components/ui/Tables/PrimaryTable/helperFunctions/renderModalContent.tsx
|
|
30
|
+
- toga2-supply/src/utils/convertConstructorColumnTitles.ts
|
|
24
31
|
- toga2-supply/src/api/toga.ts
|
|
25
32
|
- toga2-supply/package.json
|
|
26
33
|
related:
|
|
@@ -81,6 +88,52 @@ shared static that shows *only* your additions for other hosts; `npx tsc --noEmi
|
|
|
81
88
|
in on the real subdomain and confirm columns render (pageMeta), the group-by switcher is hidden if
|
|
82
89
|
you locked it, and nested chevrons appear.
|
|
83
90
|
|
|
91
|
+
**Two follow-ups the review pass added (2026-08-10) — apply both when host-scoping inventory:**
|
|
92
|
+
|
|
93
|
+
- **Re-seed `localStorage("selectedGrouping")` when the stored uuid isn't in the resolved host's
|
|
94
|
+
array** (`InventoryPage.tsx`). The key is shared across hosts on one origin, so a same-origin host
|
|
95
|
+
switch (or corrupt JSON) otherwise leaves a stale uuid that the non-null-asserted `find` throws on.
|
|
96
|
+
This is a provable no-op for hosts sharing the default array.
|
|
97
|
+
- **Redirect `/inventory_items` → `/inventory` for hosts whose default view has no `subTableParent`**
|
|
98
|
+
(`InventoryRouter.tsx`). Do it **data-driven off the resolved view**, not off a host literal: a
|
|
99
|
+
terminal (non-chained) view otherwise passes `undefined` into `PrimaryTable`.
|
|
100
|
+
|
|
101
|
+
## Reference implementation 2 — Elite Service Requests: retitling a shared page (2026-08-10)
|
|
102
|
+
|
|
103
|
+
The second Elite page is the **existing Orders page host-configured**, not a new route — the pattern
|
|
104
|
+
to copy when a client wants "their" version of a shared page under a different name. See
|
|
105
|
+
[Elite supply2 scope](../../../../clients/elite/features/supply2-scope.md) for the client detail.
|
|
106
|
+
|
|
107
|
+
The shape: a per-host `FIELDS/<HOST>/BASE.json` supplies `pageTitle`, `tableSlug`, `listingSlug`,
|
|
108
|
+
and feature flags; `useOrdersPageViewModel.ts` gets an **explicit host entry** (`ELITE: {base: …}`,
|
|
109
|
+
following the PRUDENTIAL `base` precedent); and every hardcoded slug literal in the page becomes
|
|
110
|
+
`fields?.<key>` **with the old literal as the fallback**. Elite's page replaced **8** occurrences of
|
|
111
|
+
`"sales-orders"` this way. Because no other host defines the new keys, every default path stays
|
|
112
|
+
literal-identical — that is the property that makes the change zero-risk for other tenants, and it is
|
|
113
|
+
what you should be able to assert before merging.
|
|
114
|
+
|
|
115
|
+
Feature removal is expressed by **omission**, not by a flag check scattered through the page:
|
|
116
|
+
omitting `approvalActionOptions` (plus `approvalsButton.isEnabled: false`) is what drops approvals.
|
|
117
|
+
|
|
118
|
+
**Routing stays centralized.** No route was added for "Service Requests"; the nav label and the page
|
|
119
|
+
title are config. Do not add per-client routes (see above).
|
|
120
|
+
|
|
121
|
+
## When a client's table view sits on a DIFFERENT record than the detail route
|
|
122
|
+
|
|
123
|
+
Elite's `service-requests` table view is built on the **ServiceRequests** record (joining SalesOrders
|
|
124
|
+
1:1), while every details-modal route in the page is a `/sales-orders` record route. A retitled page
|
|
125
|
+
is often *also* a re-recorded page — confirm with the backend which record the view is built on
|
|
126
|
+
before wiring the modal. The fix pattern:
|
|
127
|
+
|
|
128
|
+
- add a **resolver** API call that translates the row uuid into the record uuid the modal route
|
|
129
|
+
expects (Elite: `getSalesOrderUuidForServiceRequest()` in `Orders/api/OrdersApi.ts` — `GET
|
|
130
|
+
/sales-orders` with a ServiceRequests join and `where ServiceRequests.uuid = …`);
|
|
131
|
+
- thread the resolved uuid into the details view model as a react-query whose **`enabled` is gated on
|
|
132
|
+
the host**, so other hosts never issue the extra request;
|
|
133
|
+
- expect **columns the other record does not have** — see the `isQuote` gotcha below;
|
|
134
|
+
- expect the join to be **optional**: an unmatched row resolves to `null` and the modal opens empty.
|
|
135
|
+
Decide the empty-state treatment with product; do not silently ship a blank modal.
|
|
136
|
+
|
|
84
137
|
## Getting a login for a new host (auth + test user)
|
|
85
138
|
|
|
86
139
|
Onboarding stalls here more often than in the frontend. Two findings from Elite:
|
|
@@ -112,6 +165,12 @@ a plaintext credential. Remember [beta = the dev-sandbox
|
|
|
112
165
|
cluster](../../_underscore/workflows/local-db-refresh-from-beta.md) — seed the environment the app
|
|
113
166
|
actually talks to.
|
|
114
167
|
|
|
168
|
+
> **The seeded account does not survive a tenant refresh.** When `Client_Elite` was rebuilt from
|
|
169
|
+
> production (2026-08-07), `devteam@togatech.com` came back as a **re-seeded row with an empty
|
|
170
|
+
> password** — the login failure looks like a wrong password, not a missing user. Re-run the same
|
|
171
|
+
> hash copy from `Client_Compass.Users` (id 127046 on dev-sandbox). Anything else you hand-inserted
|
|
172
|
+
> on beta is gone too — see the gotcha below.
|
|
173
|
+
|
|
115
174
|
## Onboarding checklist
|
|
116
175
|
|
|
117
176
|
1. Confirm the client's subdomain resolves the tenant (hostname → client → `Client_<slug>` DB), and
|
|
@@ -147,8 +206,36 @@ actually talks to.
|
|
|
147
206
|
- **View-model host maps must not depend on a fallback.** Orders' `useOrdersPageViewModel.ts`
|
|
148
207
|
uses `fieldMapping[hostName] || fieldMapping.compass`, but the fallback key is a nonexistent
|
|
149
208
|
lowercase `compass`, so an unrecognized host **throws**. Always add an explicit host entry.
|
|
209
|
+
(Still true as of 2026-08-10 — the broken fallback was deliberately left in place; fix it only as
|
|
210
|
+
its own change.)
|
|
211
|
+
- **The row-click modal registry is keyed by `pageTitle`.** `renderModalContent.tsx` picks
|
|
212
|
+
`modalConfig[hostName][pageTitle] || modalConfig.DEFAULT[pageTitle]` — so **retitling a page
|
|
213
|
+
silently unhooks its modal**: rows become inert with no error. Elite's modal stayed dead until a
|
|
214
|
+
`"Service Requests"` key was added to `modalConfig.DEFAULT` routing to `OrderDetailsModal`
|
|
215
|
+
(additive). Any host given a custom page title must get a matching modal-config key in the same
|
|
216
|
+
change.
|
|
217
|
+
- **A page slug with no backend page meta used to crash the whole table.**
|
|
218
|
+
`convertConstructorColumnTitles.ts` dereferenced the meta unguarded
|
|
219
|
+
(`Cannot read properties of null (reading 'settings')`) — hit the moment a `listingSlug` pointed at
|
|
220
|
+
a page that does not exist in `Core.Pages`. It is now null-guarded and degrades to `""` labels
|
|
221
|
+
(callers already fall back to the field name). Reusing another page's `listingSlug` is therefore
|
|
222
|
+
survivable, but remember it only affects **ACL and search labels** — column headers come from the
|
|
223
|
+
**table-view** meta, not the page meta.
|
|
224
|
+
- **A record swap costs you columns.** Elite's `service-requests` view is built on ServiceRequests,
|
|
225
|
+
which has **no `isQuote` column**, so Orders' unconditional `isQuote: false` filter 500'd with
|
|
226
|
+
`1054 Unknown column`. Filters that assume the SalesOrders shape must be made conditional on a
|
|
227
|
+
FIELDS flag (`hasQuoteFilter`), not assumed. Backend confirmed this is an FE-side fix.
|
|
150
228
|
|
|
151
229
|
## Change history
|
|
230
|
+
- 2026-08-10 — Added **reference implementation 2 — retitling a shared page** (per-host
|
|
231
|
+
`FIELDS/<HOST>/BASE.json` supplying `pageTitle`/`tableSlug`/`listingSlug`, explicit host entry in
|
|
232
|
+
the view model, slug literals threaded through `fields?.` with literal fallbacks, feature removal
|
|
233
|
+
by omission) and a section on **table views built on a different record than the detail route**
|
|
234
|
+
(uuid resolver, host-gated query, optional join). New gotchas: the modal registry is keyed by
|
|
235
|
+
`pageTitle` so retitling silently unhooks the modal; `convertConstructorColumnTitles` is now
|
|
236
|
+
null-guarded for a slug with no page meta; a record swap loses columns (`isQuote` → `1054`). Also
|
|
237
|
+
recorded the `selectedGrouping` re-seed + terminal-view `/inventory_items` redirect, and that a
|
|
238
|
+
tenant refresh from prod returns the seeded `devteam` user with an empty password. (tcox)
|
|
152
239
|
- 2026-08-06 — Added the **Elite Inventory reference implementation** (separate host array +
|
|
153
240
|
call-time `getGroupOptions()`, the shared `resolveClientHostName.ts` util, host-scoped
|
|
154
241
|
`getInventoryPageFields()`, additive uuid `else if` branches, `npm run <host>` + hosts-file
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
| [Etilize Catalog Item Import & Refresh](features/etilize-catalog-item-import.md) | Client-generic catalog onboarding from an S3 CSV plus an Etilize re-pull. | worker2/Worker/Etilize/Items.php |
|
|
24
24
|
| [Etilize Item Translation Import](features/etilize-item-translation-import.md) | The abstract worker class `_Worker_Etilize_ItemTranslations` imports **non-English** item text from Etilize into the client's `ItemTranslations` table. | worker2/Worker/Etilize/ItemTranslations.php |
|
|
25
25
|
| [Monitoring Framework (Orchestrator + Child Monitors)](features/monitoring-framework.md) | A unified, DB-driven monitoring framework for business-critical data flows (Compass POs, Prudential asset imports, AIG closed claims, …). | worker2/Worker/Monitor.php, worker2/Worker/Monitors/, worker2/Worker/Monitors/RateEntitlement.php, worker2/Worker/Notification/Email.php, worker2/Worker/Rate.php, dbchanges2/Core/2026-05-21 - Monitors.sql, dbchanges2/Core/2026-06-29a - Rate Entitlement Contract Monitor.sql |
|
|
26
|
+
| [NetSuite Integrations Monitor (Monitor/Operations/NetsuiteIntegrations)](features/netsuite-integrations-monitor.md) | `_Worker_Monitor_Operations::NetsuiteIntegrations()` is a cross-client health check that detects **stuck NetSuite ↔ 2.0 integrations**. | worker2/Worker/Monitor/Operations.php, dbchanges2/Core/2026-08-10a - Netsuite Integrations Monitor.sql, _underscore/Database.php, _underscore/Query.php |
|
|
26
27
|
| [NetSuite ↔ ClickUp / TOGA Opportunity Sync (API Message Queue + worker2 webhook)](features/netsuite-opportunity-sync.md) | Outbound sync from NetSuite to TOGA for the record types the Forecast2 importer pulls (opportunities first; sales/items/etc. | worker2/Worker/Netsuite.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Worker/Clickup.php, worker2/Worker/Clickup/Opportunity.php, worker2/Controller/Index.php, _underscore/Worker.php, test/@dave/NetSuite/api-message-queue/lib_amq_queue.js, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/ue_amq_drain.js, test/@dave/NetSuite/api-message-queue/ss_amq_drain.js, test/@dave/NetSuite/api-message-queue/DEPLOY_RUNBOOK.md, test/@dave/clickup/backfill_opportunity_numbers.php, test/@dave/clickup/probe_opportunity_fields.php, test/@dave/probe_clickup_desc_match.php, test/@dave/test_model_load_behavior.php, dbchanges2/Forecast/2026-06-25a - Add unique index on Opportunities netsuiteOpportunityInternalId.sql, _underscore/Model/Forecast/Opportunity.php, test/@dave/approach/TRUE-80044.md, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
|
|
27
28
|
| [NetSuite → Forecast Open-Orders Sync (salesOrder webhook → OpenOrderItems)](features/netsuite-salesorder-open-orders-sync.md) | Webhook-driven, single-record port of the legacy open-orders importer (TRUE-79142). | worker2/Worker/Netsuite/SalesOrder.php, worker2/Worker/Netsuite.php, worker2/Component/Forecast/Db/Db.php, worker2/Worker/Netsuite/Location.php, test/@dave/Junk Drawer/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/Junk Drawer/NetSuite/api-message-queue/ue_amq_invoice_resync_salesorder.js, test/@dave/probe_salesorder_rest_shape.php, test/@dave/probe_open_order_lines.php, test/@dave/check_so_status.php, test/@dave/check_so_history.php, test/@dave/probe_so_rest_lines.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, test/@dave/probe_open_order_gating.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
|
|
28
29
|
| [NetSuite Supporting-Record Webhook Importer (the reusable recipe)](features/netsuite-supporting-record-webhook-importer.md) | A single **repeatable recipe** for porting a legacy daily-pull NetSuite *supporting-record* importer (the lookup/dimension tables behind Forecast2 — Employees, | worker2/Worker/Netsuite/Employee.php, worker2/Worker/Netsuite/Account.php, worker2/Worker/Netsuite/Classification.php, worker2/Worker/Netsuite/Customer.php, worker2/Worker/Netsuite/Item.php, worker2/Worker/Netsuite.php, _underscore/Model/Forecast/Employee.php, _underscore/Model/Forecast/Account.php, _underscore/Model/Forecast/Classification.php, _underscore/Component/Forecast/Db/Db.php, test/@dave/test_employee_lifecycle.php, test/@dave/test_account_lifecycle.php, test/@dave/test_classification_lifecycle.php, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, worker/crons/toga2/forecast2/import_supporting_records.php |
|
|
@@ -15,6 +15,7 @@ files:
|
|
|
15
15
|
- dbchanges2/Logs_Client/2026-05-21 - Email.sql
|
|
16
16
|
related:
|
|
17
17
|
- ./oneuptime-worker2-monitoring.md
|
|
18
|
+
- ./netsuite-integrations-monitor.md
|
|
18
19
|
- ./monitoring-framework.md
|
|
19
20
|
- ../../_underscore/features/email-send-pipeline.md
|
|
20
21
|
- ../../_underscore/features/per-client-database-connections.md
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: NetSuite Integrations Monitor (Monitor/Operations/NetsuiteIntegrations)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: worker2
|
|
5
|
+
project: Worker
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-08-10
|
|
10
|
+
owners: ["jcardinal"]
|
|
11
|
+
files:
|
|
12
|
+
- worker2/Worker/Monitor/Operations.php
|
|
13
|
+
- dbchanges2/Core/2026-08-10a - Netsuite Integrations Monitor.sql
|
|
14
|
+
- _underscore/Database.php
|
|
15
|
+
- _underscore/Query.php
|
|
16
|
+
related:
|
|
17
|
+
- ./all-client-email-queue-monitor.md
|
|
18
|
+
- ./oneuptime-worker2-monitoring.md
|
|
19
|
+
- ./monitoring-framework.md
|
|
20
|
+
- ../../_underscore/features/per-client-database-connections.md
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Summary
|
|
24
|
+
|
|
25
|
+
`_Worker_Monitor_Operations::NetsuiteIntegrations()` is a cross-client health check that
|
|
26
|
+
detects **stuck NetSuite ↔ 2.0 integrations**. It sweeps **every** client's business
|
|
27
|
+
database (`Client_<X>`), looks in the `Parameters` table for rows where
|
|
28
|
+
`` `key` `` LIKE `NETSUITE_EXECUTION_MODE_%` and `` `value` `` LIKE `1-RUNNING`, and pushes
|
|
29
|
+
one aggregate payload to a single OneUptime Incoming Request monitor with the **offending
|
|
30
|
+
integrations named** in the body. A row parked at the `1-RUNNING` sentinel means an
|
|
31
|
+
integration hung mid-run.
|
|
32
|
+
|
|
33
|
+
This is the second all-client monitor in `_Worker_Monitor_Operations`, modeled exactly on
|
|
34
|
+
its sibling [EmailQueue](./all-client-email-queue-monitor.md) — a "dumb reporter, smart
|
|
35
|
+
monitor": the worker only reports measured values and OneUptime holds the alarm
|
|
36
|
+
thresholds. It is **shared internal operational infrastructure**, not a client feature.
|
|
37
|
+
|
|
38
|
+
## Key files / entry points
|
|
39
|
+
|
|
40
|
+
- `worker2/Worker/Monitor/Operations.php` — `abstract class _Worker_Monitor_Operations`.
|
|
41
|
+
- `public static function NetsuiteIntegrations(): string` — the action, route
|
|
42
|
+
`Monitor/Operations/NetsuiteIntegrations`.
|
|
43
|
+
- `private static function resolveClientDatabases(): array` — one `DB_CORE` query
|
|
44
|
+
returning every client + its **business** database name (`Client_<X>`) + this
|
|
45
|
+
environment's hosts. Mirrors `resolveClientLogDatabases()` but joins `Core.Clients` on
|
|
46
|
+
`clientDatabaseId` (not `logDatabaseId`).
|
|
47
|
+
- `dbchanges2/Core/2026-08-10a - Netsuite Integrations Monitor.sql` — the `Core.CronJobs`
|
|
48
|
+
seed row (see below).
|
|
49
|
+
- Dispatched by action string, not PHP import — the autoloader + `_Worker::runTask()`
|
|
50
|
+
reach the class via the cron action path.
|
|
51
|
+
|
|
52
|
+
## How it works
|
|
53
|
+
|
|
54
|
+
1. **Resolve every client's business database.** `resolveClientDatabases()` joins
|
|
55
|
+
`Clients` → `Databases` (via `Clients.clientDatabaseId`) → `DatabaseHosts` →
|
|
56
|
+
`Environments`/`Regions`, all **LEFT OUTER JOINs** so a misconfigured client is
|
|
57
|
+
reported, not silently dropped (`unresolvedClients`). Closest region first; later rows
|
|
58
|
+
for the same client are skipped.
|
|
59
|
+
2. **Register each client's business DB under its REAL database name, no alias**, guarded
|
|
60
|
+
by `if (!isset(_Database::$_registers[$dbName]))`, then query via `_Query`. A per-client
|
|
61
|
+
loop cannot reuse a shared alias — every iteration would clobber it.
|
|
62
|
+
3. **Sweep `Parameters` per client:**
|
|
63
|
+
`` SELECT `key`, `value` FROM Parameters WHERE `key` LIKE 'NETSUITE_EXECUTION_MODE_%' AND `value` LIKE '1-RUNNING' ``.
|
|
64
|
+
The integration **type** is the key with the `NETSUITE_EXECUTION_MODE_` prefix stripped
|
|
65
|
+
via `substr()` (e.g. `NETSUITE_EXECUTION_MODE_SALES_ORDERS` → `SALES_ORDERS`).
|
|
66
|
+
4. **Bucket each client** into `checked`, `skipped` (no `Parameters` table —
|
|
67
|
+
`isMissingTableError()`), or `unreachable` (any other query/connection failure). A
|
|
68
|
+
per-client `try/catch (Throwable)` keeps one bad client from failing the sweep.
|
|
69
|
+
5. **Decide the tokens and push once** to OneUptime.
|
|
70
|
+
|
|
71
|
+
### Payload contract — `alarm` and `probe` are SEPARATE tokens
|
|
72
|
+
|
|
73
|
+
Follows the [OneUptime push-metric pattern](./oneuptime-worker2-monitoring.md): OneUptime
|
|
74
|
+
can only string-match a body, so the worker decides and emits tokens.
|
|
75
|
+
|
|
76
|
+
| Field | Meaning |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `status` | `reporting`, or `error` (reserved for the Core lookup itself failing → `error:client_lookup_failed`) |
|
|
79
|
+
| `alarm` | `DEGRADED` when any stuck integration is found, else `OK` |
|
|
80
|
+
| `probe` | `DEGRADED` when any client DB is unreachable, else `OK` |
|
|
81
|
+
| `stuckCount` | number of stuck integrations found |
|
|
82
|
+
| `offendingIntegrations` | comma-joined `"ClientName: TYPE"` |
|
|
83
|
+
| `clientsChecked`, `clientsSkipped` | coverage counters |
|
|
84
|
+
| `unreachableClients`, `unresolvedClients` | named coverage gaps |
|
|
85
|
+
| `checkedAtUtc` | `gmdate('c')` |
|
|
86
|
+
|
|
87
|
+
## Cron seed (dbchanges2)
|
|
88
|
+
|
|
89
|
+
`dbchanges2/Core/2026-08-10a - Netsuite Integrations Monitor.sql` — INSERT into
|
|
90
|
+
`Core.CronJobs`: action `Monitor/Operations/NetsuiteIntegrations`, schedule
|
|
91
|
+
`*/10 * * * *` (every 10 min), `maxExecutionTime` 120, `isActive` 1, `parameters` NULL,
|
|
92
|
+
fixed random uuid. Targets **only** `Core` (no cross-cluster refs), INSERT-only and
|
|
93
|
+
unguarded per dbchanges2 conventions. Modeled on
|
|
94
|
+
`Core/2026-07-29a - AIG Entitlement API Failure Monitor.sql`.
|
|
95
|
+
|
|
96
|
+
**Shipped `isActive=1` while the OneUptime URL is still a placeholder** (developer's
|
|
97
|
+
choice): the sweep runs but the push fails soft (`error_log`) until the real URL is pasted
|
|
98
|
+
into the worker method.
|
|
99
|
+
|
|
100
|
+
## Gotchas / known issues
|
|
101
|
+
|
|
102
|
+
- **`` `key` `` and `` `value` `` are MySQL reserved words** — backtick-quote them in the
|
|
103
|
+
`Parameters` query.
|
|
104
|
+
- **Single source of truth for the key prefix.** `$KEY_PREFIX` builds both the `LIKE`
|
|
105
|
+
pattern (via `_Database::escape`) and the `substr()` that derives the integration type;
|
|
106
|
+
never hardcode the prefix twice.
|
|
107
|
+
- **Never-throw / 200-on-all-paths.** The method always returns a summary string. The
|
|
108
|
+
OneUptime push is `setLogging(false)` + `setThrowExceptionsOnFailure(false)` **and**
|
|
109
|
+
wrapped in its own `try/catch`, so a failed ping never fails the worker job.
|
|
110
|
+
- **OneUptime push URL is a credential** — it lives only as an in-file value in the worker
|
|
111
|
+
method, never committed to dbchanges2 and never recorded in a doc. It is currently a
|
|
112
|
+
placeholder.
|
|
113
|
+
- **Business DB, not logs DB.** Join `Clients.clientDatabaseId` for `Client_<X>`; the
|
|
114
|
+
sibling EmailQueue monitor joins `logDatabaseId` for `Logs_<X>`. See
|
|
115
|
+
[per-client database connections](../../_underscore/features/per-client-database-connections.md).
|
|
116
|
+
- The all-client resolution gotchas from
|
|
117
|
+
[EmailQueue](./all-client-email-queue-monitor.md) apply verbatim: LEFT OUTER JOIN every
|
|
118
|
+
level, null host columns unless the environment join matched, detect missing table by
|
|
119
|
+
MySQL error **number** 1146 (not message text), and remember a passing dev run (single
|
|
120
|
+
all-in-one endpoint) does not prove cross-cluster correctness.
|
|
121
|
+
|
|
122
|
+
## Change history
|
|
123
|
+
|
|
124
|
+
- 2026-08-10 — Built `NetsuiteIntegrations()` + `resolveClientDatabases()` on
|
|
125
|
+
`_Worker_Monitor_Operations` (all-client `Parameters` sweep for
|
|
126
|
+
`NETSUITE_EXECUTION_MODE_%` = `1-RUNNING`), plus the `Core.CronJobs` seed
|
|
127
|
+
(`*/10 * * * *`, `isActive=1`) and an OneUptime Incoming Request monitor import. OneUptime
|
|
128
|
+
URL still a placeholder; sweep runs and pushes fail-soft until pasted. (jcardinal)
|
|
129
|
+
|
|
130
|
+
## Related docs
|
|
131
|
+
|
|
132
|
+
- [All-Client Email Queue Monitor](./all-client-email-queue-monitor.md) — the sibling
|
|
133
|
+
all-client monitor this one is modeled on.
|
|
134
|
+
- [OneUptime push-metric monitors for 2.0 workers](./oneuptime-worker2-monitoring.md) — the
|
|
135
|
+
push/token pattern and OneUptime criteria (incl. `{{requestBody.*}}` incident templating).
|
|
136
|
+
- [Per-Client Database Connections](../../_underscore/features/per-client-database-connections.md)
|
|
137
|
+
— business/log database name resolution.
|
|
138
|
+
</content>
|
|
139
|
+
</invoke>
|
|
@@ -111,6 +111,25 @@ log dive. `status: "error"` stays reserved for the checker being **fully** blind
|
|
|
111
111
|
resolution query failed). Worked example:
|
|
112
112
|
[All-Client Email Queue Monitor](./all-client-email-queue-monitor.md).
|
|
113
113
|
|
|
114
|
+
### Incident / alert title & description templating (`{{requestBody.*}}`)
|
|
115
|
+
|
|
116
|
+
OneUptime incident and alert **titles and descriptions support `{{variable}}` templating**
|
|
117
|
+
(verified against OneUptime docs 2026-08-10). For an **Incoming Request** monitor, the
|
|
118
|
+
POSTed JSON body is addressed via the `requestBody` prefix — e.g.
|
|
119
|
+
`{{requestBody.offendingIntegrations}}`, `{{requestBody.stuckCount}}`,
|
|
120
|
+
`{{requestBody.checkedAtUtc}}`, `{{requestBody.message}}`. Other available vars:
|
|
121
|
+
`{{requestHeaders}}`, `{{requestMethod}}`, `{{incomingRequestReceivedAt}}`.
|
|
122
|
+
|
|
123
|
+
- **Missing paths resolve to empty string**, so one template covers both a normal degraded
|
|
124
|
+
payload and an edge case like `error:client_lookup_failed` without a second template.
|
|
125
|
+
- Body-content criteria use `checkOn: "Request Body"` / `filterType: "Contains"`; the
|
|
126
|
+
heartbeat criterion uses `"Not Recieved In Minutes"` (note OneUptime's misspelling).
|
|
127
|
+
- A degraded criterion can OR several tokens, e.g. Contains `"alarm":"DEGRADED"` **OR**
|
|
128
|
+
`"error":"client_lookup_failed"`.
|
|
129
|
+
- Reuse the project's existing status/severity ObjectIDs when authoring the import JSON.
|
|
130
|
+
- Docs: https://oneuptime.com/docs/monitor/incident-alert-templating and
|
|
131
|
+
https://oneuptime.com/docs/en/monitor/incoming-request-monitor
|
|
132
|
+
|
|
114
133
|
### Heartbeat / cron cadence timing
|
|
115
134
|
|
|
116
135
|
The 10-min-Degraded / 15-min-Offline heartbeat thresholds pair with a **5-minute** cron
|
|
@@ -192,6 +211,10 @@ check for another client.
|
|
|
192
211
|
monitors must pass the region (see [Cloud S3 helpers](../../_underscore/features/cloud-s3-helpers.md)).
|
|
193
212
|
|
|
194
213
|
## Change history
|
|
214
|
+
- 2026-08-10 — Documented OneUptime incident/alert `{{requestBody.*}}` templating (missing
|
|
215
|
+
paths → empty string; `Not Recieved In Minutes` heartbeat spelling), discovered while
|
|
216
|
+
wiring the second all-client `_Worker_Monitor_Operations` monitor —
|
|
217
|
+
[NetSuite Integrations Monitor](./netsuite-integrations-monitor.md). (jcardinal)
|
|
195
218
|
- 2026-07-30 — Added the multi-client refinement of the payload contract: a **separate `probe`
|
|
196
219
|
token** (`OK`/`DEGRADED`) alongside `alarm`, so a partial logs-cluster outage cannot read as a
|
|
197
220
|
healthy metric, plus naming the offending clients in the body. Recorded that the previously
|
package/knowledge/INDEX.md
CHANGED
|
@@ -19,7 +19,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
19
19
|
## 2.0 framework
|
|
20
20
|
|
|
21
21
|
- **_underscore** (_Underscore) _(framework core)_ — 48 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
22
|
-
- **worker2** (Worker) —
|
|
22
|
+
- **worker2** (Worker) — 43 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
23
23
|
- **api2** (API) — 22 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
24
24
|
- **dbchanges2** (Database Changes) _(framework core)_ — 8 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
25
25
|
- **toga2-supply** (TOGa Supply) — 5 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Framework | Summary | Files |
|
|
4
4
|
|-----|-----------|---------|-------|
|
|
5
|
-
| [Elite — supply2 frontend scope (Inventory
|
|
5
|
+
| [Elite — supply2 frontend scope (Inventory + Service Requests, both built)](features/supply2-scope.md) | 2.0 | Scope for onboarding Elite to the `toga2-supply` frontend (host `ELITE`). | toga2-supply/ELITE-CLIENT-TASK-NOTES.md, toga2-supply/src/pages/Inventory/viewModel/FIELDS/DUMMYGROUPOPTIONS.ts, toga2-supply/src/pages/Inventory/viewModel/FIELDS/INVENTORYPAGEFIELDS.ts, toga2-supply/src/pages/Inventory/viewModel/index.ts, toga2-supply/src/pages/Inventory/listing/InventoryPage.tsx, toga2-supply/src/pages/Inventory/listing/InventoryRouter.tsx, toga2-supply/src/pages/Inventory/listing/InventorySubTablePage.tsx, toga2-supply/src/utils/resolveClientHostName.ts, toga2-supply/src/utils/convertConstructorColumnTitles.ts, toga2-supply/src/pages/Orders/OrdersPage.tsx, toga2-supply/src/pages/Orders/viewModel/FIELDS/ELITE/BASE.json, toga2-supply/src/pages/Orders/viewModel/useOrdersPageViewModel.ts, toga2-supply/src/pages/Orders/api/OrdersApi.ts, toga2-supply/src/pages/Orders/view/OrderView/viewModel/useOrderDetailsViewModel.ts, toga2-supply/src/components/ui/Tables/PrimaryTable/helperFunctions/renderModalContent.tsx, toga2-supply/src/components/layout/SlideMenu/SlideMenu.tsx, toga2-supply/package.json |
|
|
6
6
|
| [Elite — stale TableView config (11 dead Core.RecordFields across 9 views)](features/supply2-tableview-config-drift.md) | 2.0 | `Client_Elite`'s `TableViewJoins` predate **two** platform bridge-table migrations and still reference **11 deleted `Core.RecordFields` ids (211, 321, 932, 358, | dbchanges2/Client_Elite/, dbchanges2/Client_Nychh/2026-06-19a - FixItemFulfillmentTableViewTrackingAndRoot.sql |
|
|
7
7
|
| [Elite](profile.md) | 2.0 | Elite is a managed-services client that uses **Freshservice** as their helpdesk platform. | worker2/Worker/Elite.php, library/app/api/toga2.php |
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Elite — supply2 frontend scope (Inventory
|
|
2
|
+
title: Elite — supply2 frontend scope (Inventory + Service Requests, both built)
|
|
3
3
|
framework: "2.0"
|
|
4
4
|
repo: toga2-supply
|
|
5
5
|
project: TOGa Supply
|
|
6
6
|
client: elite
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-10
|
|
10
10
|
owners: [apeterson, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga2-supply/ELITE-CLIENT-TASK-NOTES.md
|
|
@@ -14,10 +14,14 @@ files:
|
|
|
14
14
|
- toga2-supply/src/pages/Inventory/viewModel/FIELDS/INVENTORYPAGEFIELDS.ts
|
|
15
15
|
- toga2-supply/src/pages/Inventory/viewModel/index.ts
|
|
16
16
|
- toga2-supply/src/pages/Inventory/listing/InventoryPage.tsx
|
|
17
|
+
- toga2-supply/src/pages/Inventory/listing/InventoryRouter.tsx
|
|
17
18
|
- toga2-supply/src/pages/Inventory/listing/InventorySubTablePage.tsx
|
|
18
19
|
- toga2-supply/src/utils/resolveClientHostName.ts
|
|
20
|
+
- toga2-supply/src/utils/convertConstructorColumnTitles.ts
|
|
19
21
|
- toga2-supply/src/pages/Orders/OrdersPage.tsx
|
|
22
|
+
- toga2-supply/src/pages/Orders/viewModel/FIELDS/ELITE/BASE.json
|
|
20
23
|
- toga2-supply/src/pages/Orders/viewModel/useOrdersPageViewModel.ts
|
|
24
|
+
- toga2-supply/src/pages/Orders/api/OrdersApi.ts
|
|
21
25
|
- toga2-supply/src/pages/Orders/view/OrderView/viewModel/useOrderDetailsViewModel.ts
|
|
22
26
|
- toga2-supply/src/components/ui/Tables/PrimaryTable/helperFunctions/renderModalContent.tsx
|
|
23
27
|
- toga2-supply/src/components/layout/SlideMenu/SlideMenu.tsx
|
|
@@ -32,13 +36,14 @@ related:
|
|
|
32
36
|
|
|
33
37
|
Scope for onboarding Elite to the `toga2-supply` frontend (host `ELITE`). Elite's supply2 surface
|
|
34
38
|
is two pages: **Inventory** (a single "Units By Items" group-by view, nested expand, no modal) and
|
|
35
|
-
**
|
|
39
|
+
**Service Requests** (the Orders page, host-retitled; table + details modal, **no approvals**).
|
|
36
40
|
|
|
37
|
-
**Status (2026-08-
|
|
38
|
-
first-ever host-scoping of the global inventory statics
|
|
39
|
-
[host-scoping playbook](../../../2.0/apps/toga2-supply/workflows/client-host-scoping.md),
|
|
40
|
-
reusable shape
|
|
41
|
-
|
|
41
|
+
**Status (2026-08-10): both pages are built and live-verified on beta/dev-sandbox.** Part 1 —
|
|
42
|
+
Inventory is the platform's first-ever host-scoping of the global inventory statics and follows the
|
|
43
|
+
shared [host-scoping playbook](../../../2.0/apps/toga2-supply/workflows/client-host-scoping.md),
|
|
44
|
+
where the reusable shape lives. Part 2 — "Sales Orders" was implemented and then **pivoted into
|
|
45
|
+
Service Requests**, iteratively debugged against beta 2026-08-07 → 08-10, and verified end-to-end.
|
|
46
|
+
The source-of-truth plan is `toga2-supply/ELITE-CLIENT-TASK-NOTES.md` at the repo root.
|
|
42
47
|
|
|
43
48
|
## Part 1 — Inventory (built)
|
|
44
49
|
|
|
@@ -57,41 +62,101 @@ One host-scoped group-by view only: **"Units By Items"**, no group-by modal.
|
|
|
57
62
|
Elite uuid; NYCHH's `94f1e621…` / `904cc7b1…` branches were not touched.
|
|
58
63
|
- `src/utils/resolveClientHostName.ts` (new, shared) resolves the host from
|
|
59
64
|
`useHostNameStore.getState()` with a `window.location.hostname` subdomain fallback.
|
|
60
|
-
- `SlideMenu.tsx` has an
|
|
65
|
+
- `SlideMenu.tsx` has an Elite branch whose Inventory deep-link preselects the uuid.
|
|
61
66
|
- Local run: `npm run elite` (`vite --host elite.togasupply`) plus a
|
|
62
67
|
`127.0.0.1 elite.togasupply` hosts-file entry.
|
|
63
68
|
|
|
64
|
-
**
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
69
|
+
**Hardening from the CodeRabbit review (2026-08-10)** — guardrail-preserving variants, not the
|
|
70
|
+
reviewer's literal diffs:
|
|
71
|
+
|
|
72
|
+
- `InventoryPage.tsx` **re-seeds** the persisted `localStorage("selectedGrouping")` whenever the
|
|
73
|
+
stored option's uuid is not present in the resolved host's array. This covers same-origin host
|
|
74
|
+
switches and corrupt JSON, and is a provable no-op for hosts that share the default array (their
|
|
75
|
+
stored uuids always match).
|
|
76
|
+
- `InventoryRouter.tsx` redirects `/inventory_items` → `/inventory` for any host whose default view
|
|
77
|
+
has **no `subTableParent`** — data-driven, no host literal. Without it Elite's terminal nested
|
|
78
|
+
view passes `undefined` into `PrimaryTable`.
|
|
79
|
+
|
|
80
|
+
## Part 2 — Service Requests (built, verified 2026-08-10)
|
|
81
|
+
|
|
82
|
+
**"Service Requests" is the Orders page host-configured, not a new route.** The supply2.5 mockup's
|
|
83
|
+
own source says so: `elite-app.jsx` has `VIEW_META.Orders.title = "Service Requests"` and a nav
|
|
84
|
+
entry `{label: "Orders", text: "Service Requests"}`, and its "service request details modal" is a
|
|
85
|
+
details-only order modal. The translation principle held throughout: **the 2.5 mockup is the spec
|
|
86
|
+
for WHAT, supply2.0 components are the HOW** — so this shipped as config + slug threading on the
|
|
87
|
+
existing `OrdersPage`, with zero new routes.
|
|
88
|
+
|
|
89
|
+
**Implementation**
|
|
90
|
+
|
|
91
|
+
- `src/pages/Orders/viewModel/FIELDS/ELITE/BASE.json` (new): `pageTitle` `"Service Requests"`,
|
|
92
|
+
`tableSlug` `"service-requests"`, `listingSlug` `"sales-orders-listing"`, `hasQuoteFilter` false,
|
|
93
|
+
`approvalsButton.isEnabled` false, `exportButton` true, and **no `approvalActionOptions`** (that
|
|
94
|
+
omission is what removes approvals).
|
|
95
|
+
- `useOrdersPageViewModel.ts`: an explicit `ELITE: { base: BASE_ELITE }` entry plus the host branch,
|
|
96
|
+
following the **PRUDENTIAL** `base` precedent. The broken `|| fieldMapping.compass` fallback was
|
|
97
|
+
deliberately left untouched — fixing it is out of scope and every host now needs an explicit entry
|
|
98
|
+
anyway (see the playbook).
|
|
99
|
+
- `OrdersPage.tsx`: all **8** hardcoded `"sales-orders"` literals now read `fields?.tableSlug` with a
|
|
100
|
+
`sales-orders` fallback; the page title comes from `fields`; the ACL check is optional-chained; and
|
|
101
|
+
the `isQuote` filter is **conditional** — omitted entirely when `fields?.hasQuoteFilter === false`.
|
|
102
|
+
- `SlideMenu.tsx`: the Elite nav is "Service Requests" + Inventory.
|
|
103
|
+
|
|
104
|
+
**Zero cross-client risk:** no other host defines any of the new FIELDS keys, so every default path
|
|
105
|
+
is literal-identical to before.
|
|
106
|
+
|
|
107
|
+
### The backend `service-requests` view is built on ServiceRequests, not SalesOrders
|
|
108
|
+
|
|
109
|
+
The backend table view (slug supplied by the backend dev) is built on the **ServiceRequests RECORD**
|
|
110
|
+
— joining Tickets / Customers / ServiceRequestTypes / SalesOrders, where
|
|
111
|
+
`SalesOrders.serviceRequestId = ServiceRequests.id` (1:1). It is **not** a renamed SalesOrders view.
|
|
112
|
+
Three consequences drove the rest of the work:
|
|
113
|
+
|
|
114
|
+
1. **Row uuids are ServiceRequests uuids, but every detail-modal route is a `/sales-orders` record
|
|
115
|
+
route.** Fixed with `getSalesOrderUuidForServiceRequest()` in `src/pages/Orders/api/OrdersApi.ts`
|
|
116
|
+
(`GET /sales-orders` + a ServiceRequests join, `where ServiceRequests.uuid = …`), whose resolved
|
|
117
|
+
`detailsUuid` is threaded through `useOrderDetailsViewModel.ts` via react-query. The query is
|
|
118
|
+
**enabled only for `hostName === "ELITE"`**, so every other host bypasses it entirely.
|
|
119
|
+
2. **ServiceRequests has no `isQuote` column.** `OrdersPage`'s unconditional `isQuote: false` filter
|
|
120
|
+
500'd with `1054 unknown column`; the backend confirmed the fix belongs on the FE, hence
|
|
121
|
+
`hasQuoteFilter`.
|
|
122
|
+
3. **There is no `service-requests-listing` page in `Core.Pages`.** Elite reuses
|
|
123
|
+
`sales-orders-listing`. Column headers come from the **table-view** meta, not the page meta, so
|
|
124
|
+
the only things affected by the reuse are ACL and search labels.
|
|
125
|
+
|
|
126
|
+
Verified working end-to-end on the one Elite service request that has a linked sales order
|
|
127
|
+
(ticket INC-13982).
|
|
128
|
+
|
|
129
|
+
## Open question — orphan ServiceRequests (needs backend/product input)
|
|
130
|
+
|
|
131
|
+
**5 of 6** Elite service requests on dev-sandbox have **no linked SalesOrder**, so the details modal
|
|
132
|
+
opens empty — the resolver correctly yields `null` and there is nothing to render. Undecided:
|
|
133
|
+
|
|
134
|
+
- an explicit "no order linked" empty state, or
|
|
135
|
+
- a ServiceRequest-level detail view (not just an order modal), or
|
|
136
|
+
- making order-less rows non-clickable.
|
|
137
|
+
|
|
138
|
+
The prerequisite answer is a data question: **is an order-less service request a legitimate
|
|
139
|
+
lifecycle state**, or is the dev-sandbox data simply incomplete? Do not pick a UI treatment before
|
|
140
|
+
that is answered.
|
|
141
|
+
|
|
142
|
+
## Known limitations / notes
|
|
143
|
+
|
|
91
144
|
- `Client_Elite` contains **three pre-existing test Items** (ids 3–5, created 2025-09-18 during
|
|
92
145
|
Elite onboarding) — not test data from this work.
|
|
146
|
+
- The Elite work still sits on top of the stale `Client_Elite` TableView config — see
|
|
147
|
+
[supply2-tableview-config-drift](./supply2-tableview-config-drift.md), including the
|
|
148
|
+
2026-08-07 prod→beta refresh that wiped the hand-built `service-requests` view.
|
|
93
149
|
|
|
94
150
|
## Change history
|
|
151
|
+
- 2026-08-10 — **Service Requests shipped and verified** (INC-13982): implemented as the Orders page
|
|
152
|
+
host-configured, not a new route — new `FIELDS/ELITE/BASE.json` (pageTitle/tableSlug/listingSlug/
|
|
153
|
+
`hasQuoteFilter`/no `approvalActionOptions`), explicit `ELITE` entry in `useOrdersPageViewModel`,
|
|
154
|
+
8 hardcoded `sales-orders` literals threaded through `fields?.tableSlug`, conditional `isQuote`
|
|
155
|
+
filter, and `getSalesOrderUuidForServiceRequest()` resolving ServiceRequests uuids → SalesOrders
|
|
156
|
+
record routes (ELITE-only). Recorded that the backend view is built on the ServiceRequests record
|
|
157
|
+
(no `isQuote`, no `service-requests-listing` page) and the open orphan-ServiceRequest question.
|
|
158
|
+
Also landed the CodeRabbit Inventory hardening (`selectedGrouping` re-seed, `/inventory_items`
|
|
159
|
+
redirect for terminal views). (tcox)
|
|
95
160
|
- 2026-08-06 — Elite Inventory implemented (first host-scoping of the global inventory statics):
|
|
96
161
|
separate `eliteGroupOptions` array + `getGroupOptions()`, `ELITE_UNITS_BY_ITEMS_UUID`, shared
|
|
97
162
|
`resolveClientHostName.ts`, host-scoped `getInventoryPageFields()`, additive page branches,
|
|
@@ -100,4 +165,3 @@ data-config** problem in `Client_Elite`, not a frontend one; see
|
|
|
100
165
|
keep the nav Inventory-only until the Sales Orders ticket (Orders throws for unmapped hosts).
|
|
101
166
|
(tcox)
|
|
102
167
|
- 2026-08-04 — Captured the planned Elite supply2 scope (Inventory single-view + Sales Orders without approvals) from the ELITE-CLIENT-TASK-NOTES.md scoping session; no code written yet (apeterson)
|
|
103
|
-
</content>
|
|
@@ -6,7 +6,7 @@ project: Database Changes
|
|
|
6
6
|
client: elite
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-10
|
|
10
10
|
owners: [tcox]
|
|
11
11
|
files:
|
|
12
12
|
- dbchanges2/Client_Elite/
|
|
@@ -15,6 +15,7 @@ related:
|
|
|
15
15
|
- ./supply2-scope.md
|
|
16
16
|
- ../../../2.0/apps/_underscore/features/tracking-number-bridges.md
|
|
17
17
|
- ../../../2.0/apps/_underscore/features/units-for-items-for-purchase-orders.md
|
|
18
|
+
- ../../../2.0/apps/_underscore/features/page-meta-context-field-settings.md
|
|
18
19
|
---
|
|
19
20
|
|
|
20
21
|
## Summary
|
|
@@ -87,6 +88,30 @@ Do **not** hand-patch view by view. Diff `Client_Elite.TableViewJoins` wholesale
|
|
|
87
88
|
**production as well as dev-sandbox** — production reproduces the same 500 at go-live otherwise.
|
|
88
89
|
`sales-order-shipments` (join 10) is the one case a NYCHH diff cannot resolve.
|
|
89
90
|
|
|
91
|
+
## The 2026-08-07 prod→beta refresh — what a rebuild costs you
|
|
92
|
+
|
|
93
|
+
`Client_Elite` on dev-sandbox was **rebuilt from production** at 11:55 on 2026-08-07 (with a beta API
|
|
94
|
+
redeploy). Everything hand-inserted on beta was lost, which is the strongest argument yet for the
|
|
95
|
+
recommendation above. Four distinct breakages followed, all diagnosed by reading the DB:
|
|
96
|
+
|
|
97
|
+
1. **The hand-built `service-requests` table view was wiped** — production never had it, so the
|
|
98
|
+
rebuild simply did not carry it. The backend dev recreated it by hand *again*. **Every beta
|
|
99
|
+
hand-insert must become a `dbchanges2` migration or the next refresh eats it.**
|
|
100
|
+
2. **`devteam@togatech.com` came back with an EMPTY password** (re-seeded row). Restored by copying
|
|
101
|
+
the bcrypt hash from `Client_Compass.Users` id 127046 — see the
|
|
102
|
+
[host-scoping playbook](../../../2.0/apps/toga2-supply/workflows/client-host-scoping.md).
|
|
103
|
+
3. **The Orders `isQuote` filter started 500ing** (`1054 Unknown column`) — ServiceRequests has no
|
|
104
|
+
such column; fixed on the frontend (see [supply2-scope](./supply2-scope.md)).
|
|
105
|
+
4. **Elite `/pages/meta?slug=inventory` began 500ing** with *"Undefined array key 190"* — a
|
|
106
|
+
`_underscore` framework bug in `Page::meta()` on context-scoped field settings, newly fatal
|
|
107
|
+
because of the strict Sentry error handler that shipped in the same redeploy. See
|
|
108
|
+
[legacy page meta](../../../2.0/apps/_underscore/features/page-meta-context-field-settings.md).
|
|
109
|
+
|
|
110
|
+
**Row ids are not stable across a re-import.** `TableViewJoins` ids shifted with the rebuild, so any
|
|
111
|
+
note, script, or diff that identifies a join by **row id** is worthless after a refresh. Match rows
|
|
112
|
+
by **(`tableViewId`, `joinRecordId`, `joinOnRecordFieldId`)** instead — the id columns in the table
|
|
113
|
+
above are a snapshot for orientation, not a key.
|
|
114
|
+
|
|
90
115
|
## Gotchas / known issues
|
|
91
116
|
|
|
92
117
|
- **A `Client_*` DB that predates a platform migration fails silently until a user opens the
|
|
@@ -98,10 +123,13 @@ Do **not** hand-patch view by view. Diff `Client_Elite.TableViewJoins` wholesale
|
|
|
98
123
|
[error-reporting-issue-event](../../../2.0/apps/_underscore/features/error-reporting-issue-event.md).
|
|
99
124
|
|
|
100
125
|
## Change history
|
|
126
|
+
- 2026-08-10 — Recorded the **2026-08-07 prod→beta rebuild of `Client_Elite`** and its four
|
|
127
|
+
breakages (wiped hand-built `service-requests` view, empty-password `devteam` user, the `isQuote`
|
|
128
|
+
`1054`, and the `/pages/meta` 500), plus the rule that **`TableViewJoins` row ids shift on
|
|
129
|
+
re-import — match by (`tableViewId`, `joinRecordId`, `joinOnRecordFieldId`), never by id.** (tcox)
|
|
101
130
|
- 2026-08-06 — Documented Elite's stale TableView config: 11 dead `Core.RecordFields` (211, 321,
|
|
102
131
|
932, 358, 1431) across 9 views, left behind by the tracking-number-bridge (319) and
|
|
103
132
|
`SalesOrderItems_PurchaseOrderItems` (289) migrations that NYCHH received and Elite did not.
|
|
104
133
|
Verified the view-17 fix live, then **reverted it** at the developer's request; recommended a
|
|
105
134
|
wholesale `Client_Elite` vs `Client_Nychh` diff shipped as a dbchanges2 migration to prod, and
|
|
106
135
|
flagged `sales-order-shipments` join 10 as broken in NYCHH too. (tcox)
|
|
107
|
-
</content>
|
|
@@ -10,7 +10,7 @@ project: Worker
|
|
|
10
10
|
client: elite
|
|
11
11
|
type: profile
|
|
12
12
|
status: active
|
|
13
|
-
updated: 2026-08-
|
|
13
|
+
updated: 2026-08-10
|
|
14
14
|
owners: [snaredla, apeterson, tcox]
|
|
15
15
|
files:
|
|
16
16
|
- worker2/Worker/Elite.php
|
|
@@ -28,20 +28,29 @@ Elite is a managed-services client that uses **Freshservice** as their helpdesk
|
|
|
28
28
|
TOGA 2 is the source of truth for tickets, notes, and file attachments. The integration
|
|
29
29
|
keeps Freshservice and TOGA 2 in sync via webhook-driven worker jobs.
|
|
30
30
|
|
|
31
|
-
**Elite is also being onboarded to TOGa Supply (2026-08).**
|
|
32
|
-
|
|
31
|
+
**Elite is also being onboarded to TOGa Supply (2026-08).** Both frontend pages — **Inventory** and
|
|
32
|
+
**Service Requests** (the Orders page host-retitled, not a new route) — are built and live-verified
|
|
33
|
+
on beta as of 2026-08-10; see [supply2-scope](features/supply2-scope.md). The backend blocker (stale
|
|
33
34
|
`Client_Elite` TableView config, a production go-live risk) is in
|
|
34
35
|
[supply2-tableview-config-drift](features/supply2-tableview-config-drift.md). On the backend,
|
|
35
36
|
dbchanges2 **PR #454** (TRUE-80499, snaredla24, 2026-08-05) added the **netsuite** module to
|
|
36
37
|
`Client_Elite/_modules.txt` — the supply-chain schema layer is arriving.
|
|
37
38
|
|
|
39
|
+
Elite's service-request data model: `ServiceRequests` is the record the `service-requests` table
|
|
40
|
+
view is built on (joining Tickets / Customers / ServiceRequestTypes / SalesOrders, with
|
|
41
|
+
`SalesOrders.serviceRequestId = ServiceRequests.id`, 1:1). **Most Elite service requests on
|
|
42
|
+
dev-sandbox have no linked sales order** — whether that is a legitimate lifecycle state is an open
|
|
43
|
+
question for backend/product (see supply2-scope).
|
|
44
|
+
|
|
38
45
|
**Tenant/test-account notes.** `Core.Domains` rows for Elite already exist in **every**
|
|
39
46
|
environment (dev `http://elite.togasupply`, beta, production, sandbox-dev) with no dbchanges2 Core
|
|
40
47
|
migration creating them, and Elite has **no `ClientAuthentications` row** — supply2 login is
|
|
41
48
|
ordinary password auth. The shared `devteam@togatech.com` test user was seeded into dev-sandbox
|
|
42
49
|
`Client_Elite.Users` (+ `Users_Roles`; Elite roles are 1=Base, 2=Developer, 3=API, 4=Public) from
|
|
43
50
|
`Client_Compass.Users` — see the
|
|
44
|
-
[host-scoping playbook](../../2.0/apps/toga2-supply/workflows/client-host-scoping.md).
|
|
51
|
+
[host-scoping playbook](../../2.0/apps/toga2-supply/workflows/client-host-scoping.md). **Note:**
|
|
52
|
+
`Client_Elite` on dev-sandbox was rebuilt from production on 2026-08-07, which emptied that user's
|
|
53
|
+
password and wiped hand-built config — re-seeding may be needed again after any refresh.
|
|
45
54
|
|
|
46
55
|
## TOGA 2 API credentials
|
|
47
56
|
|
package/package.json
CHANGED