toga-ai 1.0.113 → 1.0.114
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,6 +7,6 @@
|
|
|
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, test/@dave/clickup/backfill_opportunity_numbers.php, test/@dave/clickup/probe_opportunity_fields.php, 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, test/@dave/probe_clickup_desc_match.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
|
|
11
11
|
| [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, test/@dave/probe_salesorder_rest_shape.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
|
|
12
12
|
| [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 |
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-17
|
|
10
10
|
owners: ["dfranks"]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Netsuite.php
|
|
@@ -20,6 +20,7 @@ files:
|
|
|
20
20
|
- test/@dave/NetSuite/api-message-queue/DEPLOY_RUNBOOK.md
|
|
21
21
|
- test/@dave/clickup/backfill_opportunity_numbers.php
|
|
22
22
|
- test/@dave/clickup/probe_opportunity_fields.php
|
|
23
|
+
- test/@dave/probe_clickup_desc_match.php
|
|
23
24
|
- worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php
|
|
24
25
|
related:
|
|
25
26
|
- ./netsuite-salesorder-open-orders-sync.md
|
|
@@ -89,8 +90,16 @@ second, independently-gated concern in the same handler.
|
|
|
89
90
|
|
|
90
91
|
1. `findClickupTaskByOpportunityNumber($tranId)` — GET the list filtered by the `Opportunity #`
|
|
91
92
|
custom field (`include_closed=true&include_archived=true&custom_fields=[{field_id,operator:'=',value}]`).
|
|
92
|
-
2. **Match → `updateTask()`**:
|
|
93
|
-
|
|
93
|
+
2. **Match → `updateTask()`**: **diffs first, writes only what changed** (change-detection backstop,
|
|
94
|
+
since 2026-06-17). PUT name/description **only if** one differs; POST **only** the custom fields
|
|
95
|
+
that differ (each to `/task/{id}/field/{fieldId}` — ClickUp has no bulk custom-field set). When
|
|
96
|
+
nothing differs it writes nothing and returns `task unchanged`. This kills the no-op-write echo: a
|
|
97
|
+
blind rewrite fires a `taskUpdated` webhook on every sync. Comparison is **block-to-block** —
|
|
98
|
+
`buildDescription()` is regenerated from current NS data and compared to the stored block (no field
|
|
99
|
+
extraction needed), and `findClickupTaskByOpportunityNumber()` returns the **full task object** so
|
|
100
|
+
the diff has the existing name/description/custom-field values. Helpers: `clickupFieldMatches()`
|
|
101
|
+
(text = string compare; Presales Lead users field = add-only, matches when already assigned) and
|
|
102
|
+
`normalizeDescription()`.
|
|
94
103
|
3. **No match → `createTask()`**: POST to list `901111987449`, `custom_item_id` = `1009`.
|
|
95
104
|
|
|
96
105
|
Shared helpers: `buildCustomFields()` (the field array, used by both create and update),
|
|
@@ -118,7 +127,10 @@ Shared helpers: `buildCustomFields()` (the field array, used by both create and
|
|
|
118
127
|
JS `Date.toString()` style in **America/Los_Angeles**, e.g. `GMT-0800 (PST)`, auto PST/PDT by date);
|
|
119
128
|
Stage = `entityStatus.refName` (the percent string like "10%", NOT `probability`); Details = `memo`
|
|
120
129
|
(which carries NetSuite's "Missing Required Details…" fallback verbatim for pre-mandatory records).
|
|
121
|
-
The blank separator lines are a single space
|
|
130
|
+
The blank separator lines are emitted as a single space, but **ClickUp stores them as truly empty
|
|
131
|
+
lines** (our `\n \n` comes back `\n\n`). Change-detection's `normalizeDescription()` rtrims each line
|
|
132
|
+
so that whitespace delta doesn't read as a change (otherwise every sync would re-write the
|
|
133
|
+
description). Confirmed against task 868jh6x08 / opp 73142 via `test/@dave/probe_clickup_desc_match.php`.
|
|
122
134
|
- **Custom-field value mapping** (the non-obvious part — these were swapped before 2026-06-16):
|
|
123
135
|
`Opportunity #` (`a5529cdc-…`) ← `tranId`; `Customer #` (`170dc118-…`) ← `entity->refName`
|
|
124
136
|
(the customer **name**, deliberately — not the NetSuite customer number). Plus `Sales Rep`,
|
|
@@ -227,7 +239,37 @@ None — platform-wide Forecast sync.
|
|
|
227
239
|
window to the load→claim gap. `findSendableIds` must list every sendable status
|
|
228
240
|
(`Created`,`Pending`,`Retry`,`Sending`) — omitting `Created` silently matches nothing (`candidates:0`).
|
|
229
241
|
|
|
242
|
+
## CU→NS direction (future — not yet built): reverse mapping
|
|
243
|
+
|
|
244
|
+
The current sync is **NS→CU only**. When the ClickUp→NetSuite direction is built, it cannot reuse the
|
|
245
|
+
block-to-block compare, because the source of the edit is the ClickUp **composite** but the destination
|
|
246
|
+
is **discrete NetSuite fields**. It must **reverse-map** — extract the real NS values out of the wrapper
|
|
247
|
+
before comparing-to / writing-to NetSuite:
|
|
248
|
+
|
|
249
|
+
- **Title → NS `title`:** the task name is `{opp#} — {customer} — {title}`. Do **not** naively split on
|
|
250
|
+
` — ` (a title can contain a dash). Strip the **known** `{Opportunity#} — {Customer#} — ` prefix using
|
|
251
|
+
the values already on the `Opportunity #` / `Customer #` custom fields as anchors; the remainder is
|
|
252
|
+
the NS title.
|
|
253
|
+
- **Description → NS `memo`:** take only the lines between the `Details:` marker and the trailing
|
|
254
|
+
`NetSuite Internal ID:` line. Company / Amount / Expected Close / Stage are NS-derived display, not
|
|
255
|
+
ClickUp-authored — parse them out and ignore.
|
|
256
|
+
- **Custom fields** are already discrete — no extraction.
|
|
257
|
+
|
|
258
|
+
Pair this with: a **field-ownership gate** (only write fields ClickUp is authoritative for — Amount/
|
|
259
|
+
Stage/Expected Close are NS-owned, and HubSpot also writes these records, so don't push them back), the
|
|
260
|
+
same **skip-if-unchanged** compare on the extracted values, and **actor-identity suppression** (drop
|
|
261
|
+
`taskUpdated` events authored solely by the ClickUp bot/integration user) so our own NS→CU writes don't
|
|
262
|
+
trigger a CU→NS write. The NS→CU change-detection above is the complementary backstop, not a substitute.
|
|
263
|
+
|
|
230
264
|
## Change history
|
|
265
|
+
- 2026-06-17 — ClickUp **change-detection backstop** added to `updateTask()`: diff before write, skip
|
|
266
|
+
no-op syncs (`task unchanged`), PUT name/description only when changed, POST only changed custom
|
|
267
|
+
fields. `findClickupTaskByOpportunityNumber()` now returns the full task object; added
|
|
268
|
+
`clickupFieldMatches()` (add-only users field) + `normalizeDescription()` (per-line rtrim — ClickUp
|
|
269
|
+
trims our single-space separator lines to empty, which previously read as a permanent change).
|
|
270
|
+
Verified opp 73142 (changed in NS → correctly flagged) and same-data (→ `unchanged`). Also recorded
|
|
271
|
+
the future **CU→NS reverse-mapping** design (extract title/memo, field-ownership gate, actor-identity).
|
|
272
|
+
(dfranks)
|
|
231
273
|
- 2026-06-16 — ClickUp task description switched to a structured block (`buildDescription()` +
|
|
232
274
|
`formatAmount()`/`formatExpectedClose()`): Company/Opportunity/Amount/Expected Close/Stage/Details/
|
|
233
275
|
Internal ID. Field sources confirmed via `probe_opportunity_fields.php`; Expected Close rendered
|
package/package.json
CHANGED