toga-ai 1.0.840 → 1.0.841
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/workflows/authoring-and-shipping-sql-files.md +2 -1
- package/knowledge/1.0/apps/togadesk/features/field-service-dispatch.md +5 -1
- package/knowledge/clients/nycdoe/INDEX.md +1 -1
- package/knowledge/clients/nycdoe/features/hold-status-sync.md +42 -7
- package/knowledge/clients/nycdoe/features/servicenow-integration.md +4 -1
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ project: Database Changes
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-18
|
|
10
10
|
owners: [sking]
|
|
11
11
|
files:
|
|
12
12
|
- dbchanges/index.php
|
|
@@ -108,6 +108,7 @@ The repo ships `config.alpha/beta/demo/hotfix/prod/stage/test/worker.ini`, there
|
|
|
108
108
|
- **A comment-only chunk stops the whole queue** (error 1065 + `exit()` on the prod path). Your malformed file blocks unrelated teammates' migrations — a team-wide failure, not a local one.
|
|
109
109
|
- **No `DELIMITER`, no stored routines, no explicit `COMMIT`.** See the guard-pattern section.
|
|
110
110
|
- **Error 1093** is why the preflight subquery must be wrapped in a derived table.
|
|
111
|
+
- **⚠ The repo does NOT describe the whole live schema — never conclude "that column/index does not exist" from `dbchanges` alone.** `Core.AdvanceShippingNoticeQueue.dedupeKey` and `.alertSentAt` exist in production (`dedupeKey` confirmed UNIQUE) but **no migration for them exists anywhere in this repo** — they were created from a `library` commit (TRUE-77676). Found 2026-09-16 because a reviewer read the repo and reached the wrong conclusion. Verify columns and indexes against the **database**.
|
|
111
112
|
- **A merged PR is not an applied change.** Confirmed live 2026-08-13 (PR dbchanges#265): after the merge into `_production`, both target tables were still 100% unchanged. Never tell a customer or a ticket "applied" off a merge — always re-read the data.
|
|
112
113
|
- **A cross-database change half-applies without warning.** Same incident: the `TOGaDeskSupport` file landed and the `Core` file did not, leaving `assets.tag` 87/87 correct while `Core.AdvanceShippingNoticeUnits.assetTag` was 87/87 still empty — a state that *looks* fixed in the UI. Cause is the one-credential-set / folder-is-a-database model above. Verify every database in the change, per-pair.
|
|
113
114
|
- **Retry semantics are a feature**: because the data change and the `_dbchanges` row share a transaction, a failing file re-runs next cycle. Do not "help" it with a manual COMMIT.
|
|
@@ -6,7 +6,7 @@ project: TOGa Desk
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-18
|
|
10
10
|
owners: ["jcardinal", "mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- desk/includes/controllers/actions/central/
|
|
@@ -62,6 +62,10 @@ The "PREPARE SHIPMENT" button opens modal `desk/template/modals/central/addTrack
|
|
|
62
62
|
Dispatch flows branch on hardcoded client ids (e.g. Walmart / Walmart Plus, WJE, Office Depot / Compass, AIG, RUMCSI) for department mapping, scheduling rules, and notification copy — always confirm the client arm before changing shared central logic.
|
|
63
63
|
|
|
64
64
|
## Gotchas
|
|
65
|
+
- **⚠ Two undocumented, UNFIXED date skews on `repair_orders` (`TOGaDeskSupport`) — do not build queries on these columns.** Found 2026-09-16 (TRUE-82027); the writing code was not traced, blast radius untraced (other crons, reports, the desk UI all read `dtUpdated`). Needs its own ticket to fix at source in togadesk.
|
|
66
|
+
- **`repair_orders.dtUpdated` runs exactly 240 minutes (4 h) behind** the clock used by `repair_order_notes.notesdate` and `Common.NYCDOETickets.dtSynced`. Verified across 61 orders, exactly −240 every time — likely a timezone bug in the togadesk web app.
|
|
67
|
+
- **`repair_orders.lastupdateddate` is 720 minutes (12 h) off on SOME rows** and correct on others — an AM/PM format bug (`h` vs `H`). Example: INC2087375 `dtUpdated` 2026-09-15 18:01:34 vs `lastupdateddate` 2026-09-15 06:01:34.
|
|
68
|
+
- **Do NOT "fix" a consumer with a `+240 MINUTE` offset.** 240 is almost certainly UTC-vs-Eastern and becomes 300 at the DST change on 2026-11-01, so a hardcoded offset silently breaks twice a year. The DOE outbound cron sidestepped it by removing `dtUpdated` from its selection query entirely.
|
|
65
69
|
- Actions are split across two locations: the inline `actions.php` switch **and** `controllers/actions/central/*.php` (the default fallback). Check both when tracing an action.
|
|
66
70
|
- Status recomputation is centralized in `updateStatus()` — write paths that bypass it can leave an order in an inconsistent state.
|
|
67
71
|
- **`updateStatus()` / `qqStatus()` silently overwrite status via a raw `UPDATE`** (no `repair_order_history` / `repair_order_notes` row). `qqStatus()` (`library/app/model/togadesk/repairorder.php` ~L356) is a computed SQL `CASE` that recomputes `repair_orders.status` from technician / dispatch / ETA / parts data on every `updateStatus()` call (fired by `scheduleDispatch`, `technicianOnsite`, `technicianEnRoute`, `awaitingDispatch`, `reworkAwaitingScheduling`, and part flows). If a status is not explicitly gated inside `qqStatus()`, it will be recomputed away with no audit trail — this is **shared, client-agnostic** logic (no tenant conditional), so a change hits every repair-order client.
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
| Doc | Framework | Summary |
|
|
4
4
|
|-----|-----------|---------|
|
|
5
5
|
| [NYCDOE EDI 850 Receive & 997 Acknowledgement](features/edi-850-receive-and-997-ack.md) | 1.0 | DOE's EDI 850-in → NetSuite SO + inline 997-out ack; open for the 997 build, its "always accept" stub, or a DOE ack complaint. |
|
|
6
|
-
| [NYCDOE Ticket Hold-Status Sync (ServiceNow ⇄ TOGaDesk)](features/hold-status-sync.md) | 1.0 | Bidirectional DOE ticket hold-status sync (ServiceNow ⇄ TOGaDesk)
|
|
6
|
+
| [NYCDOE Ticket Hold-Status Sync (ServiceNow ⇄ TOGaDesk)](features/hold-status-sync.md) | 1.0 | Bidirectional DOE ticket hold-status sync (ServiceNow ⇄ TOGaDesk), the four defects that let holds silently revert or never re-send, and the outbound cron's sel |
|
|
7
7
|
| [NYCDOE ServiceNow / ASN Integration](features/servicenow-integration.md) | 1.0 | Full DOE ServiceNow/ASN integration map — ticket mirror, ASN → NetSuite SO/TOGa Desk pipeline, outbound status/POD, EDI; open for any DOE cron/pipeline work or |
|
|
8
8
|
| [New York City Department of Education](profile.md) | 1.0 | NYC DOE client profile (1.0 worker tier, ServiceNow + EDI); open for DOE's platform scope, endpoints, API clients, and where its ticket/ASN/EDI logic lives. |
|
|
@@ -6,13 +6,15 @@ project: Worker
|
|
|
6
6
|
client: nycdoe
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-18
|
|
10
10
|
owners: [mhammontree, sking]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/sync/nycdoe/send_ticket_updates.php
|
|
13
13
|
- worker/crons/sync/nycdoe/process_tickets.php
|
|
14
14
|
- worker/crons/sync/nycdoe/send_request_item_updates.php
|
|
15
15
|
- library/app/model/togadesk/repairorder.php
|
|
16
|
+
- library/app/model/common/nycdoeticket.php
|
|
17
|
+
- dbchanges/Common/MH/2026-09-16.sql
|
|
16
18
|
- library/app/api/nycdoev2.php
|
|
17
19
|
- togadesk/desk/includes/classes/class.repair.php
|
|
18
20
|
related:
|
|
@@ -21,20 +23,21 @@ related:
|
|
|
21
23
|
- ../../../1.0/apps/worker/architecture.md
|
|
22
24
|
---
|
|
23
25
|
|
|
24
|
-
Bidirectional DOE ticket hold-status sync (ServiceNow ⇄ TOGaDesk)
|
|
26
|
+
Bidirectional DOE ticket hold-status sync (ServiceNow ⇄ TOGaDesk), the four defects that let holds silently revert or never re-send, and the outbound cron's selection/throttle model; open before changing DOE hold logic, the selection query, or "which SNOW field is authoritative."
|
|
25
27
|
|
|
26
28
|
## Summary
|
|
27
29
|
|
|
28
|
-
DOE ticket **hold** status must round-trip between ServiceNow (SNOW) and TOGaDesk and **stay held** — holds are SLA-bearing in both systems. Covers the bidirectional hold-status sync for DOE Incidents (INC) and Request Items (RITM), the authoritative business rule, and the
|
|
30
|
+
DOE ticket **hold** status must round-trip between ServiceNow (SNOW) and TOGaDesk and **stay held** — holds are SLA-bearing in both systems. Covers the bidirectional hold-status sync for DOE Incidents (INC) and Request Items (RITM), the authoritative business rule, and the four independent defects that let holds silently revert or never reach ServiceNow. Status-sync companion to the broader [ServiceNow / ASN integration](servicenow-integration.md); the underlying crons and `App_Api_NYCDOEV2` plumbing are documented there.
|
|
29
31
|
|
|
30
32
|
> **DOE "tickets" are `repair_orders` rows** (`App_Model_TogaDesk_RepairOrder`, `db_togadesk` / legacy `TOGaDeskSupport`, **clientid = 16**) — NOT the generic `tickets` table / `class.ticket.php`. All four DOE crons filter to client 16 / `NYCDOETickets`, so hold behavior here is **NYCDOE-only**; no other client is affected.
|
|
31
33
|
|
|
32
34
|
## How it works
|
|
33
35
|
|
|
34
|
-
###
|
|
35
|
-
A held DOE order could fail to persist/propagate via *any* of
|
|
36
|
+
### Four distinct hold-failure paths — different repos, mechanisms, symptoms
|
|
37
|
+
A held DOE order could fail to persist/propagate via *any* of four unrelated paths. Paths 1–3 are about the hold being **LOST**; path 4 is about a correctly-held order never being **RE-SENT** (see *Coordinated three-repo fix & deploy ordering*):
|
|
36
38
|
- **TRUE-79922 (worker crons):** the SNOW-side `state` 3⇄2 oscillation — an outbound self-clobber pushing `state:2` over a held `state:3`, plus an inbound wrong-field read — reverted the order to **"In Progress"**. Fixed (still pending deploy at time of writing).
|
|
37
39
|
- **TRUE-80060 (`library` shared model):** the local `qqStatus()` recompute silently demoting the order to a scheduling status (**"Orders Assigned / Awaiting Scheduling"**, `ORDER_ASSIGNED_AWAITING_SCHEDULING`) whenever `updateStatus()` ran. Fixed. In a **shared, client-agnostic** model method — surfaced by DOE but affecting every TogaDesk repair-order client; durable mechanism in [Field-Service Dispatch](../../../1.0/apps/togadesk/features/field-service-dispatch.md).
|
|
40
|
+
- **TRUE-82027 (worker cron, the outbound push never re-ran):** the whole outbound push body sat inside `while ($rowNote = ...)` over new `repair_order_notes`, so **nothing was PATCHed unless a NEW note existed**. A held order gets no new notes — it is already held — so its hold+ETA were pushed once when the hold note was fresh and never again, and an **ETA change alone NEVER reached ServiceNow on any DOE incident**. This directly contradicted rule 2 below ("re-asserts On Hold every run"): it did not. Evidence: **77 of 78** held DOE orders stuck, zero hold pushes in 3 days; INC2196570's last hold push was 2026-08-25, found 2026-09-16; INC2087375's ETA changed 2026-09-15 18:01, the cron selected it at 18:21, made one GET and PATCHed nothing (local `dtEta` 2026-09-17 08:00 vs SNOW `u_eta` 2026-09-07 08:00 — ten days stale). **Fix:** the push body now runs **once per repair order per pass**, driven by the order's **current** `$repairOrder->status`, not by `$rowNote['newstatus']`. *(failure path #4)*
|
|
38
41
|
- **TRUE-80060 (`togadesk` desk app):** a hold set **via a note/comment** (`Repair::addNotes`) never reached ServiceNow. `addNotes` recorded the new status only on `repair_order_notes.newstatus` — never writing the `repair_orders.status` cache nor calling `updateStatus()` — and the TRUE-75199 real-time-sync optimization suppressed `repair_orders.dtUpdated`. The worker reads the **cached** `repair_orders.status` and selects orders by `dtUpdated >= dtSynced`, so an on-hold-with-comment order left a stale non-hold cache **and** an un-bumped `dtUpdated` — the worker never selected it and never pushed `state:3`. Fixed.
|
|
39
42
|
|
|
40
43
|
### The authoritative business rule (SME-confirmed — contact-center)
|
|
@@ -66,12 +69,40 @@ When a DOE repair order is on hold, `send_ticket_updates.php` pushes **the full
|
|
|
66
69
|
- **`hold_reason` stays pinned to `SN_HOLD_REASON_DEFAULT` (10, "Other").** See the *decision* on not mapping hold reasons.
|
|
67
70
|
- **No `u_status_task` write** on either the hold push or the hold release — ServiceNow's business rule derives it from `state`. The `SN_TASK_STATUS_ON_HOLD` / `SN_TASK_STATUS_IN_PROGRESS` constants and the `$snTaskStatus` read were removed with it.
|
|
68
71
|
- **Idempotency + release both key off `state`, not `u_status_task`:** the "already held" guard is `$snState != 'On Hold'` and the release trigger is `$snState == 'On Hold'`. **Rule: gate a push on the field you actually write** — the old `$snTaskStatus` keys gated the push on a field we no longer drive.
|
|
69
|
-
-
|
|
72
|
+
- **⚠ Closed-incident reconcile — the NO-PATCH is DELIBERATE, do not "fix" it.** if the incident is already `Resolved`/`Closed`/`Canceled` on the SNOW side it can no longer be held — the cron **advances `dtSynced`** to accept the remote closure instead of retrying forever. (A canceled incident, INC2190846, had generated **2,392** failed empty-body PATCHes before this.) **Two reviewers have now independently filed this branch as a missing-PATCH bug — a `php-reviewer` agent and CodeRabbit on GitHub. Both were rejected.** PATCHing a closed incident returns HTTP 200 with an empty body forever; adding a PATCH here recreates the retry storm.
|
|
70
73
|
- The "Assigned → In Progress" auto-start block is still guarded so it never pushes `state:2` over a held order (TRUE-79922). Named constants: `SN_STATE_IN_PROGRESS` / `SN_STATE_ON_HOLD` / `SN_HOLD_REASON_DEFAULT` in `send_ticket_updates.php`.
|
|
71
74
|
- The existing **empty-body patch-response check** (~L493) still **throws**, so any remaining rejection surfaces loudly. Keep it.
|
|
72
75
|
|
|
73
76
|
> **DECISION (TRUE-81044) — do NOT map TogaDesk hold reasons onto ServiceNow hold-reason codes.** `hold_reason` stays pinned to `10` ("Other") and the human-readable reason is carried entirely in `u_other_reason_on_hold` / `work_notes`, sourced from TogaDesk. Rationale: ServiceNow's own states are not reliable, so mapping our six `HOLD_*` constants onto their code set buys nothing and adds a second source of truth. **Do not re-propose a reason-code mapping.**
|
|
74
77
|
|
|
78
|
+
### Outbound selection, watermark & throttle (TRUE-82027 current shape)
|
|
79
|
+
`send_ticket_updates.php` picks its work from `Common.NYCDOETickets`. Two columns, two distinct meanings — **do not merge them again**:
|
|
80
|
+
- **`dtSynced` = notes high-water mark ONLY** — "which `repair_order_notes` have I forwarded" (`notesdate > dtSynced`).
|
|
81
|
+
- **`dtLastChecked` (new: nullable datetime + index, `dbchanges/Common/MH/2026-09-16.sql`) = "when did I last look at this ticket" ONLY** — drives the throttle and the queue order.
|
|
82
|
+
|
|
83
|
+
Before TRUE-82027 `dtSynced` carried **both** meanings: a standalone assignment-group block (added by TRUE-78295, 2026-04-14) stamped `dtSynced = App_Model::DATETIME_NOW`. Writing meaning 2 into a field read as meaning 1 poisoned both — it pushed `dtSynced` past `repair_orders.dtUpdated`, so the old `dtUpdated >= dtSynced` selection gate could never match again and the ticket locked itself out permanently.
|
|
84
|
+
|
|
85
|
+
Current selection query:
|
|
86
|
+
- **`repair_orders.dtUpdated` is no longer referenced at all** — removed, not compensated (see the TOGaDeskSupport date-skew gotcha; compensating with a fixed offset is a trap).
|
|
87
|
+
- **State-based eligibility:** an **OPEN** order is always eligible; a **COMPLETE** order is eligible only while it has unforwarded notes, then drops out for good.
|
|
88
|
+
- **Throttle:** `dtLastChecked <= NOW() - INTERVAL 6 MINUTE`.
|
|
89
|
+
- **`ORDER BY dtLastChecked ASC`** replaced `ORDER BY RAND()`, which gave no coverage guarantee and could starve an order forever.
|
|
90
|
+
- The standalone assignment-group block was **deleted** — the main payload already diffs `assignment_group`, so it was a duplicate GET, a second code path, and the source of the lockout stamp.
|
|
91
|
+
- Debug pin already in the file: the commented `#AND NYCDOETickets.ticketNumber = '...'` line scopes a pass to one ticket.
|
|
92
|
+
|
|
93
|
+
### Failure alerting instead of a throw (TRUE-82027)
|
|
94
|
+
**DECISION (Mark, 2026-09-17): no circuit breaker** — 1.0 is transitioning to 2.0 under a separate task, so the cheap path was chosen.
|
|
95
|
+
- A failed ServiceNow PATCH **no longer throws**; it records the incident and the run continues, then all failures are emailed **once per run** to sking@togatech.com / mhammontree@togatech.com.
|
|
96
|
+
- **Throttled to one email per 60 minutes** via a marker row in `Core.AdvanceShippingNoticeQueue` keyed `dedupeKey = 'system|ticket_sync_patch_failure_alert'` — the same mechanism as `system|po_reconciliation_alert` in `2_send_serials_to_netsuite.php`. Worst case drops from ~240 emails/day to 24.
|
|
97
|
+
- **⚠ Open the throttle window ONLY AFTER a successful `send()`.** `App_Email::send()` throws; stamping `alertSentAt` *before* sending would let a failed send silently suppress the alert for a full hour — and since the PATCH no longer throws, that alert is now the **only** signal. Hence two functions: `doeShouldSendThrottledAlert()` reads the window, `doeMarkAlertSent()` opens it.
|
|
98
|
+
- **The invalid-assignment-group email had never sent, ever** — the block built the email and never called `$email->send()` (every sibling DOE cron does, e.g. `2_send_serials_to_netsuite.php:346`). Added, behind the same throttle on its own key `system|ticket_sync_invalid_group_alert`.
|
|
99
|
+
|
|
100
|
+
### Verifying a TRUE-82027-class change (no sandbox — verified in production)
|
|
101
|
+
There is **no sandbox for the SNOW integration**; verification happens in prod. Deploy order is **dbchanges → library → worker**, and dbchanges `_production` does **not** auto-apply — the migration lands on the next worker EB deploy. After deploy, check legacy `Logs.API` filtered `endpoint LIKE '%nycd3%'` for:
|
|
102
|
+
1. A held order PATCHing `state:3` with **no new note**.
|
|
103
|
+
2. INC2087375's `u_eta` moving from 2026-09-07 to 2026-09-17.
|
|
104
|
+
3. `SELECT COUNT(*) FROM Common.NYCDOETickets WHERE dtLastChecked IS NOT NULL` climbing.
|
|
105
|
+
|
|
75
106
|
### RITM cron — deliberately unchanged by TRUE-81044
|
|
76
107
|
`send_request_item_updates.php` already pushes native `state:"8"` (On Hold) and **never wrote** `u_status_task`, so TRUE-81044's ask was already satisfied there. It carries a defensive `HOLD_*` guard on the scheduled-update path so it can't push `state:"-14"` while held; its reverse-hold logic still **reads** `u_status_task == 'On Hold'`.
|
|
77
108
|
> **OPEN QUESTION:** ServiceNow's communication was scoped to **Incident** state. We did **not** extend `hold_reason` / `u_eta` / `u_other_reason_on_hold` to the RITM payload without confirmation. Whether the RITM On Hold transition carries the same mandatory-set requirement is unconfirmed — ask before changing the RITM payload.
|
|
@@ -107,6 +138,8 @@ The SNOW round-trip note reconciliation (delete-and-reinsert in `process_tickets
|
|
|
107
138
|
### Reference facts (DOE sync debugging)
|
|
108
139
|
- **SNOW request/response logs:** logged to the legacy `Logs` schema, **`API` table** (`App_Model_Logs_API`, `db_logs`, `_databaseNameOverride 'Logs'`). `requestPayload` / `responsePayload` are queryable CHAR columns — the primary debugging avenue. Filter `endpoint LIKE '%nycd3%'`; the custom DOE endpoint base is **`/api/nycd3/`**. The state:3↔state:2 oscillation in TRUE-79922 was proven from this table (INC2074899, INC2185183 showed `state:3` then `state:2` pushed seconds apart in one run).
|
|
109
140
|
- **`NYCDOETickets` (`App_Model_Common_NYCDOETicket`, `db_common` / legacy `Common`)** stores the inbound SNOW record in `rawData`, a **`FIELDTYPE_BLOBSTORAGE`** field (S3 bucket `asifiles`, key `prod/Common/NYCDOETickets/rawData/{id}`) — **NOT a queryable DB column.**
|
|
141
|
+
- **⚠ `repair_orders.dtUpdated` is skewed — never build a selection query on it.** It is written exactly **240 minutes behind** the clock used by `repair_order_notes.notesdate` and `NYCDOETickets.dtSynced`. **Do NOT compensate with `+240 MINUTE`** — 240 is almost certainly UTC-vs-Eastern and becomes 300 at the DST change (2026-11-01), so a hardcoded offset breaks twice a year. TRUE-82027 sidestepped it by removing `dtUpdated` from the query. Mechanism + the second (720-minute) skew: [Field-Service Dispatch](../../../1.0/apps/togadesk/features/field-service-dispatch.md).
|
|
142
|
+
- **⚠ `Logs.API` history is capped at 30 days from 2026-09-17** (Jeff, sprint review) and existing logs are being purged. This investigation depended on history far older than that (9,914 PATCHes back to 2026-08-20; a hold last pushed 22 days earlier; the 2,392-call storm). **Slow-burn bugs like this can no longer be traced backwards — they must be caught by alerting.** NYCDOE stays 1.0 so its logs stay in legacy `Logs.API`; Compass/Elite/WJE move their API logging to 2.0. Agreed: hold the purge until TRUE-82027 is verified in production.
|
|
110
143
|
- **Worker prod code cannot run on a Windows dev box.** The DB/S3 data layer fetches EC2 instance-metadata (IMDSv2) credentials and fails off-EC2. Investigate with the toga DB MCP and the `Logs.API` table instead of running the crons locally.
|
|
111
144
|
|
|
112
145
|
## Gotchas
|
|
@@ -118,9 +151,11 @@ The SNOW round-trip note reconciliation (delete-and-reinsert in `process_tickets
|
|
|
118
151
|
- `worker/crons/sync/nycdoe/process_tickets.php:206` — inbound hold detection
|
|
119
152
|
- `worker/crons/sync/nycdoe/send_request_item_updates.php:224` — RITM reverse-hold
|
|
120
153
|
- `worker/crons/sync/nycdoe/send_request_item_updates.php:139` — RITM closed-state check
|
|
121
|
-
**
|
|
154
|
+
**CONFIRMED BROKEN in production (TRUE-82027, 2026-09-15).** The verification is done: ServiceNow's business rule is **NOT** deriving `u_status_task` from `state`. Live GET on INC2087375 (legacy `Logs.API` id 170862727, 2026-09-15 18:21:34) returned `"state":"On Hold"` with `"u_status_task":"Open"`. All three reads above are therefore reading the wrong value today. Not a "verify this" item any more — it needs a fix (drive those reads off `state`) or a vendor fix to the business rule.
|
|
155
|
+
- **A merged PR is not a deployed change — TRUE-81044 is merged to `_production` but NOT deployed** (merge commit worker `2f4dcfe9e`). Production payloads on 2026-09-15 still write `u_status_task` and still never send `u_other_reason_on_hold` (**0 occurrences across 9,914 PATCHes since 2026-08-20**). Read the live payload in `Logs.API` before concluding a fix is live.
|
|
122
156
|
- Holds are **never auto-released** — if a SNOW user manually changes status, the outbound cron correctly re-asserts On Hold; that is intended, not a bug. Clear the hold in TOGaDesk.
|
|
123
157
|
- **Diagnosing which writer dropped a hold:** `repair_order_history` (`repairid, userid, dtStamp, note`) records *user-driven* status changes. A hold reverting with **no** history row between the hold and the revert = a raw-`UPDATE` writer (`updateStatus`/`qqStatus`), **not** the worker inbound cron (`process_tickets.php` contains no reference to `ORDER_ASSIGNED_AWAITING_SCHEDULING`). Note that **since TRUE-80060** a *status-changing* `class.repair.php::addNotes` now **calls** `updateStatus()` (so it too can move the cache via that raw `UPDATE`); a **pure comment** still writes nothing to `repair_orders.status`. This history-gap technique is how TRUE-80060 was pinned to `qqStatus()` rather than the SNOW round-trip.
|
|
158
|
+
- **KNOWN REMAINING GAP (needs a follow-up ticket) — the invalid-assignment-group email reports only the LAST ticket of a run.** `$invalidAssignmentGroups` is declared **inside** the outer `do/while`, so it resets every ticket; the email body now says so explicitly. Agreed fix: declare it before the loop (as `$failedPatches` already is) **and** group output by assignment group with a capped ticket list — one unmapped group can cover 60+ tickets. Rare in practice; the default group is TOGA's own, historically named "ASI Systems".
|
|
124
159
|
- **KNOWN REMAINING GAP (needs a follow-up ticket):** both `qqStatus()` hold gates live inside the `type IN (ONSITE_REPAIR, ONSITE_SERVICE)` branch. The `DEPOT_REPAIR` branch (`repairorder.php` from ~L799) has **no hold gate at all**, so a `DEPOT_REPAIR` order set to any hold status is still silently recomputed out of hold by `updateStatus()` — same class of bug, untouched by TRUE-80060.
|
|
125
160
|
- **KNOWN REMAINING GAP (needs a follow-up ticket) — inbound note reconciliation is paged too small.** `process_tickets.php`'s inbound comment reconciliation fetches only `page_size=50, page_number=1` of the SNOW journal, then **deletes any local note not in that set** (the remainder-delete ~L1326–1330). An order with **>50** comments/work_notes can have local notes silently deleted. It is also inconsistent with `addNotes`, which fetches `page_size=100`.
|
|
126
161
|
- **KNOWN REMAINING GAP — `buildDoubleFieldArray` collapses unsynced notes.** In `process_tickets`, `buildDoubleFieldArray` keys on `IFNULL(referenceId,0)`, so **all** NULL-`referenceId` notes collapse to key `'0'`, making the remainder-delete handle multiple unsynced notes inconsistently.
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: nycdoe
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-18
|
|
10
10
|
owners: [mhammontree, sking]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/sync/nycdoe/import_asn.php
|
|
@@ -129,6 +129,9 @@ Vendor SFTP ───(legacy_import_asn.php, ser+non-ser)─┘ [UNIQUE ded
|
|
|
129
129
|
|
|
130
130
|
### Debugging & DB topology (DOE 1.0 sync)
|
|
131
131
|
The 1.0 worker cannot run on a dev/Windows box; use the toga DB MCP + `Logs.API` instead of running prod code locally.
|
|
132
|
+
- **⚠ `Logs.API` retention is 30 days from 2026-09-17** (Jeff, sprint review) and existing logs are being purged — so it is no longer a *historical* debugging avenue, only a recent-window one. Bugs that burn slowly (a hold not re-pushed for 22 days; 9,914 PATCHes traced back to 2026-08-20) can no longer be reconstructed after the fact and must be caught by **alerting** instead. NYCDOE stays 1.0, so its logs remain in legacy `Logs.API`; Compass/Elite/WJE move their API logging to 2.0.
|
|
133
|
+
- **`repair_orders.dtUpdated` and `.lastupdateddate` are both time-skewed in `TOGaDeskSupport`** — never select or diff on them, and never compensate with a fixed offset. Detail: [Field-Service Dispatch](../../../1.0/apps/togadesk/features/field-service-dispatch.md).
|
|
134
|
+
- **`dbchanges` does not describe the real 1.0 schema** — some live columns were created outside the repo. Verify indexes/columns against the database, not the repo: [Authoring & Shipping a 1.0 dbchanges SQL File](../../../1.0/apps/dbchanges/workflows/authoring-and-shipping-sql-files.md).
|
|
132
135
|
- **`db_core` alias → legacy (V1) environment, schema `Core`** — holds the FLAT tables `AdvanceShippingNotices` / `AdvanceShippingNoticeItems` / `AdvanceShippingNoticeUnits` (columns incl. `togadeskRepairOrderId`, `serialNumber`, `netSuiteInternalSalesOrderId`, `netSuiteInternalPurchaseOrderId`, `customerPurchaseOrder`). The 1.0 cron reads **legacy/Core**.
|
|
133
136
|
- **Do NOT confuse with prod (V2) `Client_Nycdoe`** — a DIFFERENT, normalized schema (`AdvanceShippingNoticeItemUnits` with a `unitId` FK). The 1.0 cron does not read V2.
|
|
134
137
|
- **NetSuite SOAP request/response payloads** are logged in legacy `Logs.API` — filter `sourceJob LIKE '%3_create_installation_ticket%'`; `requestPayload` contains the PO internalId. (The multi-PO case was verified this way: the receipt search for PO 6971859 returned `totalRecords=1`, only `38S0500`.)
|
package/package.json
CHANGED