Checklists and SOPs Developer Guide - edmozley/freeitsm GitHub Wiki
Checklists & SOPs โ developer guide
How FreeITSM stores reusable Standard Operating Procedures and attaches them to tickets. Shipped in 2.0.0, proposed in #138 and contributed as PR #141 by Santhosh Srinivasan (Sandy) โ FreeITSM's first community-contributed module.
For the changes made during integration, and why, see Checklists module โ house style. The administrator-facing page is not written yet.
1. The shape of it, in one idea
A knowledge article is something you read. A task is something scheduled and delegated. An SOP is neither: it is a list of things that must be ticked off on this ticket, by whoever is holding it, before it can finish.
That gap is the whole justification for the module, and it was argued out in the proposal before a line was written. Worth reading #138 for it:
- Knowledge is passive. You cannot tick a paragraph, and nothing records who read it.
- Tasks are for distinct, scheduled, delegated work. Ten sub-tasks for a routine ten-step onboarding floods the global Tasks board, generates its own calendar entries and notifications, and makes the analyst context-switch out of the ticket to tick a box.
So: a lightweight checklist that lives inside the ticket, with per-step attribution and a completion gate.
2. The data model
Six tables. Two are lookup lists, two are the reusable template, two are the copy attached to a ticket.
checklist_categories id, name โ grouping for templates
checklist_roles id, name โ "who usually does this"
checklist_templates id, title, category, scope, keywords, description, โฆ
checklist_template_items id, template_id, title, suggested_role,
is_mandatory, requires_input, input_placeholder, sort_order
ticket_checklists id, ticket_id, template_id, title
ticket_checklist_items id, ticket_checklist_id, title, suggested_role,
is_mandatory, is_completed, completed_by_id,
completed_by_name, completed_datetime,
requires_input, input_placeholder, response_value, sort_order
๐ The template/instance split is the best decision in the module
ticket_checklist_items copies the step's text. It does not point at checklist_template_items.
That looks like duplication and it is exactly right. A ticket's checklist is a record of what somebody was asked to do at the time. If the rows pointed at the template, then editing the template next year would silently rewrite the history of every ticket that ever used it โ and an SOP checklist exists precisely so that history can be trusted.
The same reasoning is why completed_by_name is stored alongside completed_by_id: the analyst's account may be renamed or removed, and the record should still say who ticked the box.
โ ๏ธ Do not "normalise" this later. It will look like a redundancy to somebody eventually. It is a snapshot, and snapshots are supposed to diverge from their source.
scope
ticket | task | both. Only the ticket half is implemented; the task half is declared and unused, which is honest โ see What is not built yet.
โ ๏ธ No foreign keys on a grown install
database/freeitsm.sql declares three foreign keys with ON DELETE CASCADE. Database Verification does not create foreign keys at all, so an installation that grew through upgrades has none of them.
Both are supported configurations, so nothing may rely on a cascade. Every delete path removes its children explicitly, inside a transaction:
$conn->beginTransaction();
$conn->prepare("DELETE FROM ticket_checklist_items WHERE ticket_checklist_id = ?")->execute([$chkId]);
$conn->prepare("DELETE FROM ticket_checklists WHERE id = ?")->execute([$chkId]);
$conn->commit();
This is a general rule in FreeITSM, not a checklists one: Database Integrity.
3. Attaching a checklist to a ticket
Three routes, all landing in the same place:
| Route | Where |
|---|---|
| The analyst picks one | the SOP panel in the ticket reading pane |
| Suggested by relevance | suggest_template scores templates against the ticket's subject and first five notes |
| A workflow action | attach_checklist, dispatched by the workflow engine |
The relevance scoring
api/tickets/ticket_checklists.php, case "suggest_template". Deliberately simple and readable rather than clever:
- template title appearing in the ticket text: +5
- each matching keyword: +2
- anything scoring below 2 is not offered at all
The score is then turned into a confidence percentage for display. There is no index and no full-text search here โ the template library is tens of rows, not thousands, and a LIKE sweep over it costs nothing. If a site ever has thousands of SOPs this is the first thing to revisit.
4. Completion, attribution and the audit trail
Ticking a step (toggle_item) writes four things: the flag, the analyst id, the analyst's name as it is now, and the timestamp.
UPDATE ticket_checklist_items
SET is_completed = 1, response_value = ?, completed_by_id = ?,
completed_by_name = ?, completed_datetime = UTC_TIMESTAMP()
WHERE id = ?
๐ด
UTC_TIMESTAMP(), neverNOW()and never PHP'sdate(). FreeITSM stores UTC at rest (#126) and renders in the viewer's zone. PHP and MySQL do not necessarily agree on the local zone โ on the reference dev box PHP runsEurope/Londonwhile MySQL runs UTC, sodate('Y-m-d H:i:s')stored timestamps an hour in the future during BST. See Date and time formats.
Steps that ask for a value. requires_input makes the tick prompt for something โ an asset tag, a serial number, a note โ stored in response_value. This is the feature that turns a checklist into a record: "laptop issued" is an assertion, "laptop issued, asset tag A-4471" is evidence.
5. The closure rule
A ticket can be closed with mandatory steps outstanding. The override is recorded.
Where this lives matters more than what it does:
// includes/services/checklists.php
ChecklistsService::assertClosureAllowed($conn, $ticketId, $tenantId); // may throw
ChecklistsService::recordClosureOverride($conn, $ctx, $ticketId); // writes the note
Both are called from TicketsService::updateTicket(), which is the choke point for the inbox, bulk actions, assign/schedule, and the v1 REST API. Putting the rule in the browser would mean the API ignored it, and a compliance control the API ignores is not a control.
๐ด And from
WorkflowEngine::action_set_ticket_status(), which writes the status itself and does not go throughupdateTicket(). Until the mandatory fields work, this gate did not apply to workflows at all, although the help card said it did. A workflow acts asActorContext(0, null, 'workflow').ticket_notes.analyst_idcannot hold 0 (it has a foreign key), so a workflow close writes its override as aWorkflow Noteinticket_audit, the same place the engine's own notes go.
Tickets โ Settings โ Checklists chooses the behaviour:
| Setting | Behaviour |
|---|---|
| Warn, and record it (default) | the close proceeds; an internal note names the skipped steps, who closed it and through which interface |
| Refuse the close | ServiceError('validation', 'mandatory_steps_outstanding', โฆ) and the ticket does not move |
๐ด The refusal is checked BEFORE the status is written. Checked afterwards, a
blockinstall would throw having already closed the ticket: the caller sees an error while the database disagrees with it. The check sits in the status-resolution branch ofupdateTicket, ahead of theUPDATE.
Why warn is the default: assets/js/inbox.js already carried the house position, written for #83 about unfinished tasks โ
"closing a ticket that still has unfinished tasks WARNS, it never blocks โฆ a warning that cannot be shown must not become a block that cannot be cleared."
A hard block traps any ticket whose remaining step has become impossible: the person who owned it has left, the step is obsolete, and there is no way out but SQL. Recording the exception is also the better compliance answer โ an auditor does not want a system where the bad outcome was impossible, they want one where it is attributable.
Either way the note is written. The setting decides whether it is allowed, never whether it is logged.
6. Where the code lives
checklists/index.php the template library (cards, filters, search)
checklists/edit/index.php the template editor โ a SCREEN, not a modal
checklists/settings/index.php categories, suggested roles, left-panel preference
checklists/api.php template get / save / delete
checklists/ticket_view.js the SOP panel inside a ticket
api/checklists/*.php category + role CRUD, over the service
api/tickets/ticket_checklists.php attach, suggest, toggle, remove
includes/services/checklists.php the closure rule and the lookup rules
database/demo-data/checklists.json five worked SOPs for evaluation
The editor is a screen because a form editor is
forms/edit/ is a full screen for the same shape of problem: a parent with an unbounded list of children, each child having attributes of its own. A 680px modal at 88vh is a poor place to drag a fifteen-step procedure into order โ the drop target is regularly off-screen, and a scrolling modal body will not auto-scroll during a drag without a lot of bespoke work. A screen also gets a URL, so ?id=N is linkable and the browser's back button behaves.
The drag-and-drop is forms' implementation, copied deliberately โ same handle-gated dragstart, same midpoint drop calculation, same .dragging / .drag-over-top / .drag-over-bottom class names.
โ ๏ธ The handle gate is the part that is easy to leave out. Without it the whole row is
draggable, and click-dragging to select text inside a step's title starts a reorder instead:let dragAllowed = false; document.addEventListener('mousedown', e => { dragAllowed = !!e.target.closest('.step-drag'); }); function onStepDragStart(e, i) { if (!dragAllowed) { e.preventDefault(); return; } โฆ }
7. What is not built yet
Declared honestly in the proposal and still true:
- the Tasks half of
scopeโ the column acceptstaskandboth; nothing consumes it - per-step approval by a watcher or manager
- enforced sequential dependencies (the panel shows the next step but does not prevent ticking a later one)
- per-step routing, sub-SLAs, branching checklists, CMDB links at step level
- version history for templates
8. Related
- Checklists module โ house style โ what changed during integration and why
- โฌ An administrator-facing page for this module is still to be written
- Service layer ยท Database Integrity ยท Database Verification
- Workflows โ the
attach_checklistaction