toga-ai 1.0.84 → 1.0.86
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.
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# webhook (Webhook) — 1.0 knowledge
|
|
2
|
+
|
|
3
|
+
| Doc | Summary | Files |
|
|
4
|
+
|-----|---------|-------|
|
|
5
|
+
| [NetSuite → ClickUp Opportunity Sync](features/netsuite-clickup-opportunity-sync.md) | NetSuite Opportunities are pushed into the ClickUp **Opportunities** list (list id `901111987449`, in the Opportunity/RFP Hub space `90113928591`) as tasks name | webhook/_/webhook/netsuite.php, webhook/api.php, api2/Webhook/Netsuite.php, worker2/Worker/Netsuite.php, api2/Controller/Index.php |
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: NetSuite → ClickUp Opportunity Sync
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: webhook
|
|
5
|
+
project: Webhook
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-15
|
|
10
|
+
owners: ["dfranks"]
|
|
11
|
+
files:
|
|
12
|
+
- webhook/_/webhook/netsuite.php
|
|
13
|
+
- webhook/api.php
|
|
14
|
+
- api2/Webhook/Netsuite.php
|
|
15
|
+
- worker2/Worker/Netsuite.php
|
|
16
|
+
- api2/Controller/Index.php
|
|
17
|
+
related: []
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Summary
|
|
21
|
+
NetSuite Opportunities are pushed into the ClickUp **Opportunities** list (list id `901111987449`,
|
|
22
|
+
in the Opportunity/RFP Hub space `90113928591`) as tasks named `<oppNumber> — <customerNumber>
|
|
23
|
+
<customer> — <title>` (e.g. `60533 — 560 Endeavor Health — Data Center Facility Consolidation
|
|
24
|
+
Services`). The trigger and the entire task payload are **built on the NetSuite side**; TOGA only
|
|
25
|
+
relays the payload to the ClickUp API verbatim.
|
|
26
|
+
|
|
27
|
+
## Key files / entry points
|
|
28
|
+
- **Live receiver:** `webhook/_/webhook/netsuite.php` → `Webhook_NetSuite::post()` → `createTask()`,
|
|
29
|
+
reached at `https://webhook.togahub.com/netsuite` and routed by `webhook/api.php` (the legacy
|
|
30
|
+
PHP 7.2 / `App_` webhook project). This is the path NetSuite actually hits.
|
|
31
|
+
- **Inactive copies (triplicated, identical relay logic):** `api2/Webhook/Netsuite.php`
|
|
32
|
+
(`_Webhook_NetSuite::post`) and `worker2/Worker/Netsuite.php` (`_Worker_NetSuite::Webhook`).
|
|
33
|
+
The api2 webhook route (`Controller/Index.php`, `case 'webhook'`) logs inbound payloads to
|
|
34
|
+
`Logs.Api`; the worker2 Lambda path logs to `Logs.Webhook`. **Neither shows any `/netsuite`
|
|
35
|
+
traffic** — confirming the legacy `webhook/` project is the live receiver, not api2/worker2.
|
|
36
|
+
- The ClickUp call uses a token carried in the inbound payload (`$body->token`); in production
|
|
37
|
+
that token belongs to `dfranks@togatech.com`, so all integration-created tasks show that creator.
|
|
38
|
+
|
|
39
|
+
## How it works
|
|
40
|
+
1. A NetSuite event/process POSTs a JSON body to `webhook.togahub.com/netsuite`.
|
|
41
|
+
2. `createTask()` resolves the ClickUp user id for `$body->presalesLead` (cached in the client DB
|
|
42
|
+
`Users.c_clickupUserIdentifier`, else fetched from the ClickUp `/team` members list).
|
|
43
|
+
3. It POSTs a new task to `list/901111987449/task` with `name => $body->name` **verbatim**,
|
|
44
|
+
`description => $body->description`, and custom fields for sales rep / presales email / presales
|
|
45
|
+
user id / opportunity number / customer number.
|
|
46
|
+
|
|
47
|
+
The single payload-builder is NetSuite **UserEvent script 2493 "Create Clickup Opportunity"**
|
|
48
|
+
(`customscript2493`, deployment `customdeploy1`), which fires `afterSubmit` when the Opportunity
|
|
49
|
+
"Presales Lead" field (`custbody_ctc_solution_architect`, employee ref) transitions **empty→set**.
|
|
50
|
+
Its output depends entirely on the trigger **context** (because it reads field values off
|
|
51
|
+
`context.newRecord`):
|
|
52
|
+
- **Full-record contexts** — form save (`create`/`edit`), and REST/SOAP API edits (NetSuite context
|
|
53
|
+
`RWS`/`WS`, e.g. the `touch_opportunity_presales_lead.php` tool) — carry the whole record →
|
|
54
|
+
`tranid`/entity/title are present → **correct** name. Verified 2026-06-15: touching opp 5483750 via
|
|
55
|
+
REST produced `60533 — 560 Endeavor Health — Data Center Facility Consolidation Services`.
|
|
56
|
+
- **Partial-record `xedit`** — `submitFields()`, inline-grid edits, mass updates — put only the
|
|
57
|
+
changed field on `context.newRecord` → `tranid`/entity/title read empty → 2493 falls back to
|
|
58
|
+
`name = "OPP " + record.id` with empty descriptive/custom fields → the degenerate stub.
|
|
59
|
+
|
|
60
|
+
The degenerate trigger is **scheduled script 2412 "Update Opp Status / % to Close / Presales Lead"**
|
|
61
|
+
(deployment `customdeploy_update_opp_status_morning`, status SCHEDULED). It auto-assigns the Presales
|
|
62
|
+
Lead via `submitFields` (NetSuite context `SCH`, user `-System-`), which fires 2493 in `xedit` → stub.
|
|
63
|
+
|
|
64
|
+
## Data model
|
|
65
|
+
- ClickUp Opportunities list `901111987449`; custom-item id `1009`.
|
|
66
|
+
- Custom field UUIDs: SALES_REP `f2bf421f-e24c-4e1c-bea4-98a227abe087`, PRESALES_EMAIL
|
|
67
|
+
`440e2f9c-07bc-482b-9bf1-39b0e673b989`, PRESALES_USER_ID `b755ca44-7d7f-4e4b-9b93-6ee95d0ea8da`,
|
|
68
|
+
OPPORTUNITY_NUMBER `a5529cdc-8dbb-4165-8bc5-9f5cc691796a`, CUSTOMER_NUMBER `170dc118-b3fa-413b-826b-2753e9405851`.
|
|
69
|
+
- Client DB `Users` table caches `c_clickupUserIdentifier` by email.
|
|
70
|
+
|
|
71
|
+
## Client variations
|
|
72
|
+
None — single internal Agilant/True integration.
|
|
73
|
+
|
|
74
|
+
## Gotchas / known issues
|
|
75
|
+
- **`OPP <internalId>` degenerate tasks (root-caused & confirmed 2026-06-15).** A task named just
|
|
76
|
+
`OPP 7147699` with no description/fields. The name is built by NetSuite and relayed verbatim;
|
|
77
|
+
the opps are **fully populated** in NetSuite (so it's not missing data, not a TOGA bug). Cause:
|
|
78
|
+
scheduled script **2412** sets the Presales Lead via `submitFields` (empty→set) → fires UserEvent
|
|
79
|
+
**2493** in `xedit` context → 2493 reads `tranid`/entity/title off the partial `newRecord` (empty)
|
|
80
|
+
→ `name = "OPP " + id`. Confirmed via `systemnote`: each of the 4 stubs (Jun 12–13) was preceded
|
|
81
|
+
~1–2s by a `context=SCH`, user `-System-`, `CUSTBODY_CTC_SOLUTION_ARCHITECT` set (to Cory Martin,
|
|
82
|
+
employee 38886). **Regression date:** script 2412's source file
|
|
83
|
+
(`Update_Opportunity_Status_Percent_to_Close.js`, file id 5182695) was last modified **2026-06-01**,
|
|
84
|
+
and **zero** stubs existed in the prior ~14 months (full ClickUp sweep, Apr 2025→Jun 12 2026) — the
|
|
85
|
+
June 1 edit added the Presales-Lead auto-assignment. **Precise failure condition:** only the
|
|
86
|
+
*empty→set* transition fires it — a scheduled set to an already-present lead is a no-op (confirmed
|
|
87
|
+
on opp 5483750, where a 07:02 `SCH` set produced no stub because the lead was already present).
|
|
88
|
+
- **Fix options (NetSuite-side, in script 2493):**
|
|
89
|
+
- *Preferred* — `record.load({type, id: context.newRecord.id})` (or `search.lookupFields` for
|
|
90
|
+
`tranid`/entity/title) before building `name`. Works for **every** trigger context; produces
|
|
91
|
+
correct names instead of stubs-or-nothing.
|
|
92
|
+
- *Alternative* — skip on `xedit` (`if (context.type === context.UserEventType.XEDIT) return;`).
|
|
93
|
+
Suppresses the stubs but **also drops** legitimate task creation from inline-grid edits, mass
|
|
94
|
+
updates, and any other `submitFields` automation. Does **not** affect full-form saves, creates,
|
|
95
|
+
or REST/SOAP edits (incl. the touch tool, which is `RWS`/full-edit). Trades a visible bug for a
|
|
96
|
+
silent one — only acceptable if presales leads are never set inline/in bulk.
|
|
97
|
+
- *TOGA backstop (optional)* — in `createTask()`, if `name` is empty or matches `^OPP \d+$`, look
|
|
98
|
+
the opp up by internal id and rebuild the name before POSTing.
|
|
99
|
+
- **No inbound logging.** `Webhook_NetSuite::post()` has a `// Log payload to file` comment but does
|
|
100
|
+
**nothing** — the raw NetSuite payload is persisted nowhere, which is why this was hard to trace.
|
|
101
|
+
Adding real request logging here would make recurrences diagnosable immediately.
|
|
102
|
+
- **Opp#/Cust# fields swapped (separate bug, all copies).** `OPPORTUNITY_NUMBER` custom field is set
|
|
103
|
+
from `$body->customerNumber` and `CUSTOMER_NUMBER` from `$body->opportunityNumber`
|
|
104
|
+
(e.g. api2 `Netsuite.php` lines 116–122).
|
|
105
|
+
- Handler is triplicated; a fix must be applied to the live `webhook/` copy (and ideally the others
|
|
106
|
+
to avoid drift).
|
|
107
|
+
|
|
108
|
+
## Remediation (re-create a proper task for an existing stub)
|
|
109
|
+
Because the integration is create-only, an `OPP <id>` stub can't be "fixed" in place — you
|
|
110
|
+
re-fire 2493 from a full-record context, which creates a correctly-named task alongside the stub
|
|
111
|
+
(then delete the stub). Run the touch tool with the opp internal id(s):
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
php test/@dave/touch_opportunity_presales_lead.php <id1>,<id2>,...
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
It blanks then restores `custbody_ctc_solution_architect` via REST (`RWS`/full-edit), firing 2493
|
|
118
|
+
with the full record → proper `<tranid> — <custNum> <customer> — <title>` task ~1–2s later.
|
|
119
|
+
**Leaves the original stub** — delete the `OPP <id>` task in ClickUp afterward.
|
|
120
|
+
Done 2026-06-15 for opps 7147699/7147698/7147472/6382879 → 4 proper tasks created; the 4 stubs
|
|
121
|
+
(task ids `868k0tr41`, `868k0tr16`, `868k0rwtp`, `868k0h44m`) were left for deletion.
|
|
122
|
+
|
|
123
|
+
## Change history
|
|
124
|
+
- 2026-06-15 — Identified the exact NetSuite culprits (scheduled **2412** `submitFields`→`xedit`→
|
|
125
|
+
UserEvent **2493**), regression date (script 2412 modified 2026-06-01; no stubs before Jun 12),
|
|
126
|
+
and `systemnote`/`SCH` confirmation; added remediation-via-touch procedure and fix-option
|
|
127
|
+
trade-offs (`record.load` vs skip-`xedit`). (dfranks)
|
|
128
|
+
- 2026-06-15 — Documented integration + root-caused `OPP <internalId>` degenerate tasks to a NetSuite
|
|
129
|
+
scheduled (~:01 hourly) fallback payload; confirmed live receiver is `webhook/`, not api2/worker2;
|
|
130
|
+
noted no-op inbound logging and the Opp#/Cust# swap. (dfranks)
|
|
131
|
+
|
|
132
|
+
## Related docs
|
|
133
|
+
- `test/@dave/touch_opportunity_presales_lead.php` re-fires the real-time presales-lead trigger
|
|
134
|
+
(blank→restore `custbody_ctc_solution_architect`).
|
package/knowledge/INDEX.md
CHANGED
|
@@ -8,6 +8,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
8
8
|
- **worker** (Worker) — 4 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
|
|
9
9
|
- **togadesk** (TOGa Desk) — 7 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
|
10
10
|
- **togaview** (TOGa View) — 5 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
|
|
11
|
+
- **webhook** (Webhook) — 1 doc(s) → [1.0/apps/webhook/INDEX.md](1.0/apps/webhook/INDEX.md)
|
|
11
12
|
|
|
12
13
|
## 2.0 framework
|
|
13
14
|
|
package/knowledge/registry.json
CHANGED
|
@@ -11,6 +11,6 @@
|
|
|
11
11
|
{ "repo": "togadesk", "project": "TOGa Desk", "framework": "1.0", "role": "app", "dependsOn": [] },
|
|
12
12
|
{ "repo": "togaview", "project": "TOGa View", "framework": "1.0", "role": "app", "dependsOn": [] },
|
|
13
13
|
{ "repo": "toga2-hub", "project": "TOGa Hub", "framework": "2.0", "role": "app", "dependsOn": ["api2"] },
|
|
14
|
-
{ "repo": "togatech", "project": "TOGA Technology Website", "framework": "standalone", "role": "app", "dependsOn": [] }
|
|
15
|
-
|
|
14
|
+
{ "repo": "togatech", "project": "TOGA Technology Website", "framework": "standalone", "role": "app", "dependsOn": [] },
|
|
15
|
+
{ "repo": "webhook", "project": "Webhook", "framework": "1.0", "role": "app", "dependsOn": ["library"] }
|
|
16
16
|
]
|
package/package.json
CHANGED