toga-ai 1.0.411 → 1.0.413
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,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: session
|
|
3
|
+
slug: nycdoe-hold-sync
|
|
4
|
+
title: NYC DOE onsite-repair hold status sync to ServiceNow
|
|
5
|
+
author: sking
|
|
6
|
+
repos: [worker, library]
|
|
7
|
+
framework: "1.0"
|
|
8
|
+
client: nycdoe
|
|
9
|
+
status: active
|
|
10
|
+
created: 2026-07-21
|
|
11
|
+
updated: 2026-07-21
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Session: nycdoe-hold-sync
|
|
15
|
+
**Date:** 2026-07-21
|
|
16
|
+
**Project/Repo:** worker + library (1.0)
|
|
17
|
+
**Task:** Make the "on hold" statuses set on NYC DOE onsite-repair Incident (INC) tickets in TogaDesk sync to — and stay on — the ServiceNow (nycd3) client side, and stop the TogaDesk ticket from reverting on inbound sync. DEPLOYED AND CONFIRMED WORKING.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What WORKED
|
|
22
|
+
<!-- Include specific file paths and evidence -->
|
|
23
|
+
- **Root-cause discovery via `Logs.API` (legacy prod):** Outbound nycd3 PATCHes are logged in `Logs.API` (direction=OUT, endpoint LIKE '%nycd3/data/tasks/%'). This is the primary diagnostic surface for this integration. Repair orders live in `TOGaDeskSupport`, tickets in `Common.NYCDOETickets`.
|
|
24
|
+
- **Controlled test PATCH proved the authoritative field.** A one-off script PATCHing `{u_status_task:'On Hold'}` to INC2192842 returned `{"status":"success"}`, and a re-fetch showed `u_status_task='On Hold'` while native `state` stayed `In Progress`. The value **persisted 3+ days** (still On Hold on 2026-07-20). → `u_status_task` (custom field) is the client-facing/authoritative status, writable independently of native `state`.
|
|
25
|
+
- **worker fix (PRs #1676 merged, #1677 merged):** In `worker/crons/sync/nycdoe/send_ticket_updates.php` — set `u_status_task='On Hold'` on the hold push (writable from any active state); set native `state=3` only when incident is In Progress (SLA pause, avoids empty-body rejection); on release set `u_status_task` back to `In Progress`; reconcile incidents already Resolved/Closed/Canceled by advancing `dtSynced` (stops the retry storm). Added named constants SN_STATE_IN_PROGRESS/ON_HOLD, SN_HOLD_REASON_DEFAULT, SN_TASK_STATUS_ON_HOLD/IN_PROGRESS.
|
|
26
|
+
- **library fix (PR #845 merged):** `library/app/model/togadesk/repairorder.php` — `qqStatus()` now recognizes all six hold constants (was only HOLD_AWAITING_APPROVAL, HOLD_OTHER) in both hold IN-clauses, so a bare `'HOLD'` note written by inbound no longer collapses the derived status to ORDER_ASSIGNED_AWAITING_SCHEDULING.
|
|
27
|
+
- **End result:** Deployed; hold now syncs to ServiceNow client side and no longer reverts. User confirmed successful.
|
|
28
|
+
- **Knowledge captured:** `knowledge/clients/nycdoe/features/hold-status-sync.md` updated + pushed (commit a05e184) — corrected the prior doc's backwards claim that native `state` was authoritative.
|
|
29
|
+
|
|
30
|
+
## What did NOT work — DO NOT RETRY THESE
|
|
31
|
+
<!-- Exact failure reasons — do not vague-ify -->
|
|
32
|
+
- **Setting native `state=3` alone (the original production behavior).** ServiceNow accepted it (`state` became "On Hold") but the client-facing `u_status_task` stayed "Open", so the client saw no change. Native `state` is NOT what the client/workflow reads. INC2173220 and INC2192842 both showed `state:"On Hold"` + `u_status_task:"Open"` simultaneously.
|
|
33
|
+
- **First fix attempt (In-Progress gate + closed-ticket reconcile only, worker PR #1676).** Necessary but insufficient — it fixed the empty-body retry storm but did NOT make the hold visible (never touched `u_status_task`). INC2195124 still "reverted as always."
|
|
34
|
+
- **Pushing native `state=3` from a non–In-Progress incident.** The nycd3 `data/tasks` PATCH returns **HTTP 200 with an EMPTY body** (no `result` object) — ServiceNow only allows native On Hold FROM In Progress. The old code then threw on the empty body and never advanced `dtSynced`, re-sending forever (INC2190846, a Canceled incident, accumulated **2,392** failed PATCHes).
|
|
35
|
+
- **`hold_reason=10`.** Dropped/stored empty by ServiceNow (not a valid choice value). Native On Hold sticks without it, so it's harmless but meaningless. Not the cause of anything.
|
|
36
|
+
- **Assuming the outbound was the only problem.** The hold also reverted LOCALLY: `qqStatus()` derived status from the latest `repair_order_notes.newstatus`, and inbound `process_tickets.php:1127` writes ServiceNow "On Hold" as the bare `STATUS_HOLD` ('HOLD') — which `qqStatus()` didn't list as a hold, so status fell through to ORDER_ASSIGNED_AWAITING_SCHEDULING. Fixing outbound alone would not have held.
|
|
37
|
+
|
|
38
|
+
## Not tried yet (candidates for next session)
|
|
39
|
+
- Confirm whether NYC DOE's SLA/timers actually key off native `state` vs `u_status_task` — if only `u_status_task` matters, the native `state=3` push (and the In-Progress gate around it) could be dropped entirely to simplify.
|
|
40
|
+
- Convert remaining pre-existing magic numbers in `send_ticket_updates.php` (`state = 8` Canceled, `state = 6` Resolved) to named constants (flagged by review, left out of scope).
|
|
41
|
+
- Broaden the same `u_status_task`-authoritative handling to the RITM path (`send_request_item_updates.php`) if RITM holds show the same client-visibility gap (it already reads `u_status_task=='On Hold'` but sets native `state=8`).
|
|
42
|
+
|
|
43
|
+
## Current file state
|
|
44
|
+
| File | Status | Notes |
|
|
45
|
+
|------|--------|-------|
|
|
46
|
+
| worker/crons/sync/nycdoe/send_ticket_updates.php | Modified, merged (#1676 + #1677), deployed | u_status_task hold push + native-state gate + closed reconcile + constants |
|
|
47
|
+
| library/app/model/togadesk/repairorder.php | Modified, merged (#845), deployed | qqStatus() recognizes all 6 hold constants in both IN-clauses |
|
|
48
|
+
| worker/crons/sync/nycdoe/test_hold_ustatus.php | Deleted from worker repo | One-off Phase-A test; user kept a copy under test/@skyler/ |
|
|
49
|
+
| knowledge/clients/nycdoe/features/hold-status-sync.md (toga-tech) | Updated, pushed (a05e184) | Corrected authoritative-field model + gotchas |
|
|
50
|
+
|
|
51
|
+
## Decisions made
|
|
52
|
+
- **Write `u_status_task` as the hold signal (not just native `state`).** Rationale: proven via test PATCH to be the client-facing/authoritative field; inbound already keys off it. Rejected: native `state=3` only (invisible to client).
|
|
53
|
+
- **Keep native `state=3` but gate on In Progress.** Rationale: likely pauses the incident SLA; only legal from In Progress so gating avoids the empty-body rejection. Rejected: dropping native state entirely (deferred pending SLA confirmation); pushing state=3 unconditionally (causes retry storm).
|
|
54
|
+
- **Fix hold recognition in shared `qqStatus()` (library) rather than in process_tickets.** Rationale: bare `STATUS_HOLD` is a valid constant that qqStatus should recognize; additive change (classifies more notes as hold, never fewer), lowest-surprise root fix. Rejected: making process_tickets write HOLD_OTHER instead of bare HOLD (narrower, leaves qqStatus wrong for other callers).
|
|
55
|
+
- **Reconcile closed-on-SN incidents by advancing dtSynced.** Rationale: stop the infinite empty-body retry; let inbound move the order to Complete/Exception.
|
|
56
|
+
|
|
57
|
+
## Blockers
|
|
58
|
+
none — deployed and confirmed working.
|
|
59
|
+
|
|
60
|
+
## Exact next step
|
|
61
|
+
> None required — fix is complete, merged (worker #1676/#1677, library #845), deployed, and confirmed successful by the user. If revisiting: confirm with NYC DOE whether SLA keys off native `state` or `u_status_task`; if only `u_status_task`, simplify by removing the native `state=3` push and its In-Progress gate in `send_ticket_updates.php`.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
_Saved by /session-save on 2026-07-21_
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: session
|
|
3
|
+
slug: surface-decision-modal
|
|
4
|
+
title: Surface-driven Sales Order decision modal (approve + deny)
|
|
5
|
+
author: apeterson
|
|
6
|
+
repos: [toga25-supply, dbchanges2]
|
|
7
|
+
framework: "2.0"
|
|
8
|
+
client: shared
|
|
9
|
+
status: active
|
|
10
|
+
created: 2026-07-21
|
|
11
|
+
updated: 2026-07-21
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Session: surface-decision-modal
|
|
15
|
+
**Date:** 2026-07-21
|
|
16
|
+
**Project/Repo:** toga25-supply + dbchanges2 (2.0)
|
|
17
|
+
**Task:** Migrate the per-client Sales Order approve/deny modal config (`approvalActionFields.json`) from static per-client JSON to metadata-driven Surfaces + SurfaceElements resolved per-role server-side — Core neutral default first, client SurfaceOverrides after.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What WORKED
|
|
22
|
+
<!-- Include specific file paths and evidence -->
|
|
23
|
+
- **Core seed 2026-07-21a** (`dbchanges2/Core/2026-07-21a - SalesOrderDecisionSummarySurfaceSeed.sql`) — creates surface `sales-order-decision-summary` (SECTION, config cardType 'decisionSummary'), SHARED across approve+deny; 8 read-only FIELD elements (recordFieldId NULL + config.valueKey), Total is CURRENCY, all isVisible=1; messages keyed `salesOrder.decision.field.*`. Confirmed run in local Core; meta-group endpoint returns the surface + 8 elements + messages.
|
|
24
|
+
- **Core seed 2026-07-21b** (`dbchanges2/Core/2026-07-21b - SalesOrderDecisionActionSurfaceSeed.sql`) — creates `sales-order-approve-action` + `sales-order-deny-action` (config cardType 'decisionAction', action approve/deny), 7 role-tagged elements each (config.role: title/guardrail/reasonInput/confirmButton/cancelButton/successToast/errorToast). Approve reason OPTIONAL (charLimit 255, check/mint-700); deny reason REQUIRED (showRequiredIndicator, xmark). Confirmed run + reload → both surfaces now resolve populated (verified via meta-group JSON).
|
|
25
|
+
- Both seeds ADDITIVE / id-agnostic / re-runnable (NOT EXISTS-guarded per surface slug + config.role/valueKey; messages guarded by messageKey; surfaces inherit appId/recordId from `order-details` sibling). Re-running 21a inserted nothing (verified — no dupes).
|
|
26
|
+
- **FE adapter** (`toga25-supply/.../SalesOrderApprovalModalsLayout/helpers/surfaceBundlesToDecisionFields.ts`) — maps 3 resolved bundles → `{ orderContentDetails, approve, deny }`; exports DECISION_SURFACE_SLUGS, surfaceBundlesToDecisionFields(), isDecisionSurfaceEnabled(), overlayDecisionSurfaceFields(). decisionSurfaceFields output verified correct against the JSON shape (user pasted matching output).
|
|
27
|
+
- **FE view-model wiring** (`.../viewModel/useApprovalModalViewModel.tsx`) — fetches via useFetchSurfaceMetaGroup(DECISION_SURFACE_SLUGS); builds `resolvedTenantFields` overlaying the 3 Surface keys onto patched JSON; returned as `tenantFields`. `SalesOrderApprovalModalsLayout.tsx` needs NO change (still reads `tenantFields.recordActionFields`).
|
|
28
|
+
- Full `npx tsc --noEmit` clean; `npx eslint` clean after the any→unknown fix.
|
|
29
|
+
- KB captured/pushed: `surface-frontend.md` + `surface-layer-schema.md` updated on _main.
|
|
30
|
+
|
|
31
|
+
## What did NOT work — DO NOT RETRY THESE
|
|
32
|
+
<!-- Exact failure reasons — do not vague-ify -->
|
|
33
|
+
- **Assuming a full JSON→Surface replacement.** The 3 surfaces cover only 3 of the 7 `recordActionFields` keys. `guardrailCopy`, `actionOptions` (approve step-two enable rule), `approvalWorkflow`, `orderContentDetailsWorkflow` are NOT modeled in surfaces; and JSON approve/deny carry functional attrs the surface never emits (`characterLimit: 255`, `inputContainerWidth`, submit valueKeys `approveOrder`/`confirmApproveOrder`/`cancel`). A straight replace would drop these → had to overlay instead.
|
|
34
|
+
- **Naive object spread for the overlay.** The adapter emits keys with value `undefined` (e.g. approve reasonTextBox.characterLimit), so `{...json, ...surface}` would overwrite JSON's 255 with undefined. Fixed with `overlayDefined()` (copies only defined values).
|
|
35
|
+
- **`any` in overlay helpers.** ESLint `@typescript-eslint/no-explicit-any` failed (6 errors) — `tsc` was clean but the repo's ESLint blocks `any`. Replaced with `Record<string, unknown>` + explicit casts at the JSON boundary.
|
|
36
|
+
- **Expecting DB seed changes to show without a reload.** useFetchSurfaceMetaGroup is session-cached (staleTime/gcTime Infinity). 21b appeared to "not work" until a hard reload / re-login. Not a bug.
|
|
37
|
+
|
|
38
|
+
## Not tried yet (candidates for next session)
|
|
39
|
+
- **Client SurfaceOverride seeds** (the whole point of the migration): Compass + Compass Canada — CONFIG override on Total element to add `isConcatenated` (`_totalLease` + "/mo", stacked). Quad — IS_VISIBLE=0 on summary fields 1 (Assigned Manager), 7 (Cost Center), 8 (Reason for Request). Register in dbchanges2 README run-order.
|
|
40
|
+
- **Move `characterLimit` / `inputType` into surface element `config` JSON** in 21b so the adapter reads them from surface (currently read from JSON via overlay). Prerequisite to deleting the JSON.
|
|
41
|
+
- **`orderContentDetailsWorkflow`** — user set it to reuse `decision.orderContentDetails`; note the workflow view historically used labels WITHOUT trailing colon and dropped "Assigned Manager". Decide whether a separate `sales-order-decision-summary-workflow` surface is needed.
|
|
42
|
+
- **Remove the two debug `console.log`s** in useApprovalModalViewModel.tsx before commit (house rule: no dead code).
|
|
43
|
+
- **Delete the per-client `approvalActionFields.json`** once all functional attrs are surface-modeled and clients verified (kept as reference for now — user explicitly said not to delete).
|
|
44
|
+
|
|
45
|
+
## Current file state
|
|
46
|
+
| File | Status | Notes |
|
|
47
|
+
|------|--------|-------|
|
|
48
|
+
| dbchanges2/Core/2026-07-21a - SalesOrderDecisionSummarySurfaceSeed.sql | created, RUN in local Core | shared decision summary surface + 8 elements |
|
|
49
|
+
| dbchanges2/Core/2026-07-21b - SalesOrderDecisionActionSurfaceSeed.sql | created, RUN in local Core | approve + deny action-chrome surfaces (7 elements each) |
|
|
50
|
+
| dbchanges2/Core/2026-07-17 - README - RUN ORDER.md | edited | 21a/21b registered under "TWO - ADDITIONAL CORE RESETS"; Quad ENUM-widener ordered before 21a; Compass/CC 21a/21b override entries |
|
|
51
|
+
| toga25-supply/.../helpers/surfaceBundlesToDecisionFields.ts | created | adapter + overlayDecisionSurfaceFields(); any→unknown; tsc+eslint clean |
|
|
52
|
+
| toga25-supply/.../viewModel/useApprovalModalViewModel.tsx | edited (additive) | fetch + resolvedTenantFields overlay; 2 debug console.logs still present |
|
|
53
|
+
| toga25-supply/.../SalesOrderApprovalModalsLayout.tsx | unchanged | still reads tenantFields.recordActionFields — no change needed |
|
|
54
|
+
| toga25-supply/.../viewModel/FIELDS/*/approvalActionFields.json | unchanged | kept as reference; NOT to be deleted yet |
|
|
55
|
+
|
|
56
|
+
## Decisions made
|
|
57
|
+
- **Overlay (merge), not full replacement.** Surface owns display attrs of the 3 migrated keys; JSON fills unmigrated keys + functional attrs. Rejected: full replace (would drop characterLimit/guardrailCopy/actionOptions/workflow). Gated by `isDecisionSurfaceEnabled` → unseeded clients fall back to pure JSON.
|
|
58
|
+
- **Shared summary surface + two per-action chrome surfaces.** The 8 summary rows are identical across approve/deny → one `sales-order-decision-summary`; the chrome diverges (title text, reason required-ness, icons, toasts) → two action surfaces. Renamed the summary from an approval-only slug to `sales-order-decision-summary`.
|
|
59
|
+
- **Core neutral default = all summary fields visible, plain Total (no concat), action buttons carry standard English labels.** Per-client differences (concat, field hides, wording) belong in SurfaceOverrides, not Core. Matches house rule: display fields default visible; only action buttons default OFF.
|
|
60
|
+
- **Elements found by `config.role`, not id.** No ADMIN/MANAGER branching in the FE — surfaces resolve per-authenticated-role server-side.
|
|
61
|
+
|
|
62
|
+
## Blockers
|
|
63
|
+
none
|
|
64
|
+
|
|
65
|
+
## Exact next step
|
|
66
|
+
> Author the client SurfaceOverride seeds in dbchanges2: Compass + Compass Canada CONFIG override on the `sales-order-decision-summary` Total element (add isConcatenated `_totalLease` "+ "/"/mo" stacked), and Quad IS_VISIBLE=0 overrides on summary fields 1/7/8 (Assigned Manager / Cost Center / Reason for Request). Follow the additive NOT EXISTS-guarded override pattern and register them in `Core/2026-07-17 - README - RUN ORDER.md`.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
_Saved by /session-save on 2026-07-21_
|
package/package.json
CHANGED