toga-ai 1.0.782 → 1.0.784
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/dbchanges/INDEX.md +3 -3
- package/knowledge/1.0/apps/library/INDEX.md +23 -22
- package/knowledge/1.0/apps/library/features/cron-execution-monitoring.md +31 -1
- package/knowledge/1.0/apps/library/features/error-capture-1-0.md +68 -1
- package/knowledge/1.0/apps/library/features/netsuite-item-assettype-sync.md +202 -0
- package/knowledge/1.0/apps/library/features/netsuite-item-isfulfillable-sync.md +28 -2
- package/knowledge/1.0/apps/library/features/toga2-api-client-and-bridge.md +9 -1
- package/knowledge/1.0/apps/test/INDEX.md +18 -18
- package/knowledge/1.0/apps/toga/INDEX.md +4 -4
- package/knowledge/1.0/apps/togadesk/INDEX.md +14 -14
- package/knowledge/1.0/apps/togaview/INDEX.md +9 -9
- package/knowledge/1.0/apps/tools/INDEX.md +20 -20
- package/knowledge/1.0/apps/tools/features/talos-kb-documents-admin.md +0 -3
- package/knowledge/1.0/apps/tools/features/theme-light-dark.md +0 -2
- package/knowledge/1.0/apps/tools/workflows/deploy-to-elastic-beanstalk-al2023.md +0 -2
- package/knowledge/1.0/apps/walmarttechservices/INDEX.md +3 -3
- package/knowledge/1.0/apps/webhook/INDEX.md +3 -3
- package/knowledge/1.0/apps/worker/INDEX.md +21 -21
- package/knowledge/1.0/apps/worker/features/compass-ma-sales-order-exception-report.md +36 -5
- package/knowledge/1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md +181 -1
- package/knowledge/2.0/apps/_underscore/INDEX.md +59 -59
- package/knowledge/2.0/apps/_underscore/features/acl-permission-chain.md +70 -7
- package/knowledge/2.0/apps/_underscore/features/error-reporting-issue-event.md +86 -2
- package/knowledge/2.0/apps/_underscore/features/forecast-sale-import.md +0 -2
- package/knowledge/2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md +0 -4
- package/knowledge/2.0/apps/_underscore/features/model-magic-field-access.md +8 -1
- package/knowledge/2.0/apps/_underscore/features/sales-order-denial-reason.md +0 -1
- package/knowledge/2.0/apps/_underscore/features/surface-resolver.md +0 -29
- package/knowledge/2.0/apps/_underscore/features/tracking-number-bridges.md +0 -3
- package/knowledge/2.0/apps/ai-bdr/INDEX.md +15 -15
- package/knowledge/2.0/apps/ai-bdr/architecture.md +0 -2
- package/knowledge/2.0/apps/ai-bdr/features/live-call-status.md +0 -2
- package/knowledge/2.0/apps/ai-bdr/features/security-landing-page.md +0 -4
- package/knowledge/2.0/apps/ai-bdr/features/vapi-integration.md +0 -4
- package/knowledge/2.0/apps/ai-bdr/features/web-funnel-app.md +0 -7
- package/knowledge/2.0/apps/api2/INDEX.md +27 -27
- package/knowledge/2.0/apps/api2/features/api-payload-interceptors.md +0 -2
- package/knowledge/2.0/apps/api2/features/language-translation-layer.md +0 -7
- package/knowledge/2.0/apps/api2/features/nested-relationship-writes.md +115 -2
- package/knowledge/2.0/apps/api2/features/v2-rest-query-contract.md +28 -1
- package/knowledge/2.0/apps/dbchanges2/INDEX.md +12 -12
- package/knowledge/2.0/apps/dbchanges2/features/surface-layer-schema.md +0 -48
- package/knowledge/2.0/apps/dbchanges2/workflows/client-schema-drift-audit.md +130 -2
- package/knowledge/2.0/apps/dbchanges2/workflows/nonprod-metadata-drift-repair.md +43 -10
- package/knowledge/2.0/apps/saml/INDEX.md +8 -7
- package/knowledge/2.0/apps/saml/features/healthcheck-endpoint.md +133 -0
- package/knowledge/2.0/apps/talos/INDEX.md +10 -10
- package/knowledge/2.0/apps/talos/architecture.md +0 -3
- package/knowledge/2.0/apps/talos/features/aegra-api.md +0 -6
- package/knowledge/2.0/apps/talos/features/talos-agent.md +0 -3
- package/knowledge/2.0/apps/talos-backend/INDEX.md +3 -3
- package/knowledge/2.0/apps/toga-blox/INDEX.md +16 -16
- package/knowledge/2.0/apps/toga-blox/features/table.md +29 -2
- package/knowledge/2.0/apps/toga-blox/features/talos-assistant.md +0 -1
- package/knowledge/2.0/apps/toga2-commerce/INDEX.md +20 -20
- package/knowledge/2.0/apps/toga2-commerce/features/client-fields.md +29 -2
- package/knowledge/2.0/apps/toga2-commerce/features/inactive-item-purchase-gating.md +0 -2
- package/knowledge/2.0/apps/toga2-commerce/workflows/amplify-build-and-deploy.md +95 -2
- package/knowledge/2.0/apps/toga2-commerce/workflows/cypress-testing.md +0 -2
- package/knowledge/2.0/apps/toga2-hub/INDEX.md +4 -4
- package/knowledge/2.0/apps/toga2-supply/INDEX.md +10 -10
- package/knowledge/2.0/apps/toga2-supply/features/fulfill-and-ship.md +0 -2
- package/knowledge/2.0/apps/toga2-supply/workflows/client-host-scoping.md +0 -1
- package/knowledge/2.0/apps/toga2-view/INDEX.md +11 -11
- package/knowledge/2.0/apps/toga2-view/architecture.md +0 -1
- package/knowledge/2.0/apps/toga25-supply/INDEX.md +20 -20
- package/knowledge/2.0/apps/toga25-supply/features/record-modals-and-nested-tables.md +0 -4
- package/knowledge/2.0/apps/toga25-supply/features/surface-frontend.md +30 -4
- package/knowledge/2.0/apps/toga25-supply/features/talos-integration.md +0 -7
- package/knowledge/2.0/apps/toga25-supply/features/transfer-orders-page.md +0 -5
- package/knowledge/2.0/apps/toga25-supply/workflows/cypress-testing.md +0 -2
- package/knowledge/2.0/apps/voice-to-voice/INDEX.md +6 -6
- package/knowledge/2.0/apps/worker2/INDEX.md +60 -56
- package/knowledge/2.0/apps/worker2/features/clickup-general-automation.md +75 -0
- package/knowledge/2.0/apps/worker2/features/netsuite-item-client-routing.md +132 -0
- package/knowledge/2.0/apps/worker2/features/netsuite-opportunity-sync.md +0 -1
- package/knowledge/2.0/apps/worker2/features/netsuite-supporting-record-webhook-importer.md +61 -14
- package/knowledge/2.0/apps/worker2/features/netsuite-transferorder-outbound-push.md +65 -11
- package/knowledge/2.0/apps/worker2/features/oneuptime-worker2-monitoring.md +70 -4
- package/knowledge/2.0/apps/worker2/features/qa-qc-review-pipeline.md +92 -0
- package/knowledge/2.0/apps/worker2/features/sso-stability-monitor.md +102 -0
- package/knowledge/2.0/apps/worker2/features/talos-transcript-ingestion.md +0 -15
- package/knowledge/CONVENTIONS.md +51 -3
- package/knowledge/INDEX.md +3 -3
- package/knowledge/clients/adyen/INDEX.md +3 -3
- package/knowledge/clients/adyen/profile.md +87 -1
- package/knowledge/clients/aig/INDEX.md +5 -5
- package/knowledge/clients/canon/INDEX.md +3 -3
- package/knowledge/clients/compass-canada/INDEX.md +9 -9
- package/knowledge/clients/compass-canada/features/french-order-email-localization.md +0 -5
- package/knowledge/clients/compass-usa/INDEX.md +35 -34
- package/knowledge/clients/compass-usa/features/oneuptime-ma-refresh-order-monitor.md +150 -0
- package/knowledge/clients/compass-usa/profile.md +1 -0
- package/knowledge/clients/compass-usa/workflows/odp-order-pipeline-to-netsuite.md +12 -2
- package/knowledge/clients/compass-usa/workflows/order-lifecycle-and-data-integrity.md +15 -1
- package/knowledge/clients/elite/INDEX.md +9 -9
- package/knowledge/clients/elite/features/netsuite-togasupply-sync.md +241 -20
- package/knowledge/clients/elite/features/supply2-scope.md +0 -5
- package/knowledge/clients/elite/features/supply2-tableview-config-drift.md +74 -2
- package/knowledge/clients/elite/profile.md +22 -2
- package/knowledge/clients/endeavor-health/INDEX.md +3 -3
- package/knowledge/clients/fordham/INDEX.md +3 -3
- package/knowledge/clients/growrk/INDEX.md +6 -6
- package/knowledge/clients/growrk/features/transfer-order-flow.md +44 -3
- package/knowledge/clients/northwell/INDEX.md +4 -4
- package/knowledge/clients/nycdoe/INDEX.md +6 -6
- package/knowledge/clients/nycdoe/features/servicenow-integration.md +0 -2
- package/knowledge/clients/nychh/INDEX.md +9 -9
- package/knowledge/clients/nychh/features/netsuite-inventory-adjustment-fulfillment-link.md +20 -1
- package/knowledge/clients/nychh/features/netsuite-transfer-order-import.md +53 -1
- package/knowledge/clients/nychh/features/transfer-order-netsuite-push.md +38 -3
- package/knowledge/clients/nychh/profile.md +25 -4
- package/knowledge/clients/office-depot/INDEX.md +5 -5
- package/knowledge/clients/pcmaticb2b/INDEX.md +6 -6
- package/knowledge/clients/prudential/INDEX.md +14 -14
- package/knowledge/clients/quad/INDEX.md +7 -7
- package/knowledge/clients/rate/INDEX.md +14 -14
- package/knowledge/clients/rate/features/subscription-cancellation.md +0 -2
- package/knowledge/clients/rate/features/whole-home-warranty-purchase-guard.md +0 -1
- package/knowledge/clients/rumcsi/INDEX.md +3 -3
- package/knowledge/clients/spglobal/INDEX.md +3 -3
- package/knowledge/clients/staples/INDEX.md +4 -4
- package/knowledge/clients/tow-foundation/INDEX.md +4 -4
- package/knowledge/clients/true/INDEX.md +4 -4
- package/knowledge/clients/walmart/INDEX.md +5 -5
- package/knowledge/clients/wje/INDEX.md +3 -3
- package/knowledge/standalone/apps/claude/INDEX.md +7 -7
- package/knowledge/standalone/apps/forward/INDEX.md +5 -5
- package/knowledge/standalone/apps/togatech/INDEX.md +7 -7
- package/knowledge/standalone/apps/togatech/features/seo-aeo-geo-prerender.md +0 -1
- package/knowledge/standalone/apps/websocket/INDEX.md +4 -4
- package/knowledge.js +85 -18
- package/package.json +1 -1
- package/skills/kickoff/SKILL.md +11 -7
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# dbchanges (Database Changes) — 1.0 knowledge
|
|
2
2
|
|
|
3
|
-
| Doc | Summary |
|
|
4
|
-
|
|
5
|
-
| [Authoring & Shipping a 1.0 dbchanges SQL File](workflows/authoring-and-shipping-sql-files.md) | `dbchanges` is the **1.0** (legacy/V1) schema-and-data change repository — the 1.0 sibling of 2.0's `dbchanges2`. |
|
|
3
|
+
| Doc | Summary |
|
|
4
|
+
|-----|---------|
|
|
5
|
+
| [Authoring & Shipping a 1.0 dbchanges SQL File](workflows/authoring-and-shipping-sql-files.md) | `dbchanges` is the **1.0** (legacy/V1) schema-and-data change repository — the 1.0 sibling of 2.0's `dbchanges2`. |
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
# library (Library) — 1.0 knowledge
|
|
2
2
|
|
|
3
|
-
| Doc | Summary |
|
|
4
|
-
|
|
5
|
-
| [Library (1.0 Framework) Architecture](architecture.md) | `library` is the shared library repository for **all 1.0 (legacy) applications** — the `App_` framework. |
|
|
6
|
-
| [Address Validation Gateway (App_Api_OfficeDepot::validateAddress + USPS fallback)](features/address-validation-gateway.md) | `App_Api_OfficeDepot::validateAddress()` is the shared 1.0 (`App_`) address-validation gateway. |
|
|
7
|
-
| [Where a new App_ class goes — the app/ folder IS a behavioral contract](features/app-class-placement-base-contracts.md) | In `library/app/`, choosing a folder is **not** a filing decision — the autoloader maps `App_<Folder>_<File>` to `app/<folder>/<file>.php`, and each folder's ba |
|
|
8
|
-
| [App_Sso — Reusable 1.0 SSO Initiation (SP-initiated SAML via saml.togahub.com)](features/app-sso-initiation.md) | `App_Sso` (`library/app/sso.php`) is the **1.0 port of the 2.0 SAML gateway's SP-initiated SSO initiation**, packaged as a reusable, framework-level capability |
|
|
9
|
-
| [Cron Execution Monitoring (App_Framework check-in/out → CronJobExecutions)](features/cron-execution-monitoring.md) | `App_Framework::cronInitialization()` / `App_Framework::cronFinished()` (in `library/app/framework.php`) give every 1.0 (`App_`) cron job a check-in/check-out l |
|
|
10
|
-
| [Diagnostic Dialog — View Recommended Services Routing](features/diagnostic-dialog-view-recommended-services.md) | Two "View Recommended Services" buttons exist in the TOGa Refresh 2026 SR view: 1. |
|
|
11
|
-
| [Elite Freshservice Sync (library)](features/elite-freshservice-sync.md) | `App_Api_Toga2` in `library/app/api/toga2.php` orchestrates bidirectional sync between TOGA 2 and TOGaDesk. |
|
|
12
|
-
| [App_Email Queued Sending & Attachments (Common.EmailsQueued)](features/email-queue-attachments.md) | `App_Email::send()` can either send **inline** (PHPMailer talks to SES right there) or **queue** the message: `base64(serialize($this))` is inserted into `Commo |
|
|
13
|
-
| [Branded HTML Email Templates (App_Email_Template)](features/email-templates.md) | `App_Email_Template` (`app/email/template.php`) is the base class for branded HTML emails in the 1.0 (`App_`) framework. |
|
|
14
|
-
| [App_Email Side Effects & Test Mode (how to send a real email that writes nothing)](features/email-test-mode-and-write-free-sends.md) | `App_Email::send()` is **not** side-effect free. |
|
|
15
|
-
| [Error Capture in 1.0 (App_Error_Capture → shared 2.0 Logs DB)](features/error-capture-1-0.md) | The 1.0 side of the platform error-reporting pipeline (TRUE-78188). |
|
|
16
|
-
| [HTTP 500 Error Monitor (App_SystemMonitor_500Error) — and why its \"Error Type\" is not a diagnosis](features/http-500-error-monitor.md) | `App_SystemMonitor_500Error` (`library/app/systemmonitor/500error.php`, title **"HTTP 500 Error Alert"**) is the 1.0 system monitor that watches **`Logs.Api` fo |
|
|
17
|
-
| [1.0 MVC Page Pattern & New-App Skeleton](features/mvc-page-pattern-and-app-skeleton.md) | This is the **reusable recipe for standing up a new 1.0 (`App_`) application** and for adding pages to one — the folder-based MVC routing, the page lifecycle, t |
|
|
18
|
-
| [NetSuite File Cabinet Content Retrieval via RESTlet (fetchInvoiceFile)](features/netsuite-filecabinet-restlet.md) | How 1.0 pulls **File Cabinet binary content** (invoice PDFs) out of NetSuite over REST. |
|
|
19
|
-
| [
|
|
20
|
-
| [NetSuite
|
|
21
|
-
| [NetSuite SuiteQL/REST
|
|
22
|
-
| [NetSuite
|
|
23
|
-
| [
|
|
24
|
-
| [
|
|
3
|
+
| Doc | Summary |
|
|
4
|
+
|-----|---------|
|
|
5
|
+
| [Library (1.0 Framework) Architecture](architecture.md) | `library` is the shared library repository for **all 1.0 (legacy) applications** — the `App_` framework. |
|
|
6
|
+
| [Address Validation Gateway (App_Api_OfficeDepot::validateAddress + USPS fallback)](features/address-validation-gateway.md) | `App_Api_OfficeDepot::validateAddress()` is the shared 1.0 (`App_`) address-validation gateway. |
|
|
7
|
+
| [Where a new App_ class goes — the app/ folder IS a behavioral contract](features/app-class-placement-base-contracts.md) | In `library/app/`, choosing a folder is **not** a filing decision — the autoloader maps `App_<Folder>_<File>` to `app/<folder>/<file>.php`, and each folder's ba |
|
|
8
|
+
| [App_Sso — Reusable 1.0 SSO Initiation (SP-initiated SAML via saml.togahub.com)](features/app-sso-initiation.md) | `App_Sso` (`library/app/sso.php`) is the **1.0 port of the 2.0 SAML gateway's SP-initiated SSO initiation**, packaged as a reusable, framework-level capability |
|
|
9
|
+
| [Cron Execution Monitoring (App_Framework check-in/out → CronJobExecutions)](features/cron-execution-monitoring.md) | `App_Framework::cronInitialization()` / `App_Framework::cronFinished()` (in `library/app/framework.php`) give every 1.0 (`App_`) cron job a check-in/check-out l |
|
|
10
|
+
| [Diagnostic Dialog — View Recommended Services Routing](features/diagnostic-dialog-view-recommended-services.md) | Two "View Recommended Services" buttons exist in the TOGa Refresh 2026 SR view: 1. |
|
|
11
|
+
| [Elite Freshservice Sync (library)](features/elite-freshservice-sync.md) | `App_Api_Toga2` in `library/app/api/toga2.php` orchestrates bidirectional sync between TOGA 2 and TOGaDesk. |
|
|
12
|
+
| [App_Email Queued Sending & Attachments (Common.EmailsQueued)](features/email-queue-attachments.md) | `App_Email::send()` can either send **inline** (PHPMailer talks to SES right there) or **queue** the message: `base64(serialize($this))` is inserted into `Commo |
|
|
13
|
+
| [Branded HTML Email Templates (App_Email_Template)](features/email-templates.md) | `App_Email_Template` (`app/email/template.php`) is the base class for branded HTML emails in the 1.0 (`App_`) framework. |
|
|
14
|
+
| [App_Email Side Effects & Test Mode (how to send a real email that writes nothing)](features/email-test-mode-and-write-free-sends.md) | `App_Email::send()` is **not** side-effect free. |
|
|
15
|
+
| [Error Capture in 1.0 (App_Error_Capture → shared 2.0 Logs DB)](features/error-capture-1-0.md) | The 1.0 side of the platform error-reporting pipeline (TRUE-78188). |
|
|
16
|
+
| [HTTP 500 Error Monitor (App_SystemMonitor_500Error) — and why its \"Error Type\" is not a diagnosis](features/http-500-error-monitor.md) | `App_SystemMonitor_500Error` (`library/app/systemmonitor/500error.php`, title **"HTTP 500 Error Alert"**) is the 1.0 system monitor that watches **`Logs.Api` fo |
|
|
17
|
+
| [1.0 MVC Page Pattern & New-App Skeleton](features/mvc-page-pattern-and-app-skeleton.md) | This is the **reusable recipe for standing up a new 1.0 (`App_`) application** and for adding pages to one — the folder-based MVC routing, the page lifecycle, t |
|
|
18
|
+
| [NetSuite File Cabinet Content Retrieval via RESTlet (fetchInvoiceFile)](features/netsuite-filecabinet-restlet.md) | How 1.0 pulls **File Cabinet binary content** (invoice PDFs) out of NetSuite over REST. |
|
|
19
|
+
| [assetType from NetSuite itemtype during Item Sync (opt-in per client)](features/netsuite-item-assettype-sync.md) | `getCreateItem()` never sent `assetType`, so **every item the NetSuite importer created had `Items.assetTypeId = NULL`** — for every client, since the importer |
|
|
20
|
+
| [isFulfillable from NetSuite during Item Sync (Phase 1)](features/netsuite-item-isfulfillable-sync.md) | This is the **1.0 (Phase 1)** half of the `isFulfillable` feature: reading the NetSuite `isfulfillable` flag during item sync and stamping it onto the **Agilant |
|
|
21
|
+
| [NetSuite SuiteQL/REST API Reference](features/netsuite-suiteql-api-reference.md) | General working reference for the Agilant NetSuite integration: how to authenticate, how SuiteQL behaves, and the confirmed schema of the tables/columns/codes w |
|
|
22
|
+
| [NetSuite SuiteQL/REST Shim — Field Semantics](features/netsuite-suiteql-rest-shim.md) | `App_Api_Netsuite_Rest` is the REST/SuiteQL replacement for the deprecated NetSuite SOAP toolkit. |
|
|
23
|
+
| [NetSuite Sync Alert Monitor (App_SystemMonitor_NetSuiteIntegration)](features/netsuite-sync-alert-monitor.md) | `App_SystemMonitor_NetSuiteIntegration` (`library/app/systemmonitor/netsuiteintegration.php`, title **"NetSuite Sync Alert"**) is a 1.0 system monitor that watc |
|
|
24
|
+
| [Startech PC Matic B2B Sync (library)](features/startech-pcmaticb2b-sync.md) | `library/app/api/toga2.php` handles bidirectional ticket sync for PC Matic B2B between TOGaDesk 1.0 and TOGA 2.0. |
|
|
25
|
+
| [App_Api_Toga2 — TOGa2 API Client & 1.0↔2.0 Sync Bridge](features/toga2-api-client-and-bridge.md) | `App_Api_Toga2` (`library/app/api/toga2.php`, ~8400 lines) is the **1.0-side client for the TOGa 2 (`_underscore`/api2) public API** *and* the home of the cross |
|
|
@@ -6,12 +6,14 @@ project: Library
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08
|
|
9
|
+
updated: 2026-09-08
|
|
10
10
|
owners: [dfranks, bala]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/framework.php
|
|
13
13
|
related:
|
|
14
14
|
- ../../worker/architecture.md
|
|
15
|
+
- ./email-queue-attachments.md
|
|
16
|
+
- ../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md
|
|
15
17
|
- ../../worker/workflows/tracing-a-worker-cron-run-in-production.md
|
|
16
18
|
---
|
|
17
19
|
|
|
@@ -51,6 +53,28 @@ row in `db_log` and of the per-job Sentry check-in monitors.
|
|
|
51
53
|
(`db_common`), *not* by POSTing from 1.0 to a 2.0 API endpoint. Per Jeff Cardinal, 1.0 code
|
|
52
54
|
must not POST to 2.0 code; the direct shared-DB write is the sanctioned path (TRUE-78182).
|
|
53
55
|
|
|
56
|
+
## For an emailing cron, this table is the ONLY proof of a run
|
|
57
|
+
|
|
58
|
+
There is **no sent-email log for a 1.0 `App_Email` / `App_Email_Agilant` ops email**, so
|
|
59
|
+
`CronJobExecutions` is the only per-run evidence a 1.0 emailing cron leaves:
|
|
60
|
+
|
|
61
|
+
- `Logs_<Client>.Email` holds **2.0 application email only**. Verified 2026-09-08: zero rows in
|
|
62
|
+
`Logs_Compass.Email` for the Compass Refresh Exception Report, a 1.0 cron email that has been
|
|
63
|
+
sending for months.
|
|
64
|
+
- `Common.EmailsQueued` is a **queue drained on send**, holding a serialized object — not a log.
|
|
65
|
+
See [App_Email Queued Sending & Attachments](./email-queue-attachments.md).
|
|
66
|
+
|
|
67
|
+
Two consequences:
|
|
68
|
+
|
|
69
|
+
1. **A cron whose only output is a "something is wrong" email is effectively unmonitored** — a
|
|
70
|
+
silent day and a dead cron look identical. Monitor the **backlog the cron drains**, not the
|
|
71
|
+
email. See
|
|
72
|
+
[OneUptime push-metric monitors for 2.0 workers](../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md).
|
|
73
|
+
2. **worker2 (2.0) has no proven connection to the legacy `Common` DB**, so a 2.0 OneUptime
|
|
74
|
+
monitor cannot read `CronJobExecutions` today. A generic "this 1.0 cron has not checked in"
|
|
75
|
+
monitor is therefore blocked on that connection, not on the data — the columns needed
|
|
76
|
+
(`dtCheckIn`, `job`, `instanceId`) are already there.
|
|
77
|
+
|
|
54
78
|
## Gotchas
|
|
55
79
|
|
|
56
80
|
- **⚠ A run skipped by the overlap guard leaves NO ROW — absence is ambiguous.**
|
|
@@ -76,6 +100,12 @@ row in `db_log` and of the per-job Sentry check-in monitors.
|
|
|
76
100
|
logic. (Fixed 2026-07-06: consolidated back to one `db_common` INSERT/UPDATE pair.)
|
|
77
101
|
|
|
78
102
|
## Change history
|
|
103
|
+
- 2026-09-08 — Recorded that `CronJobExecutions` is the **only** per-run proof for a 1.0
|
|
104
|
+
*emailing* cron, because there is no sent-email log: `Logs_<Client>.Email` is 2.0-only (zero
|
|
105
|
+
rows for a 1.0 report that has emailed for months) and `Common.EmailsQueued` is a queue drained
|
|
106
|
+
on send. Hence the rule to monitor the backlog rather than the email, and the note that worker2
|
|
107
|
+
has no proven legacy-`Common` connection, so a 2.0 "cron has not checked in" monitor is not
|
|
108
|
+
possible yet. No code change. (bala)
|
|
79
109
|
- 2026-08-25 — Corrected the schema note (prod `CronJobExecutions` **does** carry a `note` TEXT
|
|
80
110
|
column, unused by `App_Framework`, usable for temporary cron tracing) and separated it from the
|
|
81
111
|
`note = 'Started execution'` row `cronInitialization()` writes to `Log` on `db_log`. Recorded
|
|
@@ -6,12 +6,14 @@ project: Library
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08
|
|
9
|
+
updated: 2026-09-08
|
|
10
10
|
owners: ["jcardinal", "mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/error/capture.php
|
|
13
13
|
- library/app/error.php
|
|
14
14
|
- library/app/exception/business.php
|
|
15
|
+
- library/app/exception/api.php
|
|
16
|
+
- library/app/api/netsuite/rest.php
|
|
15
17
|
- library/app/api/toga2.php
|
|
16
18
|
- library/app/cloud.php
|
|
17
19
|
- worker/config.worker.ini
|
|
@@ -78,6 +80,50 @@ whichever occurrence happened to be seen first — i.e. one arbitrary order numb
|
|
|
78
80
|
|
|
79
81
|
Compass USA's sales-order → MITS transmit rejection was the first business-exception use case.
|
|
80
82
|
|
|
83
|
+
### `App_Exception_Api` — one Issue per outbound-API outage across both frameworks (added 2026-09-08)
|
|
84
|
+
|
|
85
|
+
The 1.0 twin of 2.0's `_Exception_Api`. Read the
|
|
86
|
+
[2.0 section](../../../2.0/apps/_underscore/features/error-reporting-issue-event.md#outbound-third-party-api-errors-fingerprint-on-servicestatusoperation-not-the-trace-added-2026-09-08)
|
|
87
|
+
for the full rationale — this covers only the 1.0 specifics.
|
|
88
|
+
|
|
89
|
+
- **`library/app/exception/api.php`** — `class App_Exception_Api extends Exception`, carrying
|
|
90
|
+
`service` / `httpStatus` / `operation`. **PHP 7.2, so untyped properties with `@var` docblocks**
|
|
91
|
+
(typed properties fail to parse in `library/` — same trap as `App_Exception_Business`). Extends
|
|
92
|
+
plain `Exception`, so it stays HTTP 500 + creates a `Logs.Issue`.
|
|
93
|
+
- **`App_Error_Capture` gains the same `instanceof` fingerprint branch** →
|
|
94
|
+
`buildApiFingerprint()`, keyed on `service + httpStatus + operation`, with the fingerprint text
|
|
95
|
+
using the **FIXED literal `'apiError'` prefix** — byte-identical to 2.0's, so one
|
|
96
|
+
service+status+operation is **one Issue whichever framework threw it**. Do not derive the prefix
|
|
97
|
+
from the class name (`App_Exception_Api` ≠ `_Exception_Api`), and do not reformat the string on
|
|
98
|
+
either side.
|
|
99
|
+
- **A new `firstApplicationArea()` helper was added here.** The 1.0 twin previously lacked it — the
|
|
100
|
+
DB fingerprint branch is 2.0-only, so 1.0 had no such helper before this. `buildApiFingerprint()`
|
|
101
|
+
caps `operation` at 200 chars, matching 2.0.
|
|
102
|
+
|
|
103
|
+
#### The NetSuite client is where granularity is decided — `library/app/api/netsuite/rest.php`
|
|
104
|
+
|
|
105
|
+
A private `buildApiException(message, method, url, statusCode)` was added, and the **3 HTTP-status
|
|
106
|
+
throw sites** (OAuth token, API request, RESTlet call) switched from plain `\Exception` to it. The
|
|
107
|
+
**3 config/parse-error throws** in the same file (no HTTP status) were left as plain `\Exception`
|
|
108
|
+
on purpose.
|
|
109
|
+
|
|
110
|
+
- **5xx** (NetSuite-side outage) → `operation = 'SERVER_ERROR'` (constant), so the whole outage
|
|
111
|
+
folds into ONE Issue regardless of endpoint or client.
|
|
112
|
+
- **4xx** (request-specific) → `operation = "<METHOD> <path>"`, so a genuine per-endpoint problem
|
|
113
|
+
keeps its own Issue. Because every `list*` call goes through `suiteqlListAll()` hitting the one
|
|
114
|
+
endpoint `POST /query/v1/suiteql`, all the `list*` 400s collapse together on their own.
|
|
115
|
+
- **cso hardening (in `buildApiException`):** id-like path segments are masked
|
|
116
|
+
(`preg_replace('#/\d+(?=/|$)#', '/{id}')`); an empty parsed path uses a fixed `UNKNOWN_PATH`
|
|
117
|
+
placeholder — **never the raw `$url`**, which would carry the query string back into the shared
|
|
118
|
+
fingerprint. Net guarantee: only `service` + `httpStatus` + `operation` (all controlled values)
|
|
119
|
+
reach `Logs.IssueFingerprint` — the vendor message body never does.
|
|
120
|
+
|
|
121
|
+
> **OPEN (pre-existing, not fixed here):** the assembled failure message still carries
|
|
122
|
+
> `print_r($response)` (the full vendor body, which can hold one tenant's PII) into
|
|
123
|
+
> `Issue.subject` / `Event.errorMessage` on the shared `Logs` cluster. Unchanged from the old plain
|
|
124
|
+
> throw; never touches the fingerprint. Worth a future ticket to cap/redact — same shape as the
|
|
125
|
+
> raw-SQL-in-subject open item on the 2.0 doc.
|
|
126
|
+
|
|
81
127
|
### Client attribution — resolved from the API client uuid at `App_Api_Toga2::authenticate()`
|
|
82
128
|
|
|
83
129
|
`App_Error::setCurrentClientId()` exists in 1.0 for parity with 2.0's ambient current-client, but
|
|
@@ -177,6 +223,10 @@ exists in `api2`/`worker2`. So 1.0 reads the AL1 container config at
|
|
|
177
223
|
|
|
178
224
|
## Gotchas / known issues
|
|
179
225
|
|
|
226
|
+
- **A `*/` inside PHP docblock prose silently closes the comment early.** Writing something like
|
|
227
|
+
`list*/fetch*` in a `/** … */` block ends the docblock at the `*/`, and `php -l` then reports a
|
|
228
|
+
confusing `syntax error, unexpected token` on a later line. Hit while documenting the NetSuite
|
|
229
|
+
throw sites. Avoid the literal `*/` in docblock text.
|
|
180
230
|
- **⚠ A grouped `Logs.Issue` row can show a STALE error id — read the live api2 transaction log for
|
|
181
231
|
the current error.** Two facts combine to mislead: (1) `Event.errorMessage` is **`varchar(255)`**,
|
|
182
232
|
so a long API response is **truncated** — often before the trailing error id; and (2)
|
|
@@ -229,6 +279,23 @@ exists in `api2`/`worker2`. So 1.0 reads the AL1 container config at
|
|
|
229
279
|
|
|
230
280
|
## Change history
|
|
231
281
|
|
|
282
|
+
- 2026-09-08 — **Added the 1.0 twin of the outbound-API fingerprint fix.** NetSuite failures were
|
|
283
|
+
**over-splitting** into ~40 Issues per outage (top frames differ per `list*`/`fetch*` caller and
|
|
284
|
+
per-client cron; prod `Logs.Issue` 678–692 = one HTTP 500 OAuth outage, 699–731 = one HTTP 400
|
|
285
|
+
wave). New `App_Exception_Api` (`library/app/exception/api.php`, extends plain `Exception`,
|
|
286
|
+
**PHP 7.2 untyped `@var` props**; `service`/`httpStatus`/`operation`), an `instanceof`
|
|
287
|
+
`buildApiFingerprint()` branch in `App_Error_Capture` keyed on service+status+operation with a
|
|
288
|
+
**FIXED literal `'apiError'` prefix** byte-identical to 2.0's (so one outage = one Issue across
|
|
289
|
+
both frameworks), and a new `firstApplicationArea()` helper (1.0 lacked it — the DB branch is
|
|
290
|
+
2.0-only). `library/app/api/netsuite/rest.php` gained a private `buildApiException()` and its
|
|
291
|
+
3 HTTP-status throw sites moved off plain `\Exception` (the 3 config/parse throws stayed plain on
|
|
292
|
+
purpose); granularity is set there — 5xx → constant `operation='SERVER_ERROR'`, 4xx →
|
|
293
|
+
`"<METHOD> <path>"` (all `list*` go through one `suiteql` endpoint so their 400s collapse). cso
|
|
294
|
+
hardening: id-like path segments masked to `/{id}`, empty path → `UNKNOWN_PATH` (never the raw
|
|
295
|
+
URL), `operation` capped at 200 chars — only controlled values reach the fingerprint. Deploy
|
|
296
|
+
`_underscore`, then `library`, then redeploy `worker`; not yet proven against a live failure.
|
|
297
|
+
**OPEN (pre-existing):** the message still carries `print_r($response)` (vendor body / PII) into
|
|
298
|
+
`Issue.subject`/`Event.errorMessage` — never touches the fingerprint. (jcardinal)
|
|
232
299
|
- 2026-08-17 — Recorded a diagnostic trap: a grouped `Logs.Issue` row can show a **stale error id**
|
|
233
300
|
because `Event.errorMessage` is `varchar(255)` (truncates a long API response, often before the
|
|
234
301
|
error id) and `Issue.subject`/`errorMessage` keep the **first-occurrence** text — observed issue
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: assetType from NetSuite itemtype during Item Sync (opt-in per client)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: library
|
|
5
|
+
project: Library
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-04
|
|
10
|
+
owners: [rgirish]
|
|
11
|
+
files:
|
|
12
|
+
- library/app/api/toga2.php
|
|
13
|
+
- library/app/api/netsuite/rest.php
|
|
14
|
+
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
15
|
+
- worker/crons/toga2/netsuite/sync_togasupply_elite.php
|
|
16
|
+
related:
|
|
17
|
+
- netsuite-item-isfulfillable-sync.md
|
|
18
|
+
- netsuite-suiteql-rest-shim.md
|
|
19
|
+
- toga2-api-client-and-bridge.md
|
|
20
|
+
- ../../worker/features/netsuite-togasupply-per-client-sync.md
|
|
21
|
+
- ../../../2.0/apps/api2/features/nested-relationship-writes.md
|
|
22
|
+
- ../../../2.0/apps/_underscore/features/acl-permission-chain.md
|
|
23
|
+
- ../../../clients/elite/features/netsuite-togasupply-sync.md
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Summary
|
|
27
|
+
|
|
28
|
+
`getCreateItem()` never sent `assetType`, so **every item the NetSuite importer created had
|
|
29
|
+
`Items.assetTypeId = NULL`** — for every client, since the importer began. The string `assetType`
|
|
30
|
+
appeared nowhere in `library/app/api/toga2.php` nor in `worker/crons/toga2/netsuite/*`.
|
|
31
|
+
|
|
32
|
+
Measured on production `Client_Elite.Items`: **113 rows, 110 with `assetTypeId = NULL`**. The only
|
|
33
|
+
3 non-NULL rows (`Laptop`, `Test`, `Test 2`) have `c_netsuiteInternalItemId = NULL` and
|
|
34
|
+
`inventoryType = HYBRID` — hand-made test rows, not imported ones.
|
|
35
|
+
|
|
36
|
+
This doc covers the fix: NetSuite's `item.itemtype` is now mapped to an `AssetTypes.uuid` and
|
|
37
|
+
stamped on the item. It is **opt-in per client** and deliberately narrow, because two platform
|
|
38
|
+
facts (below) make the obvious implementation break item imports outright.
|
|
39
|
+
|
|
40
|
+
Sibling doc: [isFulfillable](netsuite-item-isfulfillable-sync.md) — the other NetSuite flag stamped
|
|
41
|
+
by the same function. **The two are not symmetric** and must not be implemented the same way; see
|
|
42
|
+
[Why this is not modeled on isFulfillable](#why-this-is-not-modeled-on-isfulfillable).
|
|
43
|
+
|
|
44
|
+
## ⚠⚠ TWO blockers — read before adding this for another client
|
|
45
|
+
|
|
46
|
+
Both were found under CTO review of a first proposal that looked obviously correct. Either one
|
|
47
|
+
turns "the asset type is not set" into **"the item create fails outright."**
|
|
48
|
+
|
|
49
|
+
### 1. `Items.assetTypeId` is not writable by the API role in most tenants
|
|
50
|
+
|
|
51
|
+
api2 **aborts the entire record write** when a payload names a field the authenticating role cannot
|
|
52
|
+
write. The NetSuite sync authenticates as **roleId 3 (API)**. So simply adding `assetType` to the
|
|
53
|
+
payload does not merely fail to set the asset type — it **fails the item create**.
|
|
54
|
+
|
|
55
|
+
Verified on production `Client_Elite.AclFieldPermissions`:
|
|
56
|
+
|
|
57
|
+
| recordFieldId | Field | Roles granted |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| **269** | `Items.assetTypeId` | roleId **1 (Base) only** |
|
|
60
|
+
| 107 | `Items.manufacturerId` | roleId **1 and 3 (API)** |
|
|
61
|
+
|
|
62
|
+
**A `dbchanges2/Client_<Tenant>/` grant must land BEFORE the code deploy.** Elite's is
|
|
63
|
+
`dbchanges2/Client_Elite/2026-09-04a - EliteItemAssetTypeApiWritePermission.sql`. Mechanics:
|
|
64
|
+
[ACL permission chain](../../../2.0/apps/_underscore/features/acl-permission-chain.md).
|
|
65
|
+
|
|
66
|
+
### 2. `assetTypeId`'s childPolicy is `MATCH`, not `MATCH_UPSERT` — so get-or-create does NOT work
|
|
67
|
+
|
|
68
|
+
`getCreateManufacturer()` sits in the **same function** and does a get-or-create by name. **That
|
|
69
|
+
pattern does not transfer.** Manufacturers auto-create precisely because their field is
|
|
70
|
+
`MATCH_UPSERT`; `assetTypeId` is plain `MATCH`.
|
|
71
|
+
|
|
72
|
+
Verified on production `Core.RecordFields`: id **269** (`assetTypeId`) = `MATCH`; id **107**
|
|
73
|
+
(`manufacturerId`) = `MATCH_UPSERT`.
|
|
74
|
+
|
|
75
|
+
With `MATCH`, sending `assetType: {name: 'Services'}` for a name that has no row is a
|
|
76
|
+
**field-reference error that fails the record** — it does not create the row. Correct approach:
|
|
77
|
+
**resolve name → uuid up front (`GET /asset-types`) and send `{uuid}`.** Policy semantics:
|
|
78
|
+
[nested-relationship writes](../../../2.0/apps/api2/features/nested-relationship-writes.md).
|
|
79
|
+
|
|
80
|
+
> **General lesson:** never reason by analogy from a sibling nested write in the same function.
|
|
81
|
+
> Check that field's own `Core.RecordFields.childPolicy` first. Two fields on the same record
|
|
82
|
+
> routinely differ.
|
|
83
|
+
|
|
84
|
+
## Key files / entry points
|
|
85
|
+
|
|
86
|
+
- `library/app/api/netsuite/rest.php` — `fetchItemFlagsByIds()` now selects `itemtype` in its
|
|
87
|
+
SuiteQL and exposes it as `->itemType`. **Free**: that query already selected from `item` once
|
|
88
|
+
per 500-id batch, so no extra round-trip. Returns `null` for an empty itemtype.
|
|
89
|
+
- `library/app/api/toga2.php`
|
|
90
|
+
- `getAssetTypeUuidForNetsuiteItemType()` (~6358) — new private helper. Resolves a NetSuite
|
|
91
|
+
itemtype to an `AssetTypes.uuid` through the launcher's opt-in map. Returns `null` when the
|
|
92
|
+
client has not opted in or the itemtype is unmapped; **throws** when a configured mapping names
|
|
93
|
+
an asset type the client does not have.
|
|
94
|
+
- `getCreateItem()` (~6474) — sends `assetType: {uuid}` on create, and **fills it on existing
|
|
95
|
+
items only when currently NULL**.
|
|
96
|
+
- `worker/crons/toga2/netsuite/common_sync_togasupply.php` (~298) — the bulk `/items` GET now
|
|
97
|
+
requests `assetType => ['uuid']`. **Without this the fill-NULL-only check has nothing to read and
|
|
98
|
+
would re-PUT every item every run** (the same trap `isFulfillable` fell into — see its doc).
|
|
99
|
+
- `worker/crons/toga2/netsuite/sync_togasupply_elite.php` — the only launcher that opts in today.
|
|
100
|
+
|
|
101
|
+
## How it works
|
|
102
|
+
|
|
103
|
+
1. `fetchItemFlagsByIds()` returns `->itemType` alongside the serialized / fulfillable flags, one
|
|
104
|
+
SuiteQL query per 500 items.
|
|
105
|
+
2. `getCreateItem()` calls `getAssetTypeUuidForNetsuiteItemType($itemFlags->itemType, …)`.
|
|
106
|
+
3. The helper reads the launcher constant `NETSUITE_ITEM_TYPE_TO_ASSET_TYPE_NAME`
|
|
107
|
+
(`itemtype => AssetTypes.name`). **If the constant is not defined, it returns `null`** and the
|
|
108
|
+
`assetType` key is **omitted from the payload entirely**.
|
|
109
|
+
4. On first need it `GET /asset-types` once and builds a `strtoupper(name) => uuid` map, skipping
|
|
110
|
+
rows with a NULL/blank name. **The cache is keyed by client uuid** — one worker process can
|
|
111
|
+
handle several clients in a run.
|
|
112
|
+
5. Name not found → **throw**, naming the itemtype, the mapped name, and the fix. Fail loud, per the
|
|
113
|
+
team rule for import/sync/cron code.
|
|
114
|
+
6. Create → `assetType: {uuid}` in the POST. Existing item → set it **only if
|
|
115
|
+
`$existingItem->assetType` is NULL**.
|
|
116
|
+
|
|
117
|
+
## Design rules (and why each one)
|
|
118
|
+
|
|
119
|
+
| Rule | Why |
|
|
120
|
+
|---|---|
|
|
121
|
+
| **Opt-in via a launcher constant** | `getCreateItem()` is shared by ~20 client sync launchers. Undefined constant ⇒ the payload key is absent ⇒ the other ~19 clients are **byte-for-byte unaffected**. |
|
|
122
|
+
| **Link by uuid, never by name** | `assetTypeId` is `MATCH` — a name for a missing row fails the whole record (blocker 2). |
|
|
123
|
+
| **Never auto-create an `AssetTypes` row** | `AssetTypes` is free text with per-client `AUTO_INCREMENT` ids and **no slug or code**, so nothing marks a row as sync-made vs. operator-made. Verified: `Client_Elite.AssetTypes` has 2 rows (1=`Laptop`, 2=`Services`); `Client_Compass.AssetTypes` has **38 hand-curated rows** (`ACCESSORY`, `EQUIPMENT`, `FEE`, `CONSULTING`, `WARRANTY`, `HP LAPTOP ACCESSORIES`, … plus an **empty-name row at id 23**) and **no `Services` row at all**. Auto-creating would pollute that taxonomy. |
|
|
124
|
+
| **Fail loudly on a missing asset type** | A silent skip leaves a client half-stamped with no signal. |
|
|
125
|
+
| **Fill-NULL-only on existing items** | `assetTypeId` is shared, **hand-curated per-client** data — unlike `isFulfillable`, which is a boolean NetSuite owns. Also means a manual TOGa correction of a NetSuite-mis-typed item **survives later syncs**. |
|
|
126
|
+
| **Cache keyed by client uuid** | One worker process can service several clients. |
|
|
127
|
+
|
|
128
|
+
## Why this is not modeled on isFulfillable
|
|
129
|
+
|
|
130
|
+
[isFulfillable](netsuite-item-isfulfillable-sync.md) **refreshes on every difference** — and that
|
|
131
|
+
doc's own ⚠ CRITICAL section records the cost: the refresh **reverts any local override**, so a
|
|
132
|
+
hand fix silently disappears within a day.
|
|
133
|
+
|
|
134
|
+
`assetType` deliberately does the opposite (**fill-NULL-only**). NetSuite does not own this field;
|
|
135
|
+
the client's operators do. Do not "make it consistent" with `isFulfillable` — the asymmetry is the
|
|
136
|
+
design.
|
|
137
|
+
|
|
138
|
+
## NetSuite `itemtype` does NOT cleanly identify services
|
|
139
|
+
|
|
140
|
+
`item.itemtype` **is** populated (account-wide: InvtPart 51336, NonInvtPart 2652, Group 585,
|
|
141
|
+
Service 472, Kit 50, OthCharge 24, Discount 17, Expense 6, Description 3). But **`NonInvtPart` is
|
|
142
|
+
mixed** — it holds real services. For Elite (113 items: 65 InvtPart, 11 Service, 17 NonInvtPart),
|
|
143
|
+
`itemtype = 'Service'` alone marks only **11 of ~28** real services; the rest sit in `NonInvtPart`
|
|
144
|
+
(`SVC-FS-DEPLOY`, `SVC-FS-SHIPPING-*`, `SVC-CI-RETAINER-RS`, `SVC-TS-Removal`, `Project - Cabling`).
|
|
145
|
+
|
|
146
|
+
So a usable mapping must include the non-inventory types, not just `Service`. Elite's map is in
|
|
147
|
+
[Elite's sync doc](../../../clients/elite/features/netsuite-togasupply-sync.md).
|
|
148
|
+
|
|
149
|
+
**Residual gap: NetSuite data hygiene, not a code problem.** Three Elite items are typed
|
|
150
|
+
`InvtPart` in NetSuite though they are services (`SVC-TS-ELITE-HDONBOARDING`, `CONFIG/INSTALL`,
|
|
151
|
+
`CONF-RM-INSTALL-SUPP`). **Decided: this is a client data-hygiene ask, NOT a code special case** —
|
|
152
|
+
a part-number allowlist inside a 20-client shared import is unmaintainable. Fill-NULL-only means a
|
|
153
|
+
manual TOGa correction sticks.
|
|
154
|
+
|
|
155
|
+
## Gotchas / known issues
|
|
156
|
+
|
|
157
|
+
- **⚠ Existing items stay NULL until a cursor reset.** `getCreateItem()` only runs when a
|
|
158
|
+
transaction line references an item, so already-imported items are **not** back-stamped by
|
|
159
|
+
turning this on. Elite's 110 NULL items stay NULL until a cursor rollback makes the sync re-walk
|
|
160
|
+
transactions that reference them. Cursors live in the **client** database `Parameters` table
|
|
161
|
+
(key `NETSUITE_LAST_SYNC_CURSOR_*`, value `"<lastmodifieddate>|<netsuiteInternalId>"`), read via
|
|
162
|
+
`GET /parameters`. See [per-client sync](../../worker/features/netsuite-togasupply-per-client-sync.md).
|
|
163
|
+
- **⚠ Lumping `Discount` / `Expense` / `Description` into a "Services" asset type is semantically
|
|
164
|
+
loose.** Harmless while `assetTypeId` only hides rows on a page — a **latent bug if it ever drives
|
|
165
|
+
billing or reporting.** Revisit the map before wiring `assetTypeId` into either.
|
|
166
|
+
- **An empty-name `AssetTypes` row exists in the wild** (`Client_Compass` id 23). The name→uuid
|
|
167
|
+
cache skips NULL/blank names so it can never be matched by accident.
|
|
168
|
+
- **⚠ PRE-EXISTING BUG, deliberately NOT fixed — needs its own ticket.** The fulfillability refresh
|
|
169
|
+
(`toga2.php` ~6547) reads `$existingItem->isFulfillable`, but the bulk `GET /items` lookup in
|
|
170
|
+
`common_sync_togasupply.php` **never requested that field**. So `$currentFulfillable` is always
|
|
171
|
+
`null` and the sync almost certainly **PUTs `isFulfillable` on every item, every run, for every
|
|
172
|
+
client**. Left untouched on purpose: fixing it changes behavior for all ~20 clients and was out of
|
|
173
|
+
scope. This is the exact trap `assetType` avoids by requesting `assetType => ['uuid']` in the same
|
|
174
|
+
lookup.
|
|
175
|
+
|
|
176
|
+
## Change history
|
|
177
|
+
|
|
178
|
+
- 2026-09-04 — **Built assetType stamping from NetSuite `itemtype`.** Root-caused that
|
|
179
|
+
`getCreateItem()` never sent `assetType`, so every imported item had `assetTypeId = NULL` (prod
|
|
180
|
+
`Client_Elite`: 110 of 113). Added `itemtype` to `fetchItemFlagsByIds()`'s existing SuiteQL
|
|
181
|
+
(free), the `getAssetTypeUuidForNetsuiteItemType()` helper, `assetType => ['uuid']` to the bulk
|
|
182
|
+
`/items` GET, and the create/fill paths. Recorded the **two blockers** that break the naive
|
|
183
|
+
version — `Items.assetTypeId` (recordField 269) is granted to **roleId 1 only** while the sync
|
|
184
|
+
authenticates as roleId 3, and api2 fails the **whole** record write on a non-writable field; and
|
|
185
|
+
269's childPolicy is **`MATCH`**, not the `MATCH_UPSERT` that makes the sibling
|
|
186
|
+
`getCreateManufacturer()` get-or-create work, so a name-only nested object is a hard failure.
|
|
187
|
+
Design: opt-in per launcher, link by uuid, never auto-create an `AssetTypes` row, fail loud on a
|
|
188
|
+
missing one, **fill-NULL-only** (the deliberate opposite of `isFulfillable`'s revert-everything
|
|
189
|
+
refresh). Also collapsed the existing-item branch's **three separate PUTs to the same
|
|
190
|
+
`/items/<uuid>`** into one, and recorded the pre-existing `isFulfillable`-never-requested bug as a
|
|
191
|
+
separate ticket. (rgirish)
|
|
192
|
+
|
|
193
|
+
## Related docs
|
|
194
|
+
|
|
195
|
+
- [isFulfillable from NetSuite during Item Sync](netsuite-item-isfulfillable-sync.md) — the sibling
|
|
196
|
+
flag on the same function, with the opposite refresh policy.
|
|
197
|
+
- [Nested-relationship writes](../../../2.0/apps/api2/features/nested-relationship-writes.md) —
|
|
198
|
+
`MATCH` vs `MATCH_UPSERT` and why link-by-uuid is the only safe form.
|
|
199
|
+
- [ACL permission chain](../../../2.0/apps/_underscore/features/acl-permission-chain.md) — the
|
|
200
|
+
field-write grant this feature needs per tenant.
|
|
201
|
+
- [Elite NetSuite → TOGa Supply sync](../../../clients/elite/features/netsuite-togasupply-sync.md)
|
|
202
|
+
— the only client opted in, and its itemtype map.
|
|
@@ -6,14 +6,15 @@ project: Library
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [bala]
|
|
9
|
+
updated: 2026-09-04
|
|
10
|
+
owners: [bala, rgirish]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/netsuite.php
|
|
13
13
|
- library/app/api/toga2.php
|
|
14
14
|
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
15
15
|
- worker/crons/toga2/netsuite/backfill_isfulfillable_jul5.php
|
|
16
16
|
related:
|
|
17
|
+
- netsuite-item-assettype-sync.md
|
|
17
18
|
- toga2-api-client-and-bridge.md
|
|
18
19
|
- ../../worker/features/netsuite-togasupply-per-client-sync.md
|
|
19
20
|
- ../../worker/workflows/isfulfillable-multi-client-backfill.md
|
|
@@ -116,6 +117,22 @@ sweep that started 13:45 and set every item to `1`.
|
|
|
116
117
|
each verified to trace through an Agilant source.
|
|
117
118
|
|
|
118
119
|
## Gotchas / known issues
|
|
120
|
+
- **⚠⚠ The refresh's diff is BROKEN — `isFulfillable` is never read back, so it is PUT on every
|
|
121
|
+
item, every run, for every client.** The refresh at `toga2.php` ~6547 compares NetSuite's value
|
|
122
|
+
against `$existingItem->isFulfillable`, but the bulk `GET /items` lookup in
|
|
123
|
+
`common_sync_togasupply.php` **never requested that field** — so `$currentFulfillable` is always
|
|
124
|
+
`null` and the diff can never match. The 2026-07-28 entry below claims the field was added to that
|
|
125
|
+
field list; **it is not there** (verified 2026-09-04). Net effect: the "diff-only PUT keeps
|
|
126
|
+
re-syncs no-op" guarantee does not hold. **Not fixed — needs its own ticket**, because a fix
|
|
127
|
+
changes write volume for all ~20 clients. The sibling
|
|
128
|
+
[assetType feature](netsuite-item-assettype-sync.md) avoids this by requesting
|
|
129
|
+
`assetType => ['uuid']` in the same lookup.
|
|
130
|
+
- **The existing-item branch now sends ONE PUT, not three.** It previously issued up to three
|
|
131
|
+
separate PUTs to the same `/items/<uuid>` with identical options (`inventoryType`,
|
|
132
|
+
`isFulfillable`, and now `assetType`). One payload is collected and a single PUT is sent when
|
|
133
|
+
non-empty. Side effect worth knowing: `inventoryType` was previously never written back to the
|
|
134
|
+
in-run lookup, so a repeat line for the same part number re-PUT it — all three fields now update
|
|
135
|
+
the lookup.
|
|
119
136
|
- **Value stamped only on the Agilant source item here** — the client-facing copy is set by the 2.0
|
|
120
137
|
interceptor. If the interceptor rows aren't deployed in the target env, api2 **403s the whole item
|
|
121
138
|
write** on the unknown `isFulfillable` field (see the Phase-2 doc's deploy gotchas).
|
|
@@ -125,6 +142,15 @@ sweep that started 13:45 and set every item to `1`.
|
|
|
125
142
|
in `Client_Compass.Apis` (name `Agilant`) — never reproduce the secret value.
|
|
126
143
|
|
|
127
144
|
## Change history
|
|
145
|
+
- 2026-09-04 — **Two corrections found while building the sibling
|
|
146
|
+
[assetType stamping](netsuite-item-assettype-sync.md) on the same function.** (1) The
|
|
147
|
+
existing-item **refresh diff never works**: the bulk `GET /items` field list in
|
|
148
|
+
`common_sync_togasupply.php` does **not** request `isFulfillable` (contrary to the 2026-07-28
|
|
149
|
+
entry below), so `$currentFulfillable` is always `null` and the flag is almost certainly PUT on
|
|
150
|
+
every item, every run, for every client. Recorded as a separate ticket — not fixed, because it
|
|
151
|
+
changes write volume for ~20 clients. (2) The existing-item branch's up-to-**three** PUTs to the
|
|
152
|
+
same `/items/<uuid>` were collapsed into a **single** PUT, and `inventoryType` is now written back
|
|
153
|
+
to the in-run lookup (it previously was not, so a repeat part number re-PUT it). (rgirish)
|
|
128
154
|
- 2026-08-12 — Prod investigation (no code change): recorded that the **existing-item refresh
|
|
129
155
|
reverts any local override** (NetSuite returns `T` for services; audit-log proof on Compass items
|
|
130
156
|
2382/2384/2385, stamped NULL→1 on 2026-08-04), so an override must be enforced by a write-time
|
|
@@ -6,7 +6,7 @@ project: Library
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-08
|
|
10
10
|
owners: [jcardinal, mhammontree, bala]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/api/toga2.php
|
|
@@ -413,6 +413,14 @@ enable flags** and an optional `$monitorTogadeskDepartmentIds[]`:
|
|
|
413
413
|
|
|
414
414
|
## Change history
|
|
415
415
|
|
|
416
|
+
- 2026-09-08 - Added `updateTransferOrderStageToClosed(&$nsOrder, array &$clientConfiguration): bool`
|
|
417
|
+
and the constant `TRANSFER_ORDER_STATUS__CLOSED = 'closed'` (also now used by the `case 'Closed'` in
|
|
418
|
+
the transfer-order status switch). It is a **stage-only** writer: it never touches line items, so it
|
|
419
|
+
cannot reproduce the URI-length / HTTP 414 blowup that the importer's `Closed` skip exists to avoid,
|
|
420
|
+
and it never creates a transfer order we did not import. It exists because the sync loop's
|
|
421
|
+
`status === 'Closed'` `continue` sits before the transfer-order branch, leaving 18 NYCHH transfer
|
|
422
|
+
orders on `Pending`. Written, **not committed or deployed**. Mechanism and the rejected alternative:
|
|
423
|
+
[per-client sync](../../worker/features/netsuite-togasupply-per-client-sync.md). (bala)
|
|
416
424
|
- 2026-09-02 — `syncPurchaseOrderFromNetsuite`'s cleanup DELETE ("remove dropped items") was a **bare
|
|
417
425
|
throwing** DELETE that froze the whole PURCHASE_ORDERS section whenever a NetSuite-dropped PO line
|
|
418
426
|
still had a RESTRICT child (item receipt `ItemReceiptItems.purchaseOrderItemId`, or an SO/TO link).
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# test (Test) — 1.0 knowledge
|
|
2
2
|
|
|
3
|
-
| Doc | Summary |
|
|
4
|
-
|
|
5
|
-
| [Test (test) Architecture](architecture.md) | `test` (project **Test**) is a **repository of ad-hoc developer scripts** — not a deployed application. |
|
|
6
|
-
| [2.0 Deployment — Per-Client SQL Generator](features/2-0-deployment-client-sql.md) | `team/2.0 deployment/generate_client_sql.php` fans a single SQL change-set out across **all 2.0 client databases**. |
|
|
7
|
-
| [Active Directory Authentication Test](features/active-directory-auth-test.md) | `team/active_directory_authentication_test.php` is an interactive CLI tool to test **Active Directory authentication**. |
|
|
8
|
-
| [Compass retrofix2 — Retroactive Data-Fix SQL Generator](features/compass-retrofix2-sql-generator.md) | `@jeff/compass/retrofix2.php` is a standalone **1.0 `App_`** script that retroactively repairs historical Compass USA (`Client_Compass`, 2.0) order data left in |
|
|
9
|
-
| [Create Elastic Beanstalk Environment (script)](features/create-elastic-beanstalk.md) | `team/aws/create_elastic_beanstalk.php` is a **standalone** (no `App_` framework) constants-driven PHP generator. |
|
|
10
|
-
| [Developer Generators (password, UUID)](features/dev-generators.md) | Two tiny **1.0 `App_` framework** convenience scripts for everyday developer needs. |
|
|
11
|
-
| [Forecast vs NetSuite Discrepancy Analysis](features/forecast-netsuite-discrepancy-analysis.md) | `team/forecast-netsuite/discrepancy_analysis.php` detects discrepancies between our **Forecast database** and **NetSuite** (the source of truth for all sales da |
|
|
12
|
-
| [GitHub Audit Script (team/github_audit.php)](features/github-audit-script.md) | `team/github_audit.php` is a **standalone** (no `App_` framework) browser/CLI script that reports lines added/removed, commits, unique authors, and active repos |
|
|
13
|
-
| [@goagilant.com → @togatech.com Email-Domain Migration (1.0 + 2.0)](features/goagilant-to-togatech-email-migration.md) | Reference + technique for migrating the company email domain `@goagilant.com` → `@togatech.com` across **both** platforms. |
|
|
14
|
-
| [Static (no-DB) Regression Harness for 1.0 Logic + Source Drift Guard](features/static-no-db-regression-harness.md) | 1.0 has **no PHPUnit**, and most of its business logic sits inside methods that also write SQL, so "just call it" means standing up a client database. |
|
|
15
|
-
| [TableView Builder (2.0 TableViews SQL generator)](features/tableview-builder.md) | `team/tableViewBuilder/` generates SQL `INSERT` statements for the **2.0 `TableViews`**, `TableViewFields`, and `TableViewJoins` tables from a plain SQL `SELECT |
|
|
16
|
-
| [Talos Knowledge Base Pipeline (Uploader + Processor)](features/talos-kb-pipeline.md) | `team/talos/` holds the two-script web tooling that feeds the **TOGa Talos** (TOGa IQ) AI knowledge bases. |
|
|
17
|
-
| [TOGa 2.0 Client Onboarding SQL Generator](features/toga2-client-onboarding-sql.md) | > **Superseded by the browser wizard.** The generation logic here was extracted into the reusable > `OnboardingSqlGenerator` class and wrapped in a local browse |
|
|
18
|
-
| [TOGa 2.0 Client Onboarding Wizard (local tool)](features/toga2-onboarding-wizard.md) | A **local browser wizard** (`test/team/onboarding/`) that automates 2.0 client onboarding end to end: it (1) gathers developer input and **generates all onboard |
|
|
19
|
-
| [TOGa 2.0 User Cross-Client Access SQL Generator](features/toga2-user-cross-client-access-sql.md) | `team/generate_toga2_user_access_sql.php` generates SQL to grant an existing 2.0 user from a **home client** access to a **cross client**. |
|
|
20
|
-
| [URL & Domain Markdown Document Builder](features/url-domain-markdown-document.md) | `team/build_url_domain_markdown_document.php` generates a **markdown document of our URLs and domains** by pulling environments and domains from the 2.0 platfor |
|
|
3
|
+
| Doc | Summary |
|
|
4
|
+
|-----|---------|
|
|
5
|
+
| [Test (test) Architecture](architecture.md) | `test` (project **Test**) is a **repository of ad-hoc developer scripts** — not a deployed application. |
|
|
6
|
+
| [2.0 Deployment — Per-Client SQL Generator](features/2-0-deployment-client-sql.md) | `team/2.0 deployment/generate_client_sql.php` fans a single SQL change-set out across **all 2.0 client databases**. |
|
|
7
|
+
| [Active Directory Authentication Test](features/active-directory-auth-test.md) | `team/active_directory_authentication_test.php` is an interactive CLI tool to test **Active Directory authentication**. |
|
|
8
|
+
| [Compass retrofix2 — Retroactive Data-Fix SQL Generator](features/compass-retrofix2-sql-generator.md) | `@jeff/compass/retrofix2.php` is a standalone **1.0 `App_`** script that retroactively repairs historical Compass USA (`Client_Compass`, 2.0) order data left in |
|
|
9
|
+
| [Create Elastic Beanstalk Environment (script)](features/create-elastic-beanstalk.md) | `team/aws/create_elastic_beanstalk.php` is a **standalone** (no `App_` framework) constants-driven PHP generator. |
|
|
10
|
+
| [Developer Generators (password, UUID)](features/dev-generators.md) | Two tiny **1.0 `App_` framework** convenience scripts for everyday developer needs. |
|
|
11
|
+
| [Forecast vs NetSuite Discrepancy Analysis](features/forecast-netsuite-discrepancy-analysis.md) | `team/forecast-netsuite/discrepancy_analysis.php` detects discrepancies between our **Forecast database** and **NetSuite** (the source of truth for all sales da |
|
|
12
|
+
| [GitHub Audit Script (team/github_audit.php)](features/github-audit-script.md) | `team/github_audit.php` is a **standalone** (no `App_` framework) browser/CLI script that reports lines added/removed, commits, unique authors, and active repos |
|
|
13
|
+
| [@goagilant.com → @togatech.com Email-Domain Migration (1.0 + 2.0)](features/goagilant-to-togatech-email-migration.md) | Reference + technique for migrating the company email domain `@goagilant.com` → `@togatech.com` across **both** platforms. |
|
|
14
|
+
| [Static (no-DB) Regression Harness for 1.0 Logic + Source Drift Guard](features/static-no-db-regression-harness.md) | 1.0 has **no PHPUnit**, and most of its business logic sits inside methods that also write SQL, so "just call it" means standing up a client database. |
|
|
15
|
+
| [TableView Builder (2.0 TableViews SQL generator)](features/tableview-builder.md) | `team/tableViewBuilder/` generates SQL `INSERT` statements for the **2.0 `TableViews`**, `TableViewFields`, and `TableViewJoins` tables from a plain SQL `SELECT |
|
|
16
|
+
| [Talos Knowledge Base Pipeline (Uploader + Processor)](features/talos-kb-pipeline.md) | `team/talos/` holds the two-script web tooling that feeds the **TOGa Talos** (TOGa IQ) AI knowledge bases. |
|
|
17
|
+
| [TOGa 2.0 Client Onboarding SQL Generator](features/toga2-client-onboarding-sql.md) | > **Superseded by the browser wizard.** The generation logic here was extracted into the reusable > `OnboardingSqlGenerator` class and wrapped in a local browse |
|
|
18
|
+
| [TOGa 2.0 Client Onboarding Wizard (local tool)](features/toga2-onboarding-wizard.md) | A **local browser wizard** (`test/team/onboarding/`) that automates 2.0 client onboarding end to end: it (1) gathers developer input and **generates all onboard |
|
|
19
|
+
| [TOGa 2.0 User Cross-Client Access SQL Generator](features/toga2-user-cross-client-access-sql.md) | `team/generate_toga2_user_access_sql.php` generates SQL to grant an existing 2.0 user from a **home client** access to a **cross client**. |
|
|
20
|
+
| [URL & Domain Markdown Document Builder](features/url-domain-markdown-document.md) | `team/build_url_domain_markdown_document.php` generates a **markdown document of our URLs and domains** by pulling environments and domains from the 2.0 platfor |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# toga (TOGa) — 1.0 knowledge
|
|
2
2
|
|
|
3
|
-
| Doc | Summary |
|
|
4
|
-
|
|
5
|
-
| [Bundle Confirmation — Cart Preservation When Adding Add-On Services](features/bundleconfirmation-cart-preservation.md) | Fix: adding any add-on service (Data Transfer, Promotional Bundle, New PC Services, etc.) was silently removing the 1-year tech support SKU from the cart. |
|
|
6
|
-
| [ODP Customer Search — Loyalty Number (merchantId) Persistence](features/odp-customer-search-loyalty-persistence.md) | Fix: the ODP loyalty number (`memberId`) stopped persisting to `Customers.merchantId` in the `TOGA_ODP` legacy database — the last non-null value was **2023-11- |
|
|
3
|
+
| Doc | Summary |
|
|
4
|
+
|-----|---------|
|
|
5
|
+
| [Bundle Confirmation — Cart Preservation When Adding Add-On Services](features/bundleconfirmation-cart-preservation.md) | Fix: adding any add-on service (Data Transfer, Promotional Bundle, New PC Services, etc.) was silently removing the 1-year tech support SKU from the cart. |
|
|
6
|
+
| [ODP Customer Search — Loyalty Number (merchantId) Persistence](features/odp-customer-search-loyalty-persistence.md) | Fix: the ODP loyalty number (`memberId`) stopped persisting to `Customers.merchantId` in the `TOGA_ODP` legacy database — the last non-null value was **2023-11- |
|