toga-ai 1.0.110 → 1.0.112
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.
|
@@ -7,5 +7,5 @@
|
|
|
7
7
|
| [Creating Worker Actions](features/creating-worker-actions.md) | How to add a new callable Worker action — a PHP class whose `public static` methods are invoked as background jobs (via webhook, cron, or `_Worker::runTask()`). | worker2/Worker/, worker2/Controller/Index.php, _underscore/Worker.php |
|
|
8
8
|
| [Elite Freshservice Sync (worker2)](features/elite-freshservice-sync.md) | `_Worker_Elite` processes Freshservice webhook events and syncs them into TOGA 2. | worker2/Worker/Elite.php, worker2/Config/dev-kmaramreddy-laptop.ini |
|
|
9
9
|
| [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/Notification/Email.php, dbchanges2/Core/2026-05-21 - Monitors.sql |
|
|
10
|
-
| [NetSuite → 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/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, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
|
|
10
|
+
| [NetSuite → 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/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, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
|
|
11
11
|
| [Teams Meeting Transcript Export](features/teams-transcript-export.md) | `_Worker_Team_Transcripts` (action `Team/Transcripts/Export`) polls Microsoft Graph for Teams meeting transcripts produced by a set of organizers, classifies ea | worker2/Worker/Team/Transcripts.php, worker2/Config/production.ini |
|
|
@@ -18,6 +18,8 @@ files:
|
|
|
18
18
|
- test/@dave/NetSuite/api-message-queue/ue_amq_drain.js
|
|
19
19
|
- test/@dave/NetSuite/api-message-queue/ss_amq_drain.js
|
|
20
20
|
- test/@dave/NetSuite/api-message-queue/DEPLOY_RUNBOOK.md
|
|
21
|
+
- test/@dave/clickup/backfill_opportunity_numbers.php
|
|
22
|
+
- test/@dave/clickup/probe_opportunity_fields.php
|
|
21
23
|
- worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php
|
|
22
24
|
related:
|
|
23
25
|
- ../architecture.md
|
|
@@ -80,19 +82,50 @@ second, independently-gated concern in the same handler.
|
|
|
80
82
|
|
|
81
83
|
## ClickUp opportunity task
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
`
|
|
85
|
-
|
|
85
|
+
`maybeCreateClickupTask()` runs on both POST and PUT, gated on a **Presales Lead**
|
|
86
|
+
(`custbody_ctc_solution_architect` employee → email; empty email = skip). It is **find-or-update**
|
|
87
|
+
(dedup, since 2026-06-16):
|
|
88
|
+
|
|
89
|
+
1. `findClickupTaskByOpportunityNumber($tranId)` — GET the list filtered by the `Opportunity #`
|
|
90
|
+
custom field (`include_closed=true&include_archived=true&custom_fields=[{field_id,operator:'=',value}]`).
|
|
91
|
+
2. **Match → `updateTask()`**: PUT name/description, then POST each custom field individually to
|
|
92
|
+
`/task/{id}/field/{fieldId}` (ClickUp has no bulk custom-field set on an existing task).
|
|
93
|
+
3. **No match → `createTask()`**: POST to list `901111987449`, `custom_item_id` = `1009`.
|
|
94
|
+
|
|
95
|
+
Shared helpers: `buildCustomFields()` (the field array, used by both create and update),
|
|
96
|
+
`resolvePresalesUserId()` (cached `Client_True.Users.c_clickupUserIdentifier` lookup → else a
|
|
97
|
+
`GET /team` roster scan that back-fills the cache via `cacheClickupUser()`).
|
|
86
98
|
|
|
87
99
|
- **Task name** = `{opportunityNumber} — {customer} — {title}`, e.g.
|
|
88
100
|
`60533 — 560 Endeavor Health — Data Center Facility Consolidation Services` (empty parts dropped).
|
|
101
|
+
- **Task description** = a fixed structured block built by `buildDescription()`:
|
|
102
|
+
```
|
|
103
|
+
Company: 560 Endeavor Health
|
|
104
|
+
Opportunity: 60533 — Data Center Facility Consolidation Services
|
|
105
|
+
Amount: 750000
|
|
106
|
+
Expected Close: Tue Nov 24 2026 00:00:00 GMT-0800 (PST)
|
|
107
|
+
Stage: 10%
|
|
108
|
+
|
|
109
|
+
Details:
|
|
110
|
+
<memo>
|
|
111
|
+
|
|
112
|
+
NetSuite Internal ID: 5483750
|
|
113
|
+
```
|
|
114
|
+
Field sources (confirmed against opp 5483750 via `test/@dave/clickup/probe_opportunity_fields.php`):
|
|
115
|
+
Company = `entity.refName`; Opportunity = `tranId` + `title`; Amount = `projectedTotal`
|
|
116
|
+
(`formatAmount()`, plain number); Expected Close = `expectedCloseDate` (`formatExpectedClose()` —
|
|
117
|
+
JS `Date.toString()` style in **America/Los_Angeles**, e.g. `GMT-0800 (PST)`, auto PST/PDT by date);
|
|
118
|
+
Stage = `entityStatus.refName` (the percent string like "10%", NOT `probability`); Details = `memo`
|
|
119
|
+
(which carries NetSuite's "Missing Required Details…" fallback verbatim for pre-mandatory records).
|
|
120
|
+
The blank separator lines are a single space. Rendered verbatim — no trailing-period normalization.
|
|
89
121
|
- **Custom-field value mapping** (the non-obvious part — these were swapped before 2026-06-16):
|
|
90
122
|
`Opportunity #` (`a5529cdc-…`) ← `tranId`; `Customer #` (`170dc118-…`) ← `entity->refName`
|
|
91
123
|
(the customer **name**, deliberately — not the NetSuite customer number). Plus `Sales Rep`,
|
|
92
|
-
`Presales Lead Email`, and `Presales Lead` (users field
|
|
93
|
-
`Client_True.Users.c_clickupUserIdentifier` lookup, falling back to a `GET /team` scan).
|
|
124
|
+
`Presales Lead Email`, and `Presales Lead` (users field).
|
|
94
125
|
- The ClickUp token comes from `[clickup] token` in the worker2 config (legacy webhook received
|
|
95
126
|
it on the payload; the new contract drops it).
|
|
127
|
+
- Every ClickUp `_ApiRequest` calls `setLogging(self::CLICKUP_API_LOGGING_ENABLED=false)` — the
|
|
128
|
+
`_ApiRequest` Logs-DB default otherwise surfaces a misleading "connection refused" on dev laptops.
|
|
96
129
|
|
|
97
130
|
## Data model
|
|
98
131
|
|
|
@@ -158,13 +191,29 @@ None — platform-wide Forecast sync.
|
|
|
158
191
|
(`https://webhook.togahub.com/netsuite`) with `debugWrap` off. Also flip the enqueuer deployment
|
|
159
192
|
`Testing → Released` (Testing fires only for the deploying user). Full checklist in
|
|
160
193
|
`DEPLOY_RUNBOOK.md` §6.
|
|
161
|
-
- **
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
194
|
+
- **ClickUp dedup keys on the `Opportunity #` field, not a DB column.** We deliberately did NOT add
|
|
195
|
+
a `clickupTaskId` column — the dedup lookup queries ClickUp itself by `Opportunity #` (= tranId).
|
|
196
|
+
Consequences to know:
|
|
197
|
+
- **Legacy tasks had `Opportunity #` empty** (the old `webhook/` handler never set it), so they
|
|
198
|
+
can't be matched until backfilled — see the backfill tool below. Without backfill, the first
|
|
199
|
+
edit of a pre-existing opp creates a *second* task.
|
|
200
|
+
- **The find-then-create is not atomic.** Two near-simultaneous events for one opp (e.g. the
|
|
201
|
+
NetSuite hourly `OPP` fallback firing alongside a real edit) can both miss and both create. A
|
|
202
|
+
committed local task id would close most of that window; the field-lookup approach can't.
|
|
203
|
+
ClickUp custom fields aren't unique-constrained, so some race exists regardless.
|
|
204
|
+
- **Update is per-field** (`/task/{id}/field/{id}` — no bulk set), so a mid-loop failure leaves a
|
|
205
|
+
partially-updated task; the thrown error names how far it got. The Presales Lead users field is
|
|
206
|
+
**add-only** on update (we don't read the prior assignee to remove it).
|
|
207
|
+
- **Null guard:** the Presales Lead users field is omitted when the email has no matching ClickUp
|
|
208
|
+
user — a `null` in the `add` array is a 400 from ClickUp.
|
|
209
|
+
- **Backfill legacy `Opportunity #` with `test/@dave/clickup/backfill_opportunity_numbers.php`.**
|
|
210
|
+
Parses the opportunity number from the task title (the leading integer before the separator) and
|
|
211
|
+
writes it to the `Opportunity #` field, **never overwriting** a set value. Two legacy title
|
|
212
|
+
formats exist and both must be handled: em-dash (`74253 — 5780 Cdw — …`) **and** plain hyphen
|
|
213
|
+
(`70539 - 450 Living Well - …`). Safe by default: dry-run unless `--commit`, one task unless
|
|
214
|
+
`--all`; `--task=<id>` targets one. Token from `CLICKUP_API_KEY`. Run 2026-06-16: 146/148 tasks
|
|
215
|
+
backfilled; the 2 skipped are genuine test tasks (`Test Opportunity 2`, `TEST TEST 71437 …` —
|
|
216
|
+
number not leading).
|
|
168
217
|
- **NetSuite doubled-prefix ids.** The queue record and its fields were created with the full
|
|
169
218
|
custom prefix typed into the ID box, so the real ids are doubled:
|
|
170
219
|
type `customrecordcustomrecord_api_msg_queue`, fields `custrecordcustrecord_amq_*`. The
|
|
@@ -178,6 +227,16 @@ None — platform-wide Forecast sync.
|
|
|
178
227
|
(`Created`,`Pending`,`Retry`,`Sending`) — omitting `Created` silently matches nothing (`candidates:0`).
|
|
179
228
|
|
|
180
229
|
## Change history
|
|
230
|
+
- 2026-06-16 — ClickUp task description switched to a structured block (`buildDescription()` +
|
|
231
|
+
`formatAmount()`/`formatExpectedClose()`): Company/Opportunity/Amount/Expected Close/Stage/Details/
|
|
232
|
+
Internal ID. Field sources confirmed via `probe_opportunity_fields.php`; Expected Close rendered
|
|
233
|
+
JS-style in Pacific to match the legacy format; Details = memo. (dfranks)
|
|
234
|
+
- 2026-06-16 — ClickUp dedup implemented (find-or-update by `Opportunity #`, no DB column): edits
|
|
235
|
+
now update the existing task instead of duplicating. Added `findClickupTaskByOpportunityNumber()`,
|
|
236
|
+
`updateTask()`, and shared `buildCustomFields()`/`resolvePresalesUserId()`/`cacheClickupUser()`
|
|
237
|
+
helpers; `setLogging(false)` on all ClickUp calls; null-user guard on the users field;
|
|
238
|
+
`include_archived=true` in the lookup. Built `backfill_opportunity_numbers.php` and backfilled
|
|
239
|
+
146/148 legacy tasks (two title formats: em-dash + hyphen). (dfranks)
|
|
181
240
|
- 2026-06-16 — ClickUp parity hardening (TRUE-79182): verified the `Opportunity #`/`Customer #`
|
|
182
241
|
custom fields exist on list 901111987449 and **fixed their swapped values**; `PUT` now also
|
|
183
242
|
creates the ClickUp task (was POST-only); task name reformatted to
|
package/package.json
CHANGED