Skip to content

After Action Report takes ticket.id, not incident_number — no way to pull a report by the number the app assigns #57

Description

@rjonesbsink

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 -->
<label class="form-label small mb-1">Incident #</label>
<input type="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:

incident_number_template    = I{YY}-{NNNN}
incident_number_reset_mode  = yearly
incident_number_period      = 2026

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

after_action never returns incident_number:

$summary = [ 'incident_id' => $incident_id, ... ];
$period_label = 'Incident #' . $incident_id;

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 parseIntNaN0 → 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:

  1. Reports → After Action Report → Incident # → 53 → Run
  2. The panel is headed "Incident [Question]: BackError failed: SQLSTATE[42S02]: Base table or view not found: 1932 Table 'ticketscad_v4.message_routes' doesn't exist in engine #53" and shows I26-0052's location and
    description — a complete, correct report for a different incident than the number
    entered
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions