You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I closed I26-0054 and couldn't get its after-action report to come up. The incident is
fine — status = 1, deleted_at NULL. The problem is which number the form wants.
The Incident # field takes the internal ticket.id, not ticket.incident_number:
// assets/js/reports.js:209
var iid = parseInt(incidentFilter.value, 10) || 0;
<!-- reports.php:173 --><labelclass="form-label small mb-1">Incident #</label><inputtype="number" ...id="incidentFilter" placeholder="Incident ID" min="1">
Everywhere else in the app that incident is I26-0054.
Why this is more than a confusing label
incident_number is not a decorated ticket.id. It comes from its own templated,
period-resetting sequence (inc/incident-number.php), and on a default install:
reset_mode defaults to yearly. At the turn of the year the sequence resets to 1 and
numbering restarts at I27-0001, while ticket.id is AUTO_INCREMENT and keeps climbing.
The two sequences are structurally unrelated and are guaranteed to diverge on every
install, every January.
That makes the first weeks of a new year the dangerous window. I27-0001 through I27-0030 are small integers that will match real, long-closed ticket.id values on
any install more than a year old. So an operator asking for the after-action report on
this morning's incident gets a complete, plausible report for an unrelated incident from
a previous year.
The number the operator has is the one number the report never shows
To be fair to the report: it does render scope, location, description, problem
start/end and unit counts, so someone who worked the call would notice a mismatch quickly.
This is not a silent-wrong-answer bug and I don't want to overstate it.
The gap is narrower and more practical. The report is headed "Incident #53" — the
internal id — and incident_number appears nowhere on it. So an operator has no way to
confirm they pulled I26-0052 rather than something adjacent, short of recognising the
location and description. That is fine for this morning's call and no help at all for a
records request six months later on an incident nobody present worked.
The template is admin-configurable, which changes the failure
With the default I{YY}-{NNNN}, entering I26-0054 gives parseInt → NaN → 0 → the
API's incident_id is required error. Annoying, but at least it fails.
Change the template to drop the leading letter — e.g. {YYYY}-{NNNN} → 2026-0054 — and parseInt returns 2026, silently requesting ticket.id = 2026. On a busy install
that is a real incident. The failure mode depends on a formatting setting, which seems
like a thing worth not depending on.
Reproduce
Without waiting for January, the same mismatch shows up wherever the two sequences differ.
On this install I26-0053 is ticket.id 54, and I26-0052 is 53:
Reports → After Action Report → Incident # → 53 → Run
Enter I26-0053 as the app displays it → incident_id is required for after_action report, for a field that was filled in
Confirmed in the browser, not just from the code path.
Environment
4.2.14, Windows 11 / IIS 10, PHP 8.4.22, MySQL 8
reports.php, assets/js/reports.js, api/reports.php all verified identical to origin/main — not a local modification
incident_number is well established elsewhere upstream (api/incident-create.php, api/callboard.php, api/audit-log.php, api/external/v1/incidents.php), so the
after-action path looks like the outlier
Note
Not proposing a fix — accepting incident_number in the field, resolving it server-side,
or an incident picker instead of free-text all trade off differently, and it's your call.
The bit I'd weight regardless of the input decision: the report output should name the
incident the way the operator knows it. Even leaving the field exactly as it is,
printing I26-0052 in the header would let anyone confirm at a glance that they pulled
the report they meant to — including someone reading it long after the call, who has no
memory of the location to check it against.
I closed
I26-0054and couldn't get its after-action report to come up. The incident isfine —
status = 1,deleted_atNULL. The problem is which number the form wants.The Incident # field takes the internal
ticket.id, notticket.incident_number:Everywhere else in the app that incident is
I26-0054.Why this is more than a confusing label
incident_numberis not a decoratedticket.id. It comes from its own templated,period-resetting sequence (
inc/incident-number.php), and on a default install:reset_modedefaults to yearly. At the turn of the year the sequence resets to 1 andnumbering restarts at
I27-0001, whileticket.idis AUTO_INCREMENT and keeps climbing.The two sequences are structurally unrelated and are guaranteed to diverge on every
install, every January.
That makes the first weeks of a new year the dangerous window.
I27-0001throughI27-0030are small integers that will match real, long-closedticket.idvalues onany install more than a year old. So an operator asking for the after-action report on
this morning's incident gets a complete, plausible report for an unrelated incident from
a previous year.
The number the operator has is the one number the report never shows
after_actionnever returnsincident_number:To be fair to the report: it does render
scope,location,description, problemstart/end and unit counts, so someone who worked the call would notice a mismatch quickly.
This is not a silent-wrong-answer bug and I don't want to overstate it.
The gap is narrower and more practical. The report is headed "Incident #53" — the
internal id — and
incident_numberappears nowhere on it. So an operator has no way toconfirm they pulled
I26-0052rather than something adjacent, short of recognising thelocation and description. That is fine for this morning's call and no help at all for a
records request six months later on an incident nobody present worked.
The template is admin-configurable, which changes the failure
With the default
I{YY}-{NNNN}, enteringI26-0054givesparseInt→NaN→0→ theAPI's
incident_id is requirederror. Annoying, but at least it fails.Change the template to drop the leading letter — e.g.
{YYYY}-{NNNN}→2026-0054— andparseIntreturns 2026, silently requestingticket.id = 2026. On a busy installthat is a real incident. The failure mode depends on a formatting setting, which seems
like a thing worth not depending on.
Reproduce
Without waiting for January, the same mismatch shows up wherever the two sequences differ.
On this install
I26-0053isticket.id54, andI26-0052is 53:53→ Rundescription — a complete, correct report for a different incident than the number
entered
I26-0053as the app displays it →incident_id is required for after_action report, for a field that was filled inConfirmed in the browser, not just from the code path.
Environment
reports.php,assets/js/reports.js,api/reports.phpall verified identical toorigin/main— not a local modificationincident_numberis well established elsewhere upstream (api/incident-create.php,api/callboard.php,api/audit-log.php,api/external/v1/incidents.php), so theafter-action path looks like the outlier
Note
Not proposing a fix — accepting
incident_numberin the field, resolving it server-side,or an incident picker instead of free-text all trade off differently, and it's your call.
The bit I'd weight regardless of the input decision: the report output should name the
incident the way the operator knows it. Even leaving the field exactly as it is,
printing
I26-0052in the header would let anyone confirm at a glance that they pulledthe report they meant to — including someone reading it long after the call, who has no
memory of the location to check it against.