toga-ai 1.0.472 → 1.0.474
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/INDEX.md +5 -0
- package/knowledge/1.0/apps/dbchanges/workflows/authoring-and-shipping-sql-files.md +149 -0
- package/knowledge/1.0/apps/library/features/startech-pcmaticb2b-sync.md +23 -1
- package/knowledge/1.0/apps/worker/INDEX.md +1 -1
- package/knowledge/1.0/apps/worker/architecture.md +18 -3
- package/knowledge/2.0/apps/worker2/features/startech-webhook-handler.md +22 -1
- package/knowledge/INDEX.md +1 -0
- package/knowledge/clients/nycdoe/INDEX.md +1 -1
- package/knowledge/clients/nycdoe/features/servicenow-integration.md +97 -6
- package/knowledge/clients/nycdoe/profile.md +6 -2
- package/knowledge/clients/pcmaticb2b/INDEX.md +1 -0
- package/knowledge/clients/pcmaticb2b/features/startech-per-ticket-type-stages.md +186 -0
- package/knowledge/clients/pcmaticb2b/features/startech-ticket-sync.md +12 -1
- package/knowledge/registry.json +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# dbchanges (Database Changes) — 1.0 knowledge
|
|
2
|
+
|
|
3
|
+
| Doc | Summary | Files |
|
|
4
|
+
|-----|---------|-------|
|
|
5
|
+
| [Authoring & Shipping a 1.0 dbchanges SQL File](workflows/authoring-and-shipping-sql-files.md) | `dbchanges` is the **1.0** (legacy/V1) schema-and-data change repository — the 1.0 sibling of 2.0's `dbchanges2`. | dbchanges/index.php, dbchanges/Core/, worker/crons/infrastructure/execute_dbchanges.php, worker/.ebextensions/030_dbchanges.config |
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Authoring & Shipping a 1.0 dbchanges SQL File
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: dbchanges
|
|
5
|
+
project: Database Changes
|
|
6
|
+
client: shared
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-29
|
|
10
|
+
owners: [sking]
|
|
11
|
+
files:
|
|
12
|
+
- dbchanges/index.php
|
|
13
|
+
- dbchanges/Core/
|
|
14
|
+
- worker/crons/infrastructure/execute_dbchanges.php
|
|
15
|
+
- worker/.ebextensions/030_dbchanges.config
|
|
16
|
+
related:
|
|
17
|
+
- ../../worker/architecture.md
|
|
18
|
+
- ../../../../clients/nycdoe/features/servicenow-integration.md
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Summary
|
|
22
|
+
|
|
23
|
+
`dbchanges` is the **1.0** (legacy/V1) schema-and-data change repository — the 1.0 sibling of
|
|
24
|
+
2.0's `dbchanges2`. Unlike `dbchanges2` it ships **its own runner**, `dbchanges/index.php`, which
|
|
25
|
+
is invoked by the worker tier. The runner's parsing model is crude and its error path is
|
|
26
|
+
**fail-stop for the whole queue**, so *how* you write the `.sql` file decides whether your change
|
|
27
|
+
(and everyone else's queued behind it) applies at all.
|
|
28
|
+
|
|
29
|
+
Two rules dominate everything below:
|
|
30
|
+
|
|
31
|
+
1. **A chunk that contains only comments aborts the entire queue.** Put all commentary *before*
|
|
32
|
+
the final statement; never end a commented line with a semicolon.
|
|
33
|
+
2. **`_production` does NOT auto-apply.** Merged production data changes land on the next worker
|
|
34
|
+
**deploy**, not within minutes.
|
|
35
|
+
|
|
36
|
+
> `dbchanges/Core/*` targets the **legacy (V1) `Core` schema** — not the 2.0 prod `Core`. Confirm
|
|
37
|
+
> the target environment before writing anything.
|
|
38
|
+
|
|
39
|
+
## How the runner parses a file (`dbchanges/index.php`)
|
|
40
|
+
|
|
41
|
+
- Each `.sql` file is read whole and split on the **literal `";\n"`** (semicolon + newline)
|
|
42
|
+
— `index.php` ~L140. There is no SQL lexer: no awareness of strings, comments, or delimiters.
|
|
43
|
+
- Every non-empty chunk is sent to MySQL through a single **`mysqli_query()`** (~L145). Multi-
|
|
44
|
+
statement execution is not used.
|
|
45
|
+
- On error (~L146–170) the runner writes to `Logs.Errors` and, on the prod path, **`exit()`s** —
|
|
46
|
+
which **blocks every dbchanges file queued behind it** until the offending file is fixed.
|
|
47
|
+
- Each file runs **inside a transaction**: `mysqli_autocommit($link, false)` is set before the
|
|
48
|
+
`_dbchanges` bookkeeping INSERT, and autocommit is only re-enabled (implicit COMMIT) after the
|
|
49
|
+
whole file succeeds. So on error the statement **and** its `_dbchanges` row roll back together,
|
|
50
|
+
and the file is retried on the next run.
|
|
51
|
+
|
|
52
|
+
### Consequence: the "empty query" trap
|
|
53
|
+
|
|
54
|
+
A chunk consisting solely of `--` comment lines comes back as MySQL error **1065 "Query was
|
|
55
|
+
empty"** — a hard failure that stops the queue. This is easy to trigger accidentally by pasting
|
|
56
|
+
verification queries or explanatory notes *after* the last statement.
|
|
57
|
+
|
|
58
|
+
```sql
|
|
59
|
+
-- CORRECT: all commentary lives above the statement, one trailing statement.
|
|
60
|
+
-- Repoint ASN items 53919/53933 to the received SO/PO chain.
|
|
61
|
+
UPDATE ...;
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```sql
|
|
65
|
+
-- WRONG: the trailing comment block becomes its own chunk → error 1065 → queue stops.
|
|
66
|
+
UPDATE ...;
|
|
67
|
+
-- Verify with:
|
|
68
|
+
-- SELECT * FROM AdvanceShippingNoticeItems WHERE id IN (53919, 53933);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
All 13 pre-existing files under `dbchanges/Core/` comply with this. Follow them.
|
|
72
|
+
|
|
73
|
+
## Expressing an all-or-nothing guard (the only pattern that works)
|
|
74
|
+
|
|
75
|
+
Procedural guards are **not expressible** in a dbchanges file:
|
|
76
|
+
|
|
77
|
+
- `IF` / `SIGNAL` / conditional `COMMIT`-`ROLLBACK` on `ROW_COUNT()` are legal only inside a
|
|
78
|
+
stored routine.
|
|
79
|
+
- A routine body cannot be shipped anyway: its internal semicolons are shredded by the `";\n"`
|
|
80
|
+
split, and `DELIMITER` is a **client** directive that `mysqli` does not understand.
|
|
81
|
+
- An explicit `COMMIT` is **actively harmful** — it lands the data change independently of the
|
|
82
|
+
`_dbchanges` bookkeeping row, breaking retry-on-failure.
|
|
83
|
+
|
|
84
|
+
The working pattern is a **single-statement inline preflight guard**: a derived-table subquery
|
|
85
|
+
that counts the rows in the expected *before* state and requires an exact count, so the statement
|
|
86
|
+
is a no-op unless reality matches the assumption. The **derived-table wrapper is load-bearing** —
|
|
87
|
+
it is what avoids MySQL error **1093** ("can't specify target table for update in FROM clause").
|
|
88
|
+
Validated on **MySQL 8.0.42**.
|
|
89
|
+
|
|
90
|
+
```sql
|
|
91
|
+
UPDATE SomeTable t
|
|
92
|
+
SET t.col = <new>
|
|
93
|
+
WHERE t.id IN (<ids>)
|
|
94
|
+
AND (SELECT cnt FROM (
|
|
95
|
+
SELECT COUNT(*) AS cnt FROM SomeTable
|
|
96
|
+
WHERE id IN (<ids>) AND col = <expected-old>
|
|
97
|
+
) AS preflight) = <expected-row-count>;
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Which branches actually auto-apply
|
|
101
|
+
|
|
102
|
+
| Path | Trigger | Timing |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `execute_dbchanges` cron (`worker/crons/infrastructure/execute_dbchanges.php`) | branch matches `_%` **and** `config.<env>.ini` exists in the repo (env = branch minus the leading `_`) | every **2 minutes** |
|
|
105
|
+
| Elastic Beanstalk container command (`worker/.ebextensions/030_dbchanges.config`) | worker **deploy** | on next deploy |
|
|
106
|
+
|
|
107
|
+
The repo ships `config.alpha/beta/demo/hotfix/prod/stage/test/worker.ini`, therefore:
|
|
108
|
+
|
|
109
|
+
- **Auto-apply within ~2 min of a push:** `_alpha`, `_beta`, `_demo`, `_hotfix`, `_stage`.
|
|
110
|
+
- **`_production` does NOT auto-apply** — it would need `config.production.ini`, which does not
|
|
111
|
+
exist (the shipped file is `config.prod.ini`). Production applies via the EB deploy container
|
|
112
|
+
command instead, i.e. **on the next worker deploy**. Plan comms and expectations accordingly.
|
|
113
|
+
|
|
114
|
+
## Steps
|
|
115
|
+
|
|
116
|
+
1. Confirm the target schema/environment (`Core/` = legacy V1 `Core`).
|
|
117
|
+
2. Branch per the git-workflow rules (`fix/...`, `feature/...`) — never commit to `_production`.
|
|
118
|
+
3. Write **one file, commentary first, statements last**, no trailing comment chunk, no explicit
|
|
119
|
+
`COMMIT`, and no `DELIMITER`/stored-routine constructs.
|
|
120
|
+
4. Wrap any risky data change in the derived-table preflight guard above so a drifted before-state
|
|
121
|
+
is a no-op rather than a wrong write.
|
|
122
|
+
5. Open a PR into `_production` (or the target `_<env>` branch).
|
|
123
|
+
6. After merge: `_<env>` branches apply within ~2 min; **`_production` waits for the next worker
|
|
124
|
+
deploy** — verify with a read-only query afterwards rather than assuming.
|
|
125
|
+
|
|
126
|
+
## Gotchas
|
|
127
|
+
|
|
128
|
+
- **A comment-only chunk stops the whole queue** (error 1065 + `exit()` on the prod path). Your
|
|
129
|
+
malformed file blocks unrelated teammates' migrations, so this is a team-wide failure, not a
|
|
130
|
+
local one.
|
|
131
|
+
- **No `DELIMITER`, no stored routines, no explicit `COMMIT`.** See above.
|
|
132
|
+
- **Error 1093** is why the preflight subquery must be wrapped in a derived table.
|
|
133
|
+
- **Retry semantics are a feature**: because the data change and the `_dbchanges` row share a
|
|
134
|
+
transaction, a failing file re-runs next cycle. Do not "help" it with a manual COMMIT.
|
|
135
|
+
- **⚠ SECURITY — hardcoded GitHub token.** `worker/crons/infrastructure/execute_dbchanges.php`
|
|
136
|
+
contains a **plaintext GitHub personal access token** in the small config block near the top of
|
|
137
|
+
the file (~L14–18), and interpolates it into a `shell_exec()` git-clone command line — which
|
|
138
|
+
also exposes it in the worker host's process listing. Treat the token as **compromised**:
|
|
139
|
+
rotate it, move it to config/env (`config.<env>.ini`), and pass it without embedding it in an
|
|
140
|
+
argv string. No credential value is recorded here by design.
|
|
141
|
+
|
|
142
|
+
## Change history
|
|
143
|
+
- 2026-07-29 — Repo onboarded to the KB (1.0 `dbchanges`, framework core sibling of `dbchanges2`).
|
|
144
|
+
Documented the `index.php` `";\n"` split + `mysqli_query()` model and the comment-only-chunk /
|
|
145
|
+
error-1065 queue abort, the per-file transaction & retry semantics, the derived-table preflight
|
|
146
|
+
guard as the only workable all-or-nothing pattern (verified MySQL 8.0.42), and which `_%`
|
|
147
|
+
branches actually auto-apply (`_production` does **not** — it lands on the next worker deploy).
|
|
148
|
+
Flagged the plaintext GitHub token in `worker/crons/infrastructure/execute_dbchanges.php` for
|
|
149
|
+
rotation. (sking)
|
|
@@ -6,7 +6,7 @@ project: Library
|
|
|
6
6
|
client: pcmaticb2b
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-29
|
|
10
10
|
owners: [snaredla]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/api/toga2.php
|
|
@@ -84,3 +84,25 @@ Imports Startech (Easeedesk V3) config into TOGA 2.0 — users, groups, devices,
|
|
|
84
84
|
- 2026-06-23: Initial doc — ticket type maps (both directions), NULL sync behavior, TOGADESK_PCS type added
|
|
85
85
|
- 2026-07-06: Added syncWithToga V1 leg (cross-ref gating, cron-abort ordering, debugging findings) and supporting-records import notes (groups via groups_dispatching, getCategories ticket-type filter)
|
|
86
86
|
- 2026-07-09: Corrected the groups note — groups come from `dynamic_field_options->data->groups` (16 groups); the empty result was a credentials bug (`agilant.api`), not the endpoint. Confirmed `getCategories` `ticket_type` filter works (`50,54` → 15 categories).
|
|
87
|
+
- 2026-07-29: Added two per-ticket-type stage helpers to `App_Api_Toga2` (see
|
|
88
|
+
`clients/pcmaticb2b/features/startech-per-ticket-type-stages.md`):
|
|
89
|
+
`getStartechSelectableTicketStageNames($togadeskClientId, $togadeskDepartmentId)` resolves the
|
|
90
|
+
agent-selectable stage names for TOGaDesk's status dropdown (department → ticket type via
|
|
91
|
+
`TicketTypes.c_togadeskTicketDepartmentId`, filtered by `c_isSelectable = 1`), and
|
|
92
|
+
`getStartechTicketStageUuid($ticketTypeName, $ticketStageName)` resolves a stage within a type for
|
|
93
|
+
the 1.0 → 2.0 payload. Both reference the client's 2.0 schema **by name** (from
|
|
94
|
+
`App_Model_Client::getClientDatabaseNames()`) so the JOIN stays inside one client schema — do not use
|
|
95
|
+
`qqJoinClientsTable(…, true)` here, its UNION-across-all-clients form would cross-match ids between
|
|
96
|
+
clients. StarTech clients are identified by `App_Api_Toga2::STARTECH_TOGADESK_CLIENTS`
|
|
97
|
+
(TOGaDesk client id => TOGA 2.0 client id); non-StarTech clients short-circuit with no DB hit.
|
|
98
|
+
- 2026-07-29: Added two per-ticket-type stage helpers to `App_Api_Toga2` (see
|
|
99
|
+
`clients/pcmaticb2b/features/startech-per-ticket-type-stages.md`):
|
|
100
|
+
`getStartechSelectableTicketStageNames($togadeskClientId, $togadeskDepartmentId)` resolves the
|
|
101
|
+
agent-selectable stage names for TOGaDesk's status dropdown (department → ticket type via
|
|
102
|
+
`TicketTypes.c_togadeskTicketDepartmentId`, filtered by `c_isSelectable = 1`), and
|
|
103
|
+
`getStartechTicketStageUuid($ticketTypeName, $ticketStageName)` resolves a stage within a type for
|
|
104
|
+
the 1.0 → 2.0 payload. Both reference the client's 2.0 schema **by name** (from
|
|
105
|
+
`App_Model_Client::getClientDatabaseNames()`) so the JOIN stays inside one client schema — do not use
|
|
106
|
+
`qqJoinClientsTable(…, true)` here, its UNION-across-all-clients form would cross-match ids between
|
|
107
|
+
clients. StarTech clients are identified by `App_Api_Toga2::STARTECH_TOGADESK_CLIENTS`
|
|
108
|
+
(TOGaDesk client id => TOGA 2.0 client id); non-StarTech clients short-circuit with no DB hit.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
|
-
| [Worker (1.0 Framework) Architecture](architecture.md) | `worker` is the legacy (**1.0** `App_` framework) **background-job tier**. | worker/index.php, worker/_/app/framework.php, worker/crons/, worker/schedules/, worker/ebs/cron.worker.php, worker/.ebextensions/035_cron.worker.config |
|
|
5
|
+
| [Worker (1.0 Framework) Architecture](architecture.md) | `worker` is the legacy (**1.0** `App_` framework) **background-job tier**. | worker/index.php, worker/_/app/framework.php, worker/crons/, worker/schedules/, worker/ebs/cron.worker.php, worker/.ebextensions/035_cron.worker.config, worker/crons/infrastructure/execute_dbchanges.php, worker/.ebextensions/030_dbchanges.config |
|
|
6
6
|
| [Compass MA Sales Order Exception Report](features/compass-ma-sales-order-exception-report.md) | A worker cron that emails operations the "Compass Refresh Exception Report" — Compass `MA%` sales orders whose corresponding Office Depot (ODP) sales order has | worker/crons/toga2/compass/workflow/7_generate_ma_sales_order_exception_report.php |
|
|
7
7
|
| [Compass Partial In-Transit & Delivered Emails (per package)](features/compass-partial-in-transit-delivered-emails.md) | Compass USA and Compass Canada send a **per-package** in-transit email (and a matching delivered email) instead of one email listing the whole order. | worker/crons/toga2/compass/update_salesorder_status_from_odp.php, worker/crons/toga2/compasscanada/workflow/3_update_salesorder_status_from_grand_and_toy.php |
|
|
8
8
|
| [Elite TOGA 2.0 → TOGaDeskSupport Standalone Attachment Sync](features/elite-togadesk-attachment-sync.md) | `sync_togadesk_elite_attachments.php` is a standalone cron (every 5 minutes) that syncs file attachments from TOGA 2.0 into TOGaDeskSupport for Elite. | worker/crons/toga2/elite/sync_togadesk_elite_attachments.php, worker/crons/toga2/elite/test_sync_togadesk_elite_attachments.php |
|
|
@@ -6,8 +6,8 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [jcardinal]
|
|
9
|
+
updated: 2026-07-29
|
|
10
|
+
owners: [jcardinal, sking]
|
|
11
11
|
files:
|
|
12
12
|
- worker/index.php
|
|
13
13
|
- worker/_/app/framework.php
|
|
@@ -15,8 +15,11 @@ files:
|
|
|
15
15
|
- worker/schedules/
|
|
16
16
|
- worker/ebs/cron.worker.php
|
|
17
17
|
- worker/.ebextensions/035_cron.worker.config
|
|
18
|
+
- worker/crons/infrastructure/execute_dbchanges.php
|
|
19
|
+
- worker/.ebextensions/030_dbchanges.config
|
|
18
20
|
related:
|
|
19
21
|
- ../library/architecture.md
|
|
22
|
+
- ../dbchanges/workflows/authoring-and-shipping-sql-files.md
|
|
20
23
|
---
|
|
21
24
|
|
|
22
25
|
## Summary
|
|
@@ -190,7 +193,7 @@ The worker box is assembled from several repos, not just this one. `ebs/git.json
|
|
|
190
193
|
|---|---|---|
|
|
191
194
|
| `library` | `/var/www/library` | 1.0 framework core (`_.php`, all `App_*`). |
|
|
192
195
|
| `resources` | `…/resources` | Front-end assets (not used by crons). |
|
|
193
|
-
| `dbchanges` | `/var/www/dbchanges` | DB migration scripts. |
|
|
196
|
+
| `dbchanges` | `/var/www/dbchanges` | DB migration scripts **+ their runner (`index.php`)**. Applied by the 2-min `crons/infrastructure/execute_dbchanges.php` cron on `_%` branches with a matching `config.<env>.ini`, and by `.ebextensions/030_dbchanges.config` on deploy. Authoring rules: [Authoring & Shipping a 1.0 dbchanges SQL File](../dbchanges/workflows/authoring-and-shipping-sql-files.md). |
|
|
194
197
|
| `togadesk` | `…/ontrack` | TOGaDesk app — note `cron.worker.togadesk.json` runs `../ontrack/crons/tickets_prod.php` and `monitoring_prod.php` **out of the sibling repo**. |
|
|
195
198
|
| `_underscore` | `/var/www/_underscore` | **2.0 framework core** — present because the `toga2/` client integrations reach into 2.0 (TOGa-2 / TOGaSupply). This is a genuine 1.0↔2.0 bridge living inside a 1.0 app. |
|
|
196
199
|
|
|
@@ -205,6 +208,18 @@ web face (`mvc/` GET routes for login/logout/404); the tier's real work is the c
|
|
|
205
208
|
|
|
206
209
|
## Conventions & gotchas
|
|
207
210
|
|
|
211
|
+
- **`dbchanges` auto-apply is branch-gated, and `_production` is NOT auto-applied.**
|
|
212
|
+
`crons/infrastructure/execute_dbchanges.php` runs every 2 minutes but only for branches matching
|
|
213
|
+
`_%`, deriving the env by stripping the leading `_` and requiring `config.<env>.ini` in the
|
|
214
|
+
dbchanges repo. Present configs mean `_alpha`/`_beta`/`_demo`/`_hotfix`/`_stage` apply within
|
|
215
|
+
~2 min of a push, while `_production` does not (it would need `config.production.ini`; the
|
|
216
|
+
shipped file is `config.prod.ini`) — production migrations land via
|
|
217
|
+
`.ebextensions/030_dbchanges.config` on the **next worker deploy**.
|
|
218
|
+
- **⚠ SECURITY — plaintext GitHub token in `crons/infrastructure/execute_dbchanges.php`.** A
|
|
219
|
+
hardcoded GitHub personal access token sits in the config block near the top of the file
|
|
220
|
+
(~L14–18) and is interpolated into a `shell_exec()` git-clone command line, exposing it in the
|
|
221
|
+
worker host's process listing. Treat as **compromised**: rotate, move to `config.<env>.ini`/env,
|
|
222
|
+
and stop passing it in argv. (No credential value is recorded in the KB.)
|
|
208
223
|
- **Schedules are the source of truth for what runs.** A `.php` under `crons/` that no schedule
|
|
209
224
|
references is dormant (and may be `DOA/`). Set `active: 0` to disable without deleting.
|
|
210
225
|
- **All cron paths are relative to `crons/`** except the `../ontrack/...` jobs that run from the
|
|
@@ -6,12 +6,13 @@ project: Worker
|
|
|
6
6
|
client: pcmaticb2b
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-07-29
|
|
10
10
|
owners: [snaredla]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Startech.php
|
|
13
13
|
related:
|
|
14
14
|
- clients/pcmaticb2b/features/startech-ticket-sync.md
|
|
15
|
+
- clients/pcmaticb2b/features/startech-per-ticket-type-stages.md
|
|
15
16
|
- clients/pcmaticb2b/profile.md
|
|
16
17
|
---
|
|
17
18
|
|
|
@@ -53,6 +54,26 @@ Conditional additions: `ticketStage` (from `ticket_status`), `contact` (from `cr
|
|
|
53
54
|
- `c_escalateToStartech: 0` must be in the payload. If omitted, the interceptor still skips Startech (null is also falsy), but the field stays NULL in TOGA 2.0 — making webhook tickets indistinguishable from TOGaDesk-created ones.
|
|
54
55
|
- `ticketType` is resolved via `c_startechTicketTypeId` on `TicketTypes` — not a hardcoded string. Startech type IDs: SR=6, PCS=54, API=201.
|
|
55
56
|
|
|
57
|
+
## Ticket type and stage lookups (per-ticket-type stages)
|
|
58
|
+
|
|
59
|
+
Since ticket stages became scoped to a ticket type, matching a nested child by a single Startech id is
|
|
60
|
+
ambiguous, so `Webhook()` resolves both explicitly and sends `['uuid' => …]`:
|
|
61
|
+
|
|
62
|
+
- **Ticket type** — `GET /ticket-types` filtered on `c_startechTicketTypeId` **plus** `code`, mapped by
|
|
63
|
+
`_Worker_Startech::TOGA_TICKET_TYPE_CODE_BY_STARTECH_TICKET_TYPE_ID` (`50 => 'SUPPORT REQUEST'`,
|
|
64
|
+
`54 => 'PCS'`). **Startech type 54 maps to three TOGA types** for PC Matic B2B — 1 PCS, 3 API and
|
|
65
|
+
4 Togadesk PCS — so `c_startechTicketTypeId` alone cannot identify a type. API and Togadesk PCS are
|
|
66
|
+
TOGA-side origins (tickets created via the API or in TOGaDesk), never what Startech sends inbound.
|
|
67
|
+
- **Ticket stage** — `GET /ticket-stages` filtered on `c_startechStageId` **plus** the resolved
|
|
68
|
+
`ticketTypeId`. Startech status ids repeat across types (New = `1` for every type), so a stage is only
|
|
69
|
+
unique per type. If the type cannot be resolved, the stage is **omitted** rather than risking a stage
|
|
70
|
+
from another type; the type itself falls back to the old `c_startechTicketTypeId` match.
|
|
71
|
+
|
|
72
|
+
Both filters use `fields` to request only `id`/`uuid`. Filtering on `ticketTypeId` requires the
|
|
73
|
+
`Core.RecordFields` registration (id 2479) — see the per-ticket-type-stages doc for deploy order.
|
|
74
|
+
|
|
56
75
|
## Change history
|
|
57
76
|
|
|
77
|
+
- 2026-07-29: Explicit ticket-type (id + code) and ticket-stage (id + ticketTypeId) lookups for
|
|
78
|
+
per-ticket-type stages; documented the Startech type 54 → three TOGA types ambiguity
|
|
58
79
|
- 2026-06-23: Initial doc — webhook handler, c_escalateToStartech=0 payload field, POST vs PUT logic
|
package/knowledge/INDEX.md
CHANGED
|
@@ -6,6 +6,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
6
6
|
|
|
7
7
|
- **library** (Library) _(framework core)_ — 14 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
8
|
- **worker** (Worker) — 17 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
|
|
9
|
+
- **dbchanges** (Database Changes) _(framework core)_ — 1 doc(s) → [1.0/apps/dbchanges/INDEX.md](1.0/apps/dbchanges/INDEX.md)
|
|
9
10
|
- **worker1.5** (Worker 1.5) — 0 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
|
|
10
11
|
- **togadesk** (TOGa Desk) — 10 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
|
11
12
|
- **togaview** (TOGa View) — 6 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
| Doc | Framework | Summary | Files |
|
|
4
4
|
|-----|-----------|---------|-------|
|
|
5
5
|
| [NYCDOE Ticket Hold-Status Sync (ServiceNow ⇄ TOGaDesk)](features/hold-status-sync.md) | 1.0 | DOE ticket **hold** status must round-trip between ServiceNow (SNOW) and TOGaDesk and **stay held** — holds are SLA-bearing in both systems. | worker/crons/sync/nycdoe/send_ticket_updates.php, worker/crons/sync/nycdoe/process_tickets.php, worker/crons/sync/nycdoe/send_request_item_updates.php, library/app/model/togadesk/repairorder.php, library/app/api/nycdoev2.php, togadesk/desk/includes/classes/class.repair.php |
|
|
6
|
-
| [NYCDOE ServiceNow / ASN Integration](features/servicenow-integration.md) | 1.0 | The NYCDOE/ServiceNow integration mirrors DOE's ServiceNow tickets (Incidents + RITMs) into local tables, turns vendor shipment notices into NetSuite Sales Orde | worker/crons/sync/nycdoe/import_asn.php, worker/crons/sync/nycdoe/import_inc.php, worker/crons/sync/nycdoe/legacy_import_asn.php, worker/crons/sync/nycdoe/legacy_process_asn_queue.php, worker/crons/sync/nycdoe/process_tickets.php, worker/crons/sync/nycdoe/1_send_asn_to_netsuite.php, worker/crons/sync/nycdoe/2_send_serials_to_netsuite.php, worker/crons/sync/nycdoe/3_create_installation_ticket.php, worker/crons/sync/nycdoe/test_multi_po_receipt_resolution.php, worker/crons/sync/nycdoe/send_ticket_updates.php, worker/crons/sync/nycdoe/send_request_item_updates.php, worker/crons/sync/nycdoe/send_nycdoe_proof_of_delivery.php, worker/crons/sync/nycdoe/sync_nycdoe_locations.php, worker/crons/sync/nycdoe/receive_edi_purchase_orders.php, worker/crons/sync/nycdoe/send_edi_open_invoices.php, worker/crons/notifications/nycdoe/, worker/schedules/cron.worker.sync.json, worker/schedules/cron.worker.notification.json, library/app/api/nycdoe.php, library/app/api/nycdoev2.php, library/app/asnprocessor/manufacturer.php, library/app/asnprocessor/apple.php, library/app/asnprocessor/lenovo.php, library/app/asnprocessor/lexmark.php, library/app/asnprocessor/acer.php, library/app/edi.php, library/app/netsuite.php |
|
|
6
|
+
| [NYCDOE ServiceNow / ASN Integration](features/servicenow-integration.md) | 1.0 | The NYCDOE/ServiceNow integration mirrors DOE's ServiceNow tickets (Incidents + RITMs) into local tables, turns vendor shipment notices into NetSuite Sales Orde | worker/crons/sync/nycdoe/import_asn.php, worker/crons/sync/nycdoe/import_inc.php, worker/crons/sync/nycdoe/legacy_import_asn.php, worker/crons/sync/nycdoe/legacy_process_asn_queue.php, worker/crons/sync/nycdoe/process_tickets.php, worker/crons/sync/nycdoe/1_send_asn_to_netsuite.php, worker/crons/sync/nycdoe/2_send_serials_to_netsuite.php, worker/crons/sync/nycdoe/3_create_installation_ticket.php, worker/crons/sync/nycdoe/test_multi_po_receipt_resolution.php, worker/crons/sync/nycdoe/send_ticket_updates.php, worker/crons/sync/nycdoe/send_request_item_updates.php, worker/crons/sync/nycdoe/send_nycdoe_proof_of_delivery.php, worker/crons/sync/nycdoe/sync_nycdoe_locations.php, worker/crons/sync/nycdoe/receive_edi_purchase_orders.php, worker/crons/sync/nycdoe/send_edi_open_invoices.php, worker/crons/notifications/nycdoe/, worker/schedules/cron.worker.sync.json, worker/schedules/cron.worker.notification.json, library/app/api/nycdoe.php, library/app/api/nycdoev2.php, library/app/asnprocessor/manufacturer.php, library/app/asnprocessor/apple.php, library/app/asnprocessor/lenovo.php, library/app/asnprocessor/lexmark.php, library/app/asnprocessor/acer.php, library/app/edi.php, library/app/netsuite.php, dbchanges/Core/SK/ |
|
|
7
7
|
| [New York City Department of Education](profile.md) | 1.0 | NYC DOE (New York City Department of Education) is a TOGA client whose entire integration runs in the **1.0 worker tier** (~30 cron scripts under `worker/crons/ | |
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: nycdoe
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-29
|
|
10
10
|
owners: [mhammontree, sking]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/sync/nycdoe/import_asn.php
|
|
@@ -36,10 +36,12 @@ files:
|
|
|
36
36
|
- library/app/asnprocessor/acer.php
|
|
37
37
|
- library/app/edi.php
|
|
38
38
|
- library/app/netsuite.php
|
|
39
|
+
- dbchanges/Core/SK/
|
|
39
40
|
related:
|
|
40
41
|
- ../profile.md
|
|
41
42
|
- hold-status-sync.md
|
|
42
43
|
- ../../../1.0/apps/worker/architecture.md
|
|
44
|
+
- ../../../1.0/apps/dbchanges/workflows/authoring-and-shipping-sql-files.md
|
|
43
45
|
---
|
|
44
46
|
|
|
45
47
|
## Summary
|
|
@@ -149,11 +151,36 @@ Vendor SFTP ───(legacy_import_asn.php, ser+non-ser)─┘ [UNIQUE ded
|
|
|
149
151
|
line = 1 unit = 1 RO). Existing-serial lookup is scoped **per item row** (`$asnItemId`).
|
|
150
152
|
Item receipts are now resolved **Sales-Order-wide** via
|
|
151
153
|
`App_NetSuite::listItemReceiptsCreatedFromSalesOrder($soId)` (fans out across ALL POs from
|
|
152
|
-
`listPurchaseOrdersCreatedFromSalesOrder`, dedupes receipts by `internalId`), cached
|
|
153
|
-
per
|
|
154
|
-
|
|
155
|
-
receipt
|
|
156
|
-
|
|
154
|
+
`listPurchaseOrdersCreatedFromSalesOrder`, dedupes receipts by `internalId`), cached
|
|
155
|
+
**per sales-order internal id** (`$itemReceiptsBySo`, keyed by `netSuiteInternalSalesOrderId`)
|
|
156
|
+
— a shared SO is still fetched only once — with the `getItemReceipt` detail cache kept per
|
|
157
|
+
(globally-unique) receipt `internalId`. Both the inner item query **and** the outer
|
|
158
|
+
ASN-candidate query require `netSuiteInternalSalesOrderId <> 0` (a sentinel guard mirroring
|
|
159
|
+
the existing PO guard) so a null/zero-SO row cannot poison the cache or match the wrong SO's
|
|
160
|
+
receipts. Self-healing: units previously stuck under an unstamped PO clear on subsequent
|
|
161
|
+
5-min cron runs.
|
|
162
|
+
- **Receipt resolution is SO-ONLY — the stamped PO id never finds a receipt** (traced
|
|
163
|
+
2026-07-29, `3_create_installation_ticket.php`: candidate query ~L248-278, per-item ~L311-341,
|
|
164
|
+
receipt resolution ~L354-360, part-number match ~L418-422, per-unit gate ~L511-535). The chain
|
|
165
|
+
is strictly `AdvanceShippingNoticeItems.netSuiteInternalSalesOrderId` →
|
|
166
|
+
`listPurchaseOrdersCreatedFromSalesOrder` (all POs created from that SO) →
|
|
167
|
+
`listItemReceiptsCreateFromPurchaseOrder` for each → dedupe (this is what
|
|
168
|
+
`listItemReceiptsCreatedFromSalesOrder` wraps, `library/app/netsuite.php` ~L739 / ~L762 /
|
|
169
|
+
~L797, details via `getItemReceipt` ~L1697). **`netSuiteInternalPurchaseOrderId` is NEVER used
|
|
170
|
+
to look up receipts** — it is only a non-null / non-zero **eligibility gate**. The schema is
|
|
171
|
+
misleading here: the PO column *looks* authoritative and is not.
|
|
172
|
+
- **Item receipts are never persisted.** They are fetched **live from NetSuite on every run**
|
|
173
|
+
(no table, no bridge row), and the receipt-line → ASN-line linkage is derived **at runtime by
|
|
174
|
+
part-number string match** after stripping the `DOE-` prefix and `_` characters. So NetSuite is
|
|
175
|
+
the only source of truth for what was received, and a part-number formatting drift breaks the
|
|
176
|
+
match silently.
|
|
177
|
+
- **MSO reuse is safe on re-run.** `doeCreateInstallationRepairOrder()` looks the managed service
|
|
178
|
+
order up by `advanceShippingNoticeId`, so re-running Stage 5 against an ASN that already has an
|
|
179
|
+
MSO **reuses** it rather than creating a duplicate.
|
|
180
|
+
- **The per-SO (not per-ASN) receipt cache is load-bearing.** An ASN can carry lines from
|
|
181
|
+
more than one NetSuite sales order; caching the receipt set once per ASN off the *first*
|
|
182
|
+
line's SO matches every other line against the wrong SO's receipts (FIXED 2026-07-22 — see
|
|
183
|
+
the multi-SO gotcha + change history). This is one level *above* the multi-PO-per-SO fix.
|
|
157
184
|
- **"#/N received" UI** (`togadesk/desk/template/pages/managedserviceorders/view.php`):
|
|
158
185
|
denominator = `SUM(qtyOrder)` per part (committed + unsent); numerator = Units with
|
|
159
186
|
`togadeskRepairOrderId IS NOT NULL`.
|
|
@@ -212,6 +239,37 @@ Vendor SFTP ───(legacy_import_asn.php, ser+non-ser)─┘ [UNIQUE ded
|
|
|
212
239
|
pairs to size live double-truck-roll exposure; then trace the numeric-PO↔WR cluster.
|
|
213
240
|
Investigation was **read-only** — no code or data changes made.
|
|
214
241
|
|
|
242
|
+
- **⚠ SYSTEMIC, UNALERTED — a manually edited NetSuite SO strands hardware forever.** If a
|
|
243
|
+
NetSuite sales order is manually edited **after** Stage 3 has already stamped the SO/PO ids,
|
|
244
|
+
NetSuite may spawn a **replacement SO + PO**. The ASN line then points at the replacement PO,
|
|
245
|
+
which is **unreceived** (the goods were received on the *original* SO/PO chain), so Stage 5
|
|
246
|
+
resolves zero receipts → matches no serials → stamps nothing → creates no installation ticket.
|
|
247
|
+
**Silently, forever**: no error, no retry escalation, no alert; the every-5-min cron just keeps
|
|
248
|
+
finding nothing while the hardware sits stranded. Because receipt resolution is SO-only (see
|
|
249
|
+
Stage 5), the stamped SO id is the single point of failure.
|
|
250
|
+
- **Same family as the cross-wired SO/PO stamp regression below** (diagnosed 2026-07-23, ASN
|
|
251
|
+
26215): both start with a **manual NetSuite edit** and both are invisible because receipt
|
|
252
|
+
resolution is SO-only. There, the stamped PO still held the real receipt (a union lookup
|
|
253
|
+
recovers it); here the whole stamped chain points at an unreceived replacement, so the fix is
|
|
254
|
+
to repoint the ASN lines at the received chain.
|
|
255
|
+
- **`email_notification_siteid.php` does NOT catch this** — its gate still assumes the older
|
|
256
|
+
pre-non-serialized shape and it lacks the `netSuiteInternalSalesOrderId <> 0` sentinel
|
|
257
|
+
exclusion.
|
|
258
|
+
- **Durable fix (not yet built):** detect an ASN item with a stamped SO whose POs yield **zero**
|
|
259
|
+
item receipts beyond a threshold age, and alert — that condition is exactly this failure class.
|
|
260
|
+
- **Recovery pattern (what we do today):** repoint the affected `AdvanceShippingNoticeItems` rows
|
|
261
|
+
back to the SO/PO chain the goods were actually **received** on, via a `dbchanges` data repair,
|
|
262
|
+
then let the existing `*/5` Stage 5 cron create the TOGa Desk tickets itself. **No code change
|
|
263
|
+
is needed** and MSO reuse (keyed on `advanceShippingNoticeId`) makes the re-run idempotent.
|
|
264
|
+
- **Worked incident — ASN `26280`, customer PO `S202620046` (repaired 2026-07-29).** SO `271613`
|
|
265
|
+
(internal `6920229`) was manually edited, spawning replacement SO `274520`
|
|
266
|
+
(internal `6997347`) + PO `165123` (internal `6997348`) for two monitor lines. The monitors
|
|
267
|
+
were physically received on the **original** chain — PO `164203` (internal `6924437`), item
|
|
268
|
+
receipt internal `6924439` — so PO `165123` stayed unreceived and Stage 5 found nothing. Repair
|
|
269
|
+
repointed `AdvanceShippingNoticeItems` rows `53919` and `53933` to SO `6920229` / PO `6924437`
|
|
270
|
+
(`dbchanges/Core/SK/2026-07-29-data-repair-asn26280.sql`, PR dbchanges#257, branch
|
|
271
|
+
`fix/asn26280-repoint-monitor-so` → `_production`). Note `_production` does **not** auto-apply —
|
|
272
|
+
see the [1.0 dbchanges authoring workflow](../../../1.0/apps/dbchanges/workflows/authoring-and-shipping-sql-files.md).
|
|
215
273
|
- **The queue `dedupeKey` format is load-bearing.** A 2026 incident (WR260236464 duplicate
|
|
216
274
|
repair order) was caused by dedupeKey format drift (4-part keys with serial vs 3-part
|
|
217
275
|
without) letting old rows re-import; Stage 2 then created a sibling item row (frozen
|
|
@@ -325,6 +383,16 @@ use the toga DB MCP + `Logs.API` instead of running prod code locally.
|
|
|
325
383
|
of the consumer query; `php -l` every touched file.
|
|
326
384
|
|
|
327
385
|
## Change history
|
|
386
|
+
- 2026-07-29 — Repaired stranded ASN `26280` / customer PO `S202620046` (data-only, no code): a
|
|
387
|
+
manually edited SO spawned a replacement SO/PO (`274520`/`165123`) that was never received, so
|
|
388
|
+
Stage 5 found zero receipts and never ticketed two monitor lines; repointed
|
|
389
|
+
`AdvanceShippingNoticeItems` `53919`/`53933` to the received chain SO `6920229` / PO `6924437`
|
|
390
|
+
and let the `*/5` cron ticket them (`dbchanges/Core/SK/2026-07-29-data-repair-asn26280.sql`,
|
|
391
|
+
PR dbchanges#257). Documented the underlying **systemic, unalerted stranding failure class**
|
|
392
|
+
(`email_notification_siteid.php` does not catch it) and the traced fact that Stage 5 resolves
|
|
393
|
+
item receipts **only** via `netSuiteInternalSalesOrderId` — `netSuiteInternalPurchaseOrderId` is
|
|
394
|
+
purely an eligibility gate — with receipts fetched live from NetSuite each run and linked by
|
|
395
|
+
part-number match after stripping `DOE-`/`_`. Read-only in `worker`/`library`. (sking)
|
|
328
396
|
- 2026-07-23 — Diagnosed a regression of the 2026-07-09 multi-PO fix: Stage 5's SO-wide receipt
|
|
329
397
|
resolution *replaced* the by-stamped-PO lookup, so a cross-wired stamp (an item's stamped PO
|
|
330
398
|
not a child of its stamped SO) silently drops install tickets with no error. Root cause of the
|
|
@@ -336,6 +404,29 @@ use the toga DB MCP + `Logs.API` instead of running prod code locally.
|
|
|
336
404
|
owed ROs; a permanent union fix in the cron was considered but deferred (warehouse process now
|
|
337
405
|
corrected by training). Added a regression gotcha + two debugging notes. No production code
|
|
338
406
|
changed. (mhammontree; SME sking)
|
|
407
|
+
- 2026-07-22 — Fixed multi-SO-per-ASN silently dropping install tickets: Stage 5 cached the
|
|
408
|
+
NetSuite item-receipt set once per ASN off the first line's SO, so lines belonging to any
|
|
409
|
+
other SO on the same ASN matched the wrong receipts and never got a RepairOrder/asset. Now
|
|
410
|
+
cached **per sales-order internal id** (`$itemReceiptsBySo`); added the
|
|
411
|
+
`netSuiteInternalSalesOrderId <> 0` sentinel guard to both the outer ASN-candidate and the
|
|
412
|
+
per-item queries. One level above the 2026-07-09 multi-PO-per-SO fix. Case: PO `S202668066`,
|
|
413
|
+
ASN `26641`, SO `7219713`+`7246833`. Verified by read-only regression probe
|
|
414
|
+
`test_multi_so_receipt_cache.php` (PASS). PR worker#1678 (`fix/nycdoe-multi-so-receipt-cache`).
|
|
415
|
+
(sking)
|
|
416
|
+
- 2026-07-10 — Stage 5 (`3_create_installation_ticket.php`) now creates install tickets for
|
|
417
|
+
**non-serialized** received lines, which were previously never ticketed (Stage 5 was entirely
|
|
418
|
+
serial-driven off NetSuite `inventoryAssignment`). Added line-level column
|
|
419
|
+
`AdvanceShippingNoticeItems.togadeskRepairOrderId` (db_core, migration `dbchanges/Core/SK/
|
|
420
|
+
2026-07-10.sql`) + field on `App_Model_Core_AdvanceShippingNoticeItem`; ONE RO per non-serial
|
|
421
|
+
line on first receipt with qty>0 (no partial-qty accumulation); null serial/tag (both nullable,
|
|
422
|
+
verified). Rewrote ASN + item selection queries JOIN→EXISTS/NOT EXISTS (mutually-exclusive
|
|
423
|
+
serialized vs non-serialized branches); serialization decided authoritatively via
|
|
424
|
+
`App_NetSuite::getItemIsSerializedByPartNumber()`; extracted shared
|
|
425
|
+
`doeCreateInstallationRepairOrder()`; hoisted the ASN-completion check to once-per-ASN counting
|
|
426
|
+
both unit and line non-tickets. Also fixed a pre-existing SQL-injection risk (NetSuite serials
|
|
427
|
+
interpolated into `IN (...)` unescaped → now `App_Database::sqlEscape` via `array_map`; added
|
|
428
|
+
`(int)` casts on `$asnId`/`$asnItemId`). Branch `feature/doe-stage3-nonserialized-install`;
|
|
429
|
+
3 PRs deploy order dbchanges → library → worker. (sking)
|
|
339
430
|
- 2026-07-09 — Fixed silent install-ticket drop when a NetSuite SO is fulfilled across
|
|
340
431
|
multiple POs: Stage 5 now resolves item receipts Sales-Order-wide via new helper
|
|
341
432
|
`App_NetSuite::listItemReceiptsCreatedFromSalesOrder` (fans across all POs from the SO,
|
|
@@ -5,12 +5,13 @@ apps:
|
|
|
5
5
|
- worker
|
|
6
6
|
- library
|
|
7
7
|
- togadesk
|
|
8
|
+
- dbchanges
|
|
8
9
|
project: Worker
|
|
9
10
|
client: nycdoe
|
|
10
11
|
type: profile
|
|
11
12
|
status: active
|
|
12
|
-
updated: 2026-07-
|
|
13
|
-
owners: [mhammontree]
|
|
13
|
+
updated: 2026-07-29
|
|
14
|
+
owners: [mhammontree, sking]
|
|
14
15
|
files: []
|
|
15
16
|
related:
|
|
16
17
|
- features/servicenow-integration.md
|
|
@@ -32,6 +33,9 @@ runs over DOE's SFTP.
|
|
|
32
33
|
orders, and the "#received / #ordered" Receiving Summary UI.
|
|
33
34
|
- **NetSuite:** Sales Orders, PO reconciliation, item receipts, Item Fulfillments, invoices
|
|
34
35
|
(SuiteTalk toolkit in `library`).
|
|
36
|
+
- **`dbchanges` (1.0):** DOE schema + **data-repair** changes ship here (`dbchanges/Core/` targets
|
|
37
|
+
the legacy/V1 `Core` schema). Stranded-ASN recoveries are data-only repairs — see the
|
|
38
|
+
[1.0 dbchanges authoring workflow](../../1.0/apps/dbchanges/workflows/authoring-and-shipping-sql-files.md).
|
|
35
39
|
|
|
36
40
|
## Endpoints
|
|
37
41
|
| System | Endpoint | Notes |
|
|
@@ -3,5 +3,6 @@
|
|
|
3
3
|
| Doc | Framework | Summary | Files |
|
|
4
4
|
|-----|-----------|---------|-------|
|
|
5
5
|
| [PC Matic B2B — Startech Entitlement Provisioning (customer + SKU)](features/startech-entitlement-provisioning.md) | 2.0 | When a PC Matic B2B entitlement is created in TOGA 2.0 (`POST /entitlements`), the client registers the customer on StarTech's OptimumDesk platform and assigns | _underscore/Trait/Startech/Entitlement.php, _underscore/Model/Pcmaticb2b/Entitlement.php, _underscore/Config.php, api2/Config/production.ini, api2/Config/beta.ini, api2/Config/sandbox-client.ini, test/@srija/Startech Testing/PC Matic B2B/test_e2e_pcmaticb2b.sh |
|
|
6
|
+
| [PC Matic B2B — Startech Per-Ticket-Type Ticket Stages](features/startech-per-ticket-type-stages.md) | 2.0 | Startech (OptimumDesk) exposes a **different status set per ticket type** — Support Request (workflow 56) has 15 statuses, Phone Call Support (workflow 59) has | dbchanges2/Client/2026-07-22a - TicketStageTicketTypeId.sql, dbchanges2/Core/2026-07-28 - TicketStageTicketTypeIdRecordField.sql, dbchanges2/_modules/startech/2026-07-28 - TicketStageIsSelectable.sql, dbchanges2/Client_Pcmaticb2b/2026-07-28 - pcmaticb2b ticketstages.sql, _underscore/Model/Client/TicketStage.php, _underscore/Trait/Startech/TicketStage.php, _underscore/Model/Pcmaticb2b/TicketStage.php, _underscore/Trait/Startech/Ticket.php, worker2/Worker/Startech.php, library/app/api/toga2.php, library/app/model/togadesk/ticket.php, togadesk/desk/includes/functions.php, togadesk/desk/includes/controllers/data/tickets/manage.php, togadesk/desk/template/pages/tickets/manage.php |
|
|
6
7
|
| [PC Matic B2B — Startech Ticket Sync](features/startech-ticket-sync.md) | 1.0 | Bidirectional ticket sync between TOGaDesk 1.0 (client 177), TOGA 2.0 (client 21), and Startech (Easeedesk). | worker/crons/toga2/startech/sync_togadesk_startech_pcmaticb2b.php, worker/crons/toga2/startech/common_import_supporting_records.php, library/app/api/toga2.php, library/app/api/startechticket.php, togadesk/desk/includes/controllers/quickactions.php, togadesk/desk/template/pages/tickets/manage.php, togadesk/desk/includes/functions.php, worker2/Worker/Startech.php, _underscore/Trait/Startech/Ticket.php, dbchanges2/Client_Pcmaticb2b/2026-06-22-pcmaticb2b-enhancements.sql |
|
|
7
8
|
| [PC Matic B2B Client Profile](profile.md) | 1.0 | PC Matic B2B is a client using TOGaDesk 1.0 for ticket management, with TOGA 2.0 as the data layer and Startech (Easeedesk) as an external ticketing system for | worker/crons/toga2/startech/sync_togadesk_startech_pcmaticb2b.php, togadesk/desk/includes/functions.php |
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: PC Matic B2B — Startech Per-Ticket-Type Ticket Stages
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
project: TOGa
|
|
5
|
+
client: pcmaticb2b
|
|
6
|
+
type: client-feature
|
|
7
|
+
status: active
|
|
8
|
+
updated: 2026-07-29
|
|
9
|
+
owners: [snaredla]
|
|
10
|
+
files:
|
|
11
|
+
- dbchanges2/Client/2026-07-22a - TicketStageTicketTypeId.sql
|
|
12
|
+
- dbchanges2/Core/2026-07-28 - TicketStageTicketTypeIdRecordField.sql
|
|
13
|
+
- dbchanges2/_modules/startech/2026-07-28 - TicketStageIsSelectable.sql
|
|
14
|
+
- dbchanges2/Client_Pcmaticb2b/2026-07-28 - pcmaticb2b ticketstages.sql
|
|
15
|
+
- _underscore/Model/Client/TicketStage.php
|
|
16
|
+
- _underscore/Trait/Startech/TicketStage.php
|
|
17
|
+
- _underscore/Model/Pcmaticb2b/TicketStage.php
|
|
18
|
+
- _underscore/Trait/Startech/Ticket.php
|
|
19
|
+
- worker2/Worker/Startech.php
|
|
20
|
+
- library/app/api/toga2.php
|
|
21
|
+
- library/app/model/togadesk/ticket.php
|
|
22
|
+
- togadesk/desk/includes/functions.php
|
|
23
|
+
- togadesk/desk/includes/controllers/data/tickets/manage.php
|
|
24
|
+
- togadesk/desk/template/pages/tickets/manage.php
|
|
25
|
+
related:
|
|
26
|
+
- clients/pcmaticb2b/features/startech-ticket-sync.md
|
|
27
|
+
- 2.0/apps/worker2/features/startech-webhook-handler.md
|
|
28
|
+
- 1.0/apps/library/features/startech-pcmaticb2b-sync.md
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Summary
|
|
32
|
+
|
|
33
|
+
Startech (OptimumDesk) exposes a **different status set per ticket type** — Support Request
|
|
34
|
+
(workflow 56) has 15 statuses, Phone Call Support (workflow 59) has 10. Previously TOGA stored a
|
|
35
|
+
single flat set of 8 generic stages, so Startech statuses like Connected or Dispatched could not be
|
|
36
|
+
represented and agents could pick statuses Startech would reject.
|
|
37
|
+
|
|
38
|
+
This feature scopes ticket stages to a ticket type via a nullable `TicketStages.ticketTypeId`, and
|
|
39
|
+
marks which stages an agent may actually select via a `c_isSelectable` custom field. TOGaDesk shows
|
|
40
|
+
**every** synced status (full visibility for reporting) but only offers the selectable subset in the
|
|
41
|
+
status dropdown.
|
|
42
|
+
|
|
43
|
+
A bridge table was deliberately rejected in favour of the nullable FK to avoid duplicating stage rows.
|
|
44
|
+
|
|
45
|
+
## Key files / entry points
|
|
46
|
+
|
|
47
|
+
| Concern | File |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `ticketTypeId` column (all client DBs) | `dbchanges2/Client/2026-07-22a - TicketStageTicketTypeId.sql` |
|
|
50
|
+
| `ticketTypeId` API registration + ACL | `dbchanges2/Core/2026-07-28 - TicketStageTicketTypeIdRecordField.sql` |
|
|
51
|
+
| `c_isSelectable` column + registration | `dbchanges2/_modules/startech/2026-07-28 - TicketStageIsSelectable.sql` |
|
|
52
|
+
| Per-type stage seed | `dbchanges2/Client_Pcmaticb2b/2026-07-28 - pcmaticb2b ticketstages.sql` |
|
|
53
|
+
| Model fields | `_underscore/Model/Client/TicketStage.php`, `_underscore/Trait/Startech/TicketStage.php` |
|
|
54
|
+
| Inbound webhook lookups | `worker2/Worker/Startech.php` (`_Worker_Startech::Webhook`) |
|
|
55
|
+
| Outbound to Startech | `_underscore/Trait/Startech/Ticket.php` (`postPost` / `postPut`) |
|
|
56
|
+
| 1.0 ↔ 2.0 sync mapping | `library/app/api/toga2.php` |
|
|
57
|
+
| Desk dropdown + badges | `togadesk/desk/includes/controllers/data/tickets/manage.php`, `.../includes/functions.php` |
|
|
58
|
+
|
|
59
|
+
## Data model
|
|
60
|
+
|
|
61
|
+
`TicketStages` gained two fields:
|
|
62
|
+
|
|
63
|
+
- **`ticketTypeId`** — nullable `INT UNSIGNED` FK to `Client.TicketTypes`, `AFTER ticketStatusId`.
|
|
64
|
+
NULL = applies to all types. Registered as `Core.RecordFields` **id 2479**
|
|
65
|
+
(`type = 'NUMBER'`, `precision = 0`, **`isIdentifier = 1`**) so it can be filtered over the V2 API
|
|
66
|
+
and participate in MATCH keys. Record 88 (`Ticket stages`) is `aclDatabase = CLIENT`, so its native
|
|
67
|
+
fields need a grant in **both** `Core.AclFieldPermissions` (roleId 3) **and** each client's
|
|
68
|
+
`AclFieldPermissions` (roleId 1) — mirroring the sibling `ticketStatusId`.
|
|
69
|
+
- **`c_isSelectable`** — nullable `TINYINT UNSIGNED` custom field (`1` = agent-selectable,
|
|
70
|
+
`0` = display-only). Registered in the client's `CustomRecordFields` (recordId 88, `BOOLEAN`) with
|
|
71
|
+
`AclCustomFieldPermissions` for roles 1 and 3. Declared on `_Trait_Startech_TicketStage` alongside
|
|
72
|
+
`c_startechStageId`, so every Startech client's TicketStage model gets both by `use`-ing the trait.
|
|
73
|
+
|
|
74
|
+
### Seeded stages (PC Matic B2B) — 35 rows across 4 types
|
|
75
|
+
|
|
76
|
+
| Dept | TicketTypes.id | Type | Startech type / workflow | Stage ids | Count |
|
|
77
|
+
|---|---|---|---|---|---|
|
|
78
|
+
| 336 | 2 | Support Request | 50 / 56 | 1-15 | 15 |
|
|
79
|
+
| 337 | 1 | Phone Call Support | 54 / 59 | 16-25 | 10 |
|
|
80
|
+
| 338 | 3 | API | 54 | 26-30 | 5 |
|
|
81
|
+
| 338 | 4 | Togadesk PCS | 54 | 31-35 | 5 |
|
|
82
|
+
|
|
83
|
+
Selectable in **every** set: New Ticket, In Progress, Resolved, Reopened, Closed. Everything else
|
|
84
|
+
(Connected 76, Transferred 141, User Offline 155, Cancelled 177, Remote Control 178, Customer
|
|
85
|
+
Confirmation 179, Lost 180, Dispatched 181, Automate Check 183, Live 186, Rework 74) is display-only.
|
|
86
|
+
|
|
87
|
+
API and Togadesk PCS get **only the 5 mutual TOGaDesk/Startech statuses** — those tickets originate on
|
|
88
|
+
the TOGA/Desk side, so Startech automation statuses would never legitimately apply. Awaiting User
|
|
89
|
+
(210), Open (72) and On Hold (209) were dropped: they appear in neither workflow.
|
|
90
|
+
|
|
91
|
+
## How it works
|
|
92
|
+
|
|
93
|
+
### Desk visibility + selectability (1.0)
|
|
94
|
+
|
|
95
|
+
1. `manage.php` calls `App_Api_Toga2::getStartechSelectableTicketStageNames($clientId, $departmentId)`.
|
|
96
|
+
2. The resolver maps the TOGaDesk client id to a TOGA 2.0 client id via
|
|
97
|
+
`App_Api_Toga2::STARTECH_TOGADESK_CLIENTS` (also the "is this a Startech client?" test), resolves
|
|
98
|
+
the 2.0 schema **by name** from `App_Model_Client::getClientDatabaseNames()`, and queries
|
|
99
|
+
`TicketStages ⋈ TicketTypes WHERE c_togadeskTicketDepartmentId = ? AND c_isSelectable = 1`.
|
|
100
|
+
3. Non-Startech clients get `[]` back with **no DB hit** and fall through to the default dropdown.
|
|
101
|
+
4. Display is unrestricted: `ticketStatusBadge()` renders any status. Its third parameter
|
|
102
|
+
(`$showStartechStatusColors`) enables dark inline hex + white text, passed only from the pcmatic
|
|
103
|
+
ticket-details templates — so lists/dashboard/search show the same statuses in neutral gray.
|
|
104
|
+
|
|
105
|
+
### Inbound Startech webhook (2.0)
|
|
106
|
+
|
|
107
|
+
`_Worker_Startech::Webhook` resolves type and stage **explicitly** instead of relying on child MATCH:
|
|
108
|
+
|
|
109
|
+
1. `GET /ticket-types` filtered on `c_startechTicketTypeId` **+** `code` (via
|
|
110
|
+
`TOGA_TICKET_TYPE_CODE_BY_STARTECH_TICKET_TYPE_ID`: `50 => SUPPORT REQUEST`, `54 => PCS`).
|
|
111
|
+
2. `GET /ticket-stages` filtered on `c_startechStageId` **+** the resolved `ticketTypeId`.
|
|
112
|
+
3. Both passed as `['uuid' => …]`. If the type cannot be resolved the stage is **omitted** rather than
|
|
113
|
+
risking another type's stage; the type itself falls back to the old `c_startechTicketTypeId` match.
|
|
114
|
+
|
|
115
|
+
### 1.0 ↔ 2.0 sync (`library/app/api/toga2.php`)
|
|
116
|
+
|
|
117
|
+
- **2.0 → 1.0:** use `ticketStage->name` directly. 2.0 stage names match TOGaDesk status strings for
|
|
118
|
+
every type, and the name is inherently type-correct because the stage belongs to the ticket's type.
|
|
119
|
+
- **1.0 → 2.0:** `getStartechTicketStageUuid($ticketTypeName, $ticketStageName)` resolves the type by
|
|
120
|
+
name then the stage by name within that type, and the payload sends `ticketStage.uuid`. Results are
|
|
121
|
+
cached per run in `$_startechTicketStageUuids`. Applied at both write sites (ticket create and
|
|
122
|
+
note/reply status change). Non-pcmatic clients keep the existing `status → code` map untouched.
|
|
123
|
+
|
|
124
|
+
### Outbound to OptimumDesk (interceptor)
|
|
125
|
+
|
|
126
|
+
`_Trait_Startech_Ticket::getStartechTicketStatusForTicketType($payload)` compares
|
|
127
|
+
`ticketStage->ticketTypeId` with `ticketType->id` and returns `0` on mismatch, so the status is omitted
|
|
128
|
+
instead of sent. Startech validates a status against the type's workflow and returns **400** for a
|
|
129
|
+
status that workflow lacks (e.g. Transferred on a Phone Call Support ticket). Used by both `postPost`
|
|
130
|
+
(create) and `postPut` (update).
|
|
131
|
+
|
|
132
|
+
## Client variations
|
|
133
|
+
|
|
134
|
+
Only PC Matic B2B is seeded today. Selectability generalises to any Startech client by adding one row
|
|
135
|
+
to `STARTECH_TOGADESK_CLIENTS`; the **coloured** badges are pcmatic-only by product decision
|
|
136
|
+
(`TOGADESK_CLIENT_ID__PCMATICB2B`).
|
|
137
|
+
|
|
138
|
+
## Gotchas / known issues
|
|
139
|
+
|
|
140
|
+
- **Deploy order matters.** Migrations must run before the library/worker2/_underscore deploy:
|
|
141
|
+
`Client/2026-07-22a` → `Core/2026-07-28` → `_modules/startech/2026-07-28` → pcmatic seed. Until
|
|
142
|
+
`ticketTypeId` is registered and stages are seeded, the new lookups resolve nothing and the code
|
|
143
|
+
falls back to the old paths.
|
|
144
|
+
- **The old `ticketStage->code` switch was a live mis-mapping.** Codes used to mean statuses (1-8);
|
|
145
|
+
after the seed they are row ids (1-35), so code 4 (`Reopened`) would have set the ticket to
|
|
146
|
+
`Closed`. Never map a pcmatic stage by code — use the name or the uuid.
|
|
147
|
+
- **`c_startechStageId` is no longer unique.** New = `1` for all four types. Always scope a stage
|
|
148
|
+
lookup by `ticketTypeId`.
|
|
149
|
+
- **Startech type `54` maps to three TOGA types** (1 PCS, 3 API, 4 Togadesk PCS), so
|
|
150
|
+
`c_startechTicketTypeId` alone cannot identify a type — pair it with `code`.
|
|
151
|
+
- **Interceptors receive `$outData`**, the resolved response record with expanded relations (depth
|
|
152
|
+
bumped to the interceptor's `minDepth`) — not the raw request. That is why sending
|
|
153
|
+
`ticketStage.uuid` still yields a populated `c_startechStageId` / `ticketTypeId` downstream.
|
|
154
|
+
- **`c_isSelectable` is registered with `isIdentifier = 0` — deliberately.** `isIdentifier = 1` would
|
|
155
|
+
put a mutable flag in the child-match lookup key (`api2/Component/Api/V2/V2.php:7053-7128` searches
|
|
156
|
+
only on identifier fields), so flipping a stage's selectability would stop a MATCH payload finding the
|
|
157
|
+
row and the API would insert a **duplicate stage** instead of updating. `ticketTypeId` by contrast
|
|
158
|
+
*must* be `isIdentifier = 1` — it is what makes `{c_startechStageId, ticketTypeId}` resolve to the one
|
|
159
|
+
correct per-type stage. Note: when a record has **no** identifier fields at all, the API falls back to
|
|
160
|
+
treating every field as an identifier.
|
|
161
|
+
- **The seed opens with `DELETE FROM TicketStages` / `TicketStatuses`.** Clean for a fresh onboard, but
|
|
162
|
+
it will fail or orphan rows if live tickets already reference the old stage ids — a migrate-in-place
|
|
163
|
+
is needed for an already-populated schema.
|
|
164
|
+
- **`c_isSelectable` exists only in 2.0.** TOGaDesk 1.0 has no statuses table (`tickets.status` is a
|
|
165
|
+
free string), so 1.0 reads the flag live from the client's 2.0 schema on each ticket-details render.
|
|
166
|
+
Flipping it in 2.0 changes the dropdown immediately, with no sync step.
|
|
167
|
+
- **Dept 338 maps to two types** (3 and 4) with identical status names; the resolver's
|
|
168
|
+
`GROUP BY ticketStages.name` collapses them to one clean list. That ambiguity still matters for any
|
|
169
|
+
department → type lookup elsewhere.
|
|
170
|
+
|
|
171
|
+
## Related docs
|
|
172
|
+
|
|
173
|
+
- `clients/pcmaticb2b/features/startech-ticket-sync.md`
|
|
174
|
+
- `2.0/apps/worker2/features/startech-webhook-handler.md`
|
|
175
|
+
- `1.0/apps/library/features/startech-pcmaticb2b-sync.md`
|
|
176
|
+
|
|
177
|
+
## Change history
|
|
178
|
+
|
|
179
|
+
- 2026-07-29: Initial doc — delivers the per-ticket-type statuses enhancement flagged as pending on
|
|
180
|
+
2026-07-10 in `startech-ticket-sync.md`. Covers `TicketStages.ticketTypeId` (Core.RecordFields 2479,
|
|
181
|
+
`isIdentifier = 1`) and the `c_isSelectable` custom field, the 35-stage seed across 4 ticket types,
|
|
182
|
+
TOGaDesk visibility vs selectability (`getStartechSelectableTicketStageNames()`, badge colour scoping),
|
|
183
|
+
explicit type + stage lookups in the worker2 webhook, per-type stage mapping in both sync directions,
|
|
184
|
+
and the outbound interceptor's ticket-type/stage consistency guard. Records two bugs fixed (the
|
|
185
|
+
2.0 → 1.0 `ticketStage->code` mis-mapping; non-unique `c_startechStageId` and Startech type 54) and
|
|
186
|
+
the migration-before-deploy ordering.
|
|
@@ -5,7 +5,7 @@ project: TOGa
|
|
|
5
5
|
client: pcmaticb2b
|
|
6
6
|
type: client-feature
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-07-
|
|
8
|
+
updated: 2026-07-29
|
|
9
9
|
owners: [snaredla]
|
|
10
10
|
files:
|
|
11
11
|
- worker/crons/toga2/startech/sync_togadesk_startech_pcmaticb2b.php
|
|
@@ -19,6 +19,7 @@ files:
|
|
|
19
19
|
- _underscore/Trait/Startech/Ticket.php
|
|
20
20
|
- dbchanges2/Client_Pcmaticb2b/2026-06-22-pcmaticb2b-enhancements.sql
|
|
21
21
|
related:
|
|
22
|
+
- clients/pcmaticb2b/features/startech-per-ticket-type-stages.md
|
|
22
23
|
- clients/pcmaticb2b/profile.md
|
|
23
24
|
- 2.0/apps/worker2/features/startech-webhook-handler.md
|
|
24
25
|
- 1.0/apps/library/features/startech-pcmaticb2b-sync.md
|
|
@@ -228,3 +229,13 @@ not yet resolved:
|
|
|
228
229
|
- 2026-07-06: Added sync prerequisites/gotchas (entitlement gate, staff-email collision) and clarified the escalate button's actual behavior (desk changes always; 2.0 PUT only when linked)
|
|
229
230
|
- 2026-07-09: Corrected Startech ticket-type IDs (50 Support Request, 54 Phone Call Support for company 24412); added Sync Risks (escalate flag never reset, webhook 1→0 silencing, interceptor DB-gating) and Verification (OUT-log + E2E test); cross-linked entitlement provisioning.
|
|
230
231
|
- 2026-07-10: Added Ticket Statuses & Workflows section — workflow IDs 56 (Support Request) / 59 (Phone Call Support), statuses queried by workflow_id (not type id); TicketStages.c_startechStageId check (Awaiting User 210 / On Hold 209 disabled, Open 72 absent → invalid); flagged per-ticket-type statuses as a pending enhancement (sync + TOGaDesk).
|
|
232
|
+
- 2026-07-29: Per-ticket-type stages delivered (the 2026-07-10 pending enhancement) — see
|
|
233
|
+
`clients/pcmaticb2b/features/startech-per-ticket-type-stages.md`. Sync-relevant changes:
|
|
234
|
+
**2.0 → 1.0** no longer switches on `ticketStage->code` (codes became per-type row ids 1-35, so the
|
|
235
|
+
old 1-8 switch mis-mapped — code 4 `Reopened` set the ticket to `Closed`); it now uses
|
|
236
|
+
`ticketStage->name`, which is type-correct by construction. **1.0 → 2.0** no longer uses the shared
|
|
237
|
+
`status → code` map for pcmatic (it would attach another type's stage); `App_Api_Toga2::getStartechTicketStageUuid($ticketTypeName, $ticketStageName)`
|
|
238
|
+
resolves the stage within the ticket's type and the payload sends `ticketStage.uuid` (applied to both
|
|
239
|
+
ticket create and note/reply status changes, cached per run). The outbound interceptor now drops a
|
|
240
|
+
status whose stage belongs to a different ticket type, since Startech 400s on a status outside the
|
|
241
|
+
type's workflow.
|
package/knowledge/registry.json
CHANGED
package/package.json
CHANGED