Repository navigation
Starters Steps As Tickets Developer Guide
How a Starters & leavers template step can be raised as a ticket instead of a task, several steps can share one ticket, and the ticket stays tied to its case. Built for #316 (5.2.0), from discussion #298. User docs: Starters & leavers β Steps as tickets. The proposal it came from: design. The module itself: Starters & leavers - Developer Guide.
The one-line version: applyTemplate() sets aside the steps marked ticket, groups them by ticket name, and raiseTicket() makes one ticket per group through TicketsService::createTicket() - then links it in jml_case_tickets, which every progress count, the case page, the ticket's Links strip and the requester-email switch read.
The design was written before the code was read closely. Four things changed, each Ed's decision (2026-11-13):
| Design said | Built | Why |
|---|---|---|
| Requester: the form's submitter, or nobody | the submitter, else the case's manager, else the person; none β the steps become tasks | Every ticket must have a requester (createTicket() refuses one without). A ticket with no requester would touch requester emails, CSAT, the portal and reports across Tickets. |
| Due: the earliest due date | the ticket is scheduled (all day) on it, and the subject says the date | Tickets have no due date. The schedule puts it on the Calendar. |
| No requester emails | a template setting, off by default, kept per ticket | Some organisations do want the manager told. |
| Done: offer to close the open tickets | Done never closes a ticket | The same rule as tasks: Done never closes anybody's work. |
The case's own shape (one task with a checklist, or task + subtasks) was left as it is.
| File | π¨ | What changed |
|---|---|---|
database/freeitsm.sql, includes/db_verify_schema.php, includes/db_verify_indexes.php, api/system/db_verify.php
|
ποΈ | the columns, jml_case_tickets, three foreign keys |
includes/starters/service.php |
βοΈ |
saveTemplate(), applyTemplate() β addTaskStep() + raiseTicket(), ticketRequester(), teamDepartment(), ticketsReady(), progressSql(), caseTickets(), getCase(), listCases(), casesForPerson()
|
includes/services/checklists.php |
βοΈ |
addTicketChecklist() - a checklist from a list of steps, not a template |
includes/ticket_requester_mute.php |
βοΈ |
new: ticketRequesterEmailsMuted()
|
includes/template_email.php, includes/csat.php
|
βοΈ | ask it before a requester email, and before an automatic survey |
includes/report_packs/blocks_starters.php |
π | progress, ready on the day and overdue count tickets |
api/starters/lookups.php, api/starters/get.php
|
π | ticket types; each ticket's link |
api/tickets/get_email_detail.php, assets/js/inbox.js
|
ππ₯οΈ | the ticket's π§³ case pill |
api/tickets/permanently_delete_ticket.php, empty_trash.php, api/system/debug-tools/D002_delete_ticket.php, includes/ticket_merge.php
|
ποΈ | remove the link; a merge leaves it with the merged ticket |
starters/settings/index.php, assets/js/starters-settings.js, assets/css/starters.css
|
π₯οΈ | Raise as / Ticket, the ticket settings, the team warning |
starters/view.php, assets/js/starters-view.js
|
π₯οΈ | the Tickets panel |
lang/en/starters.php, lang/en/tickets.php, starters/help.php
|
π | strings and the help section |
includes/feature_bingo/cards/starters.php |
π― | starters.step_as_ticket |
tests/starters-tickets.php |
π§ͺ | new: 28 checks |
ALTER TABLE jml_templates
ADD ticket_type_id INT NULL, -- FK ticket_types, ON DELETE SET NULL
ADD email_requester TINYINT(1) NOT NULL DEFAULT 0;
ALTER TABLE jml_template_steps
ADD raise_as VARCHAR(10) NOT NULL DEFAULT 'task', -- task | ticket
ADD ticket_name VARCHAR(100) NULL; -- same name = one ticket
CREATE TABLE jml_case_tickets (
id INT NOT NULL AUTO_INCREMENT,
case_id INT NOT NULL, -- FK jml_cases, CASCADE
ticket_id INT NOT NULL, -- FK tickets, CASCADE
ticket_name VARCHAR(100) NULL,
mute_requester TINYINT(1) NOT NULL DEFAULT 1, -- the template's switch WHEN RAISED
created_datetime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_jml_case_ticket (case_id, ticket_id),
KEY idx_jml_case_tickets_ticket (ticket_id)
);raise_as defaults to task, so no existing template changes by itself. Before Database Verification none of it is there, and StartersService::ticketsReady() (cached per request) keeps everything working the old way: the editor disables Raise as with a hint, saveTemplate() writes only the old columns, applyTemplate() makes every step a task, and progress counts tasks only.
The loop that made every step a subtask now sets ticket steps aside, keyed by their ticket name (case-insensitive - IT accounts and it accounts are one ticket) or, for a blank name, by the step's own id:
foreach (self::steps($conn, (int)$template['id']) as $step) {
if (!self::ruleMet($step['condition_rule'], $raw)) { $skipped++; continue; }
if ($canTicket && ($step['raise_as'] ?? 'task') === 'ticket') {
$key = trim((string)($step['ticket_name'] ?? '')) !== '' ? 'n:' . mb_strtolower(trim((string)$step['ticket_name'])) : 's:' . $step['id'];
$ticketGroups[$key][] = $step;
continue;
}
if (self::addTaskStep($conn, $ctx, $c, $caseId, $step, $fill, $warnings)) $added++;
}
foreach ($ticketGroups as $group) {
$added += self::raiseTicket($conn, $ctx, $c, $caseId, $template, $group, $fill, $answers, $warnings);
}Only if rules work exactly as before: a step whose rule fails never joins a group, and a group with no steps left raises no ticket. addTaskStep() is the old loop body, unchanged, pulled out so raiseTicket() can fall back to it.
private static function raiseTicket(PDO $conn, ActorContext $ctx, array $c, int $caseId, array $template,
array $group, callable $fill, array $answers, array &$warnings): intIn order:
-
The requester -
ticketRequester():// whoever asked for the case, else the manager, else the person - each only if they exist in users $ids[] = form_submissions.submitted_by_user_id; // the portal user who filled in the form $ids[] = $c['manager_user_id']; $ids[] = $c['user_id'];
None β each step of the group goes through
addTaskStep()instead,ticket_no_requesteris written to the history and a warning returned. The work is never lost. -
The due date - the earliest of the group's
effective_date + due_offset_days. -
The ticket -
TicketsService::createTicket(), the front door every ticket uses, so its rules, audit, numbering andticket.createdall apply:$in = array_filter([ 'subject' => mb_substr($subject, 0, 255), // "IT accounts: Jane Smith - due 3 Nov 2026" 'description' => implode("\n", $lines), // plain text: createTicket() escapes it 'user_id' => $requester, 'assigned_team_id' => $first['assigned_team_id'], 'assigned_analyst_id' => $first['assigned_analyst_id'], 'department_id' => self::teamDepartment($conn, $first['assigned_team_id']), 'ticket_type_id' => $template['ticket_type_id'] ?: null, ], fn($v) => $v !== null); $ticketId = TicketsService::createTicket($conn, $ctx, $tenantId, $in, null, 'Raised by Starters & leavers case #' . $caseId);
teamDepartment()returns the team's department only when it has exactly one indepartment_teams; otherwise null, andcreateTicket()applies the company's default department, as for any ticket. -
The link, at once - from here on everything the ticket does knows it belongs to a case:
INSERT INTO jml_case_tickets (case_id, ticket_id, ticket_name, mute_requester) VALUES (?, ?, ?, ?) -- mute_requester = empty($template['email_requester']) ? 1 : 0
-
The schedule -
updateTicket(['work_start_at' => "$due 00:00:00", 'work_all_day' => 1]). -
The checklist -
ChecklistsService::addTicketChecklist($conn, $ctx, $ticketId, $name, $items): one mandatory item per step (title and description filled), closure warn. Then each step's own checklist template throughattachTemplateToTicket(..., skipIfAttached: true)- a task-only checklist is refused there and becomes a warning. -
The history -
ticket_raisedwith the ticket number.
TRAP: the schedule and the checklist are not allowed to lose the ticket. Each is in its own try; a failure is a warning. Only createTicket() failing abandons the ticket - and then the case history says so, as for a task step that fails.
π No outer transaction, for the module's usual reason:
createTicket()firesticket.created, and a workflow must never act on a ticket that is then rolled back.
The requester is usually a manager who filled in a form. The template's Email the requester about these tickets decides whether they hear about the work; off by default.
// includes/ticket_requester_mute.php
function ticketRequesterEmailsMuted(PDO $conn, int $ticketId): bool
{
// SELECT 1 FROM jml_case_tickets WHERE ticket_id = ? AND mute_requester = 1 - cached; false if the table is missing
}Asked in exactly two places - the choke points every automatic requester email and survey goes through:
// includes/template_email.php - sendTemplateEmail(), after the merge data is built
if (templateEventAudience($eventTrigger) === 'requester' && ticketRequesterEmailsMuted($conn, $ticketId)) {
emailLogSkipped($conn, templateGetMailboxForTicket($conn, $ticketId), 'template', $mergeData['requester_email'] ?? '',
'Automatic email: ' . $eventTrigger,
'This ticket was raised by a Starters & leavers case whose template does not email the requester.', $ticketId);
return;
}
// includes/csat.php - csatAutoOnClose()
if (ticketRequesterEmailsMuted($conn, $ticketId)) return;- Logged, not silent - each one not sent is a skipped row in the mailbox's send log, so "why did the manager hear nothing?" has an answer.
-
analyst_assignedstill goes - its audience is the analyst, not the requester. -
Kept per ticket.
mute_requesteris copied from the template when the ticket is raised, so ticking the box later changes new cases, not tickets already out. The test checks exactly that. - By hand still works - an analyst's Reply is not a template email.
A ticket is one step: total when it is not in the trash, done when its status is closed. One definition, used by every count:
public static function progressSql(PDO $conn): string
{
$tasksTotal = "(SELECT COUNT(*) FROM tasks s WHERE s.parent_task_id = c.parent_task_id)";
...
$ticketsTotal = "(SELECT COUNT(*) FROM jml_case_tickets ct JOIN tickets tk ON tk.id = ct.ticket_id
WHERE ct.case_id = c.id AND tk.deleted_datetime IS NULL)";
$ticketsDone = "(... JOIN ticket_statuses ts ON ts.id = tk.status_id ... AND ts.is_closed = 1)";
return "($tasksTotal + $ticketsTotal) AS steps_total, ($tasksDone + $ticketsDone) AS steps_done";
}| Where | How |
|---|---|
| The case page |
getCase(): progressOf(steps + tickets not in the trash)
|
| The case list |
listCases(): progressSql()
|
| The person's page |
casesForPerson(): progressSql()
|
| Report Packs - list |
rpStartersProgressCols($conn) β progressSql()
|
| Report Packs - ready on the day | a ticket must also be closed (closed_datetime) before the day after the case's date; a case with only tickets counts |
| Report Packs - overdue count and list |
rpStartersOpenWorkSql(): open subtasks UNION ALL open tickets, a ticket's due = the day it is scheduled on |
Before this, four copies of the subtask count lived in three files. TRAP: a new kind of step means one change, in progressSql() - and the overdue union.
-
The case -
caseTickets()returns number, name, subject, team, analyst, scheduled date, status, closed, in the trash, and checklist done / total;api/starters/get.phpadds each one'surlonly for an analyst with Tickets who can open that ticket (analystCanAccessTicket- its department and company).starters-view.jsdraws a Tickets panel under the steps, in the same style. -
The ticket -
get_email_detail.phpaddsjml_case(id, kind, person, date) for an analyst who can use Starters & leavers;buildLinksSection()ininbox.jsdraws π§³ Starter: Jane Smith in the Links strip, opening the case. -
The editor - a Raise as column (select + a ticket-name box shown only for tickets, with a
<datalist>of the names already used); Steps raised as tickets under the steps (type, the requester switch);ticketBits()re-checks on every change and warns when one name's steps have different teams.
-
Permanently deleting a ticket (or emptying the trash, or the D002 debug tool) removes its
jml_case_ticketsrow - it also cascades, but an install missing that foreign key must not keep a link to nothing. -
Merging leaves the link with the merged-away ticket (
includes/ticket_merge.phplists it as STAY): it describes that ticket. The merged ticket goes to the trash, so it leaves the case's progress. -
Done changes the case only; its confirmation already counts open steps from
progress, which now includes tickets. Reopen never reopens anything.
FREEITSM_TEST_DATABASE=<throwaway db> FREEITSM_BASE_URL=http://localhost/<that install> php tests/starters-tickets.php
28 checks, with a ZZ team in exactly one ZZ department, a ZZ ticket type and a ZZ manager:
- the template - the type and switch saved; Raise as / Ticket per step, a task keeping no ticket name; an unknown type refused;
-
a case - two tickets (IT accounts from two steps named in different case, MFA alone); subject; requester = the manager; team, its department, the type; scheduled on the earliest date; a checklist of the two steps with
{name}filled; text with the case link and a step's description; the task step still a subtask; the link muted; - progress - 0 of 3, then 1 of 3 after closing a ticket - on the case, the list and Report Packs;
-
the requester - muted;
sendTemplateEmail('ticket_closed')logs skipped with the reason; another ticket not muted; - Done leaves the open ticket open;
- with the switch on, new tickets are not muted - and the first case's still is;
- nobody to raise for - no tickets, four tasks, two history lines and two warnings;
-
the endpoint -
get.phpreturns both tickets with a link.
Still passing: tests/starters.php (46), report-packs-starters.php (17), csat-delay.php (24), ticket-checklists.php (30), checklists-api.php (21), ticket-merge-undo.php (5). A headless Chrome harness on the throwaway install checked the editor (the column, the suggestions, the team warning, a task switched to a ticket), the case page (the Tickets panel, 0 of 2 steps done), the ticket's jml_case, and the π§³ pill escaping the name - with no script errors.
- Add a step to one open case (the design's Later).
- Demo data - the demo templates are all tasks; a ticket step would need the Tickets demo to own its tickets (two demo modules must never write the same table).
- The other languages - English only until the release's translation pass.
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
-
Architecture
- β³ π§Ή Root folder tidy (URL routing)
- β³ π Database Integrity (FKs & constraints)
- β³ π οΈ Database Verification β Developer Guide
- β³ π§© Service Layer (UI + API share code)
- β³ πͺ€ Code traps (the TRAP: comments)
- β³ π Commenting for the Engineering wiki
- β³ π οΈ Engineering wiki scanner - Developer Guide
- β³ π Service-Layer progress
- π€ Contributing (read before a pull request)
- π§ͺ Developer tests
- π PDF exports moved to PDFKit (#254)
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- π± Currencies
- π§ The sidebar (optional module rail)
- π§© Preference templates
- π Display order (modules, Watchtower cards) β Developer Guide
- Theming & Dark Mode
- ποΈ Recent β getting back to what you were doing
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
-
MobileβFriendly
- β³ π« Mobile: Tickets
- β³ π» Mobile: Assets
- β³ π Mobile: Calendar
- β³ π Mobile: Knowledge
- β³ π¦ Mobile: Service Status
- β³ πΌ Mobile: Watchtower
- β³ π§© Mobile: Problem Management
- β³ π Mobile: Change Management
- β³ πΏ Mobile: Software
- β³ β Mobile: Tasks
- β³ π Mobile: Forms
- β³ π Mobile: Contracts
- β³ π Mobile: Domains
- β³ π Mobile: People
- β³ π Mobile: Projects
- β³ ποΈ Mobile: Files
- β³ π Mobile: LMS
- β³ πΊοΈ Mobile: CMDB
- β³ πΊοΈ Mobile: Network Mapper
- β³ π§ Mobile: Process Mapper
- β³ βοΈ Mobile: Workflow
- β³ π₯οΈ Mobile: System
- β³ π Mobile: Reporting
- β³ π Mobile: System Wiki
- β³ π Mobile: Self-Service Portal
- β³ π§° Mobile: Techniques & Tricks
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- β³ π Switching modules off
- β³ π οΈ Module availability β Developer Guide
- β³ π Module availability β design (#217)
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- β³ π‘οΈ CSRF protection (S4) β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
-
π¦ Microsoft Entra ID
- β³ π₯ Importing people and groups
- β³ π» Intune: assets to their primary user
- β³ π’ Organisation chart
- β³ π οΈ Entra directory sync β Developer Guide
- β³ π οΈ Entra import - review (#235)
- β³ π οΈ Entra follow-ups - review (#237-#240)
- β³ π οΈ Intune primary user - review (#236)
- β³ π οΈ Intune after an Entra import - review (#334)
- β³ π οΈ Intune primary user history - review (#338)
- β³ π οΈ Intune asset types - review (#339)
- β³ π οΈ Intune scheduled syncs - review (#344)
- β³ π οΈ Microsoft 365 in Integrations - review (#352)
- β³ π οΈ Organisation chart - reviews (#242, #265)
- π CardDAV contact sync
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π Proposal: custom fields, inventory import and change evidence (#194)
- β³ π οΈ Custom fields, evidence + inventory - review (#195-#197)
- β³ π οΈ Long forms in a task's Work view - review (#198)
- β³ π οΈ Empty Procedures fold - review (#247)
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π REST API: Domains
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ π·οΈ REST API: Cost centres
- β³ π’ REST API: Companies
- β³ π REST API: Chart of accounts
- β³ π REST API: Projects
- β³ π‘οΈ API threat protection
- β³ π§βπΌ REST API: Analysts
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ Reply subject and replying to one message
- β³ π Live updates
- β³ π οΈ Watching a ticket - Developer Guide (1 of 2)
- β³ βοΈ Ticket address suggestions - Developer Guide
- β³ π οΈ CSAT delay and reminder - Developer Guide
- β³ π οΈ Ticket checklist step types - Developer Guide (1 of 2)
- β³ π οΈ Right-click Attach checklist - Developer Guide
- β³ π οΈ Checklists in the REST API - Developer Guide
- β³ π A ticket's related block and attachments - Developer Guide
- β³ π οΈ Inbox.js map β Developer Guide
- β³ π οΈ Floating windows & the file viewer β Developer Guide
- β³ π οΈ Tickets settings β Developer Guide
- β³ π Simpler ticket and task forms - design (#218)
- β³ ποΈ The ticket workspace
- β³ π Forms on tickets
- β³ π The ticket table
- β³ π Rota copy and paste β Developer Deep Dive
- β³ β Checklists & SOPs
- β³ βοΈ Mandatory fields
- β³ π§Ύ Custom ticket fields
- β³ π·οΈ Ticket categories
- β³ π§Ύ A type for every new ticket
- β³ π₯ Assigning tickets to a team, and escalation
- β³ π’ One board across every company
- β³ π Ticket dashboard β Developer Guide
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ βοΈ Emailed reports
- β³ π Attachment size limits and large emails
-
β³ Basic IMAP mailboxes
- β³ π Basic IMAP without the PHP extension (#257)
- β³ π οΈ Basic IMAP client β Developer Guide
- β³ π§ͺ Basic IMAP client β Testing and verifying
- β³ π οΈ Basic IMAP - Sent copy - Developer Guide (#270)
- β³ π οΈ Verifying a mail folder β Developer Guide
- β³ π οΈ Mailbox providers β Developer Guide
- β³ π οΈ How Gmail mail collection works β Developer Guide
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
-
β³
βοΈ Telegram channel - β³ β CSAT company scope and filters β Developer Guide
- β³ π₯ Microsoft Teams channel
- β³ π¨οΈ Mattermost channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ β Record previews
- β³ π Ticket notes: internal or shared
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ π¨ Telling the analyst a ticket is theirs
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π’ Ticket numbering
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ π Confidential tickets
- β³ π₯ Portal managers
- β³ π Who has seen a ticket
- β³ π Reading long tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ ποΈ The folder pane
- β³ π½ Just my tickets, or no closed ones
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- β³ π Case reports - a ticket and its work as one PDF
- β³ π Scheduled work in your own calendar
- Problem Management
-
Tasks
- β³ π οΈ Task board and subtasks β Developer Guide
- β³ π§βπ€βπ§ People on a task ("Involved")
- β³ π Repeating tasks
- β³ π’ Moving a task between companies
- π Projects
-
Assets
- β³ π οΈ Asset list by type - review (#340)
- β³ π’ Moving an asset between companies
- β³ π Shared asset locations
- β³ π§βπΌ Assigning assets to analysts
- β³ π Warranty and lease alerts
- β³ π Leases
- β³ π Saved table views
- β³ π¨οΈ Recording anything, and importing it
- β³ π§© Asset type layouts
- β³ π Inventory freshness
- β³ π Linked configuration item
- β³ π Connected assets
- β³ π Asset review
-
β³ π·οΈ QR asset labels
- β³ π οΈ QR asset labels β Developer Guide
- β³ π Asset label designer - design (#229)
- β³ π· Asset scanning β Developer Guide
- β³ π οΈ Asset tag numbering β Developer Guide
- β³ π οΈ Asset reconciliation β Developer Guide
- β³ π οΈ Reconciliation, tags + labels - review (#164)
- β³ π Who holds what, and handover documents
- β³ π₯οΈ The inventory agent (PowerShell)
- β³ ποΈ Proxmox VE servers
- β³ βοΈ VMware Cloud Director servers
- β³ πͺ Hyper-V and Failover Clusters
- β³ π Linking equipment to tickets
- β³ βοΈ Follow-up tasks on a ticket
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
-
Forms
- β³ π¨ The form designer β Developer Guide
- β³ π Layout & the grid β Developer Guide
- β³ ποΈ Collections β grouping submissions
- β³ π Submissions as PDFs
- β³ β‘ What happens next β a form's own actions
- β³ π οΈ Sections & conditional logic β Developer Guide
- β³ π οΈ Lookup fields β Developer Guide
- β³ π‘οΈ Catalogue request approvals
- People
- Starters & leavers
- Domains
- ποΈ Files
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
-
CMDB
- β³ π§± Designing your CMDB
- β³ π οΈ CMDB β Developer Guide
- β³ π οΈ Impact analysis β Developer Guide
- β³ π οΈ Data quality β Developer Guide
- β³ π οΈ Record references β Developer Guide
- β³ π οΈ CMDB assistant - Developer Guide
- β³ π Linked assets
- β³ π Linking an asset to its CI - design (#201)
- β³ π Discovery and redundancy-aware impact - design (#199)
- β³ π οΈ Relationship wording - review (#297)
- Network Mapper
-
Workflows
- β³ π οΈ Workflow engine β Developer Guide
- β³ π οΈ Workflows for tasks β Developer Guide
- β³ π οΈ Workflow notes on a ticket β Developer Guide
- β³ π οΈ Workflow category routing β Developer Guide
- β³ β° Time-Based Triggers
- β³ πͺ Webhooks
- β³ π¬ Webhook Message Formats
- β³ π HTTPS Certificates & CA Bundles
- β³ π οΈ SSL Verification β the Global Setting
- β³ π Workflow & Webhook Pitfalls
- Issue trackers (Jira, Azure DevOps)
- π‘ Zabbix (monitoring)
- π¬ Microsoft Teams alerts
- π 3CX (phone system)
-
System
- β³ π οΈ System cards by topic - review (#347)
- β³ π¦ Microsoft 365 (Entra ID and Intune)
- β³ π οΈ Preferences β Developer Guide
- β³ π§Ή Maintenance
- β³ βοΈ Emailed reports
- β³ π Finding people in the Analysts list
- β³ πͺ When an analyst leaves
- β³ π οΈ System β Branding β Developer Guide
- β³ π©Ί Page health (D019) β Developer Guide
- β³ β Feature Bingo
- β³ π Locations
- β³ π·οΈ Cost centres
- β³ π Chart of accounts
- β³ π· Photo Album
- β³ Demo Data
- β³ π οΈ Demo Data β Developer Guide
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ One company supporting another
- β³ π’ One board across every company
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)
- What this is
-
π Bugs resolved
- β³ π The Intune tab did not check the company
- β³ π #332 Files viewer would not open
- β³ π #333 Note attachments downloaded
- β³ πͺ #326/#327 Form webhook Full record
- β³ π #325 Form PDF download failed
- β³ π Small fixes in 5.1.0
- β³ π CMDB relationships read backwards (5.1.0)
- β³ π #301 CSAT delay was never applied
- β³ π #281 Comments imported from Jira never arrived
- β³ π #288 A change failed to open with Enforce approvals on
- β³ π Long non-Latin articles never got an embedding
- β³ π« #280 Email lookups scanned the whole table
- β³ β #266 Dragging a task told nobody
- β³ π #255 Article previews showed HTML codes
- β³ π #252 A link to a Knowledge article did not open
- β³ π #254 PDFs could not print Polish
- β³ π #250 Clicking a folder left the open article on screen
- β³ π« #260 The dashboard cog did nothing
- β³ π« #248 A department rule never matched on four routes
- β³ π A directory account could be matched to someone else
- β³ π« #244 Reply went to the assigned analyst
- β³ π Mailbox sign-in was not checked
- β³ π Four weaknesses around signing in
- β³ π A lookup answer could name any record
- β³ π« #227 A mailbox could not be deleted
- β³ π« #226 A picture on a note could not be closed
- β³ π» #222 The Assets page never loaded in 4.3.0
- β³ βοΈ #214 A "contains" condition did not match capitals
- β³ βοΈ #216 A workflow email from a chosen mailbox always failed
- β³ π³ The Docker image was missing PHP extensions
- β³ π A failed DNS lookup read as a missing record
- β³ π A link could change data
- β³ π Access outlived its removal
- β³ π Knowledge articles could run scripts
- β³ π Knowledge writes ignored permissions
- β³ π Company boundaries (Oct 2026)
- β³ π Smaller gaps (Oct 2026)
- β³ π Escaped text could leave its attribute
- β³ π Task descriptions were not cleaned
- β³ π Task documents ignored company scope
- β³ π Repeating tasks ignored company scope
- β³ π¬ #183 Mailbox checks overlapped
- β³ π #188 Transfer lock shown off when unknown
- β³ β #184 Subtask names and Overdue
- β³ π§© #186 Teams: the buttons did nothing
- β³ π Text in a change could run script
- β³ π AI settings changed without the AI permission
- β³ πͺ #178 Deleting an analyst failed
- β³ π« A team with no departments showed 0 tickets
- β³ π Projects: adding a phase did nothing on Save
- β³ π Leavers could still sign in
- β³ πΌοΈ Logo and courses broke on Apache with PHP-FPM
- β³ π’ Chat tickets ignored your ticket numbering
- β³ π Dates shown as a dash, or in server time
- β³ π Assets β Users showed people from other companies
- β³ π Restricted analysts could read other modules' data
- β³ πΌοΈ Replies with a picture in the thread failed to send
- β³ π Reply attachments never reached the customer
- β³ π οΈ Outbound email attachments β Developer Guide
- β³ π A global SSO provider was missing from the portal
- β³ π Behind a proxy, the SSO redirect said http
- β³ βοΈ The portal tagline moved when you saved it
- β³ π¨ The portal settings screen forgot what you saved
- β³ π‘οΈ The approvals inbox said "Error" and nothing else
- β³ π A table's answers were missing from the PDF
- β³ β A single-select column let you tick every option
- β³ π The portal ignored a form's field widths
- β³ π The tasks board stopped taking clicks
- β³ ποΈ #121 The index list is out of date after upgrading
- β³ π #133 The calendar subscription was empty
- β³ π #131 Tasks always reopened on the board
- β³ π₯ #129 Every page returned HTTP 500 after upgrading
- β³ π³ #127 A PHP warning above the System page
- β³ π #126 Notes stamped with the server's clock
- β³ π Storing every date in UTC
- β³ πͺ The portal was down for everyone signed in
- β³ βοΈ #120 Workflow notes could never be written
- β³ βοΈ #123 Three errors when running Database Verification
- β³ π #122 The description box was a stub in the corner
- β³ π£ Demo data deleted real accounts
- β³ π #117 Sign-in redirected to the wrong address
- β³ π¨ #108 The priority dot was invisible
- β³ β±οΈ #116 Time logged from the right-click menu
- β³ π #114 API keys refused by our own guard
- β³ ποΈ #110 Assigning a task told nobody
- β³ πͺ #107 Signed out while still working
- β³ π #103 "Share with Requester" reached nobody
- β³ π #102 Search found nothing for hyphens
- β³ πͺ #101 Source code editor opened behind
- β³ βοΈ #88 Subtasks could not be ticked off
- β³ π» #84 Asset deep link selected nothing
- β³ π« #79 A new ticket arrived with no status
- β³ π§ #79 A ticket from email did not say so
- β³ π #78 Bell opened to nothing
- β³ π¬ #77 Mail only collected from Inbox
- β³ π #74 The default password could not be changed
- β³ π¦ #70 Renaming an impact level
- β³ π€ #67 App-only mailboxes could not send
- β³ π #45 Verify only ever worked for Microsoft
- β³ π #45 IMAP reported as not authenticated
- β³ βοΈ An email template stopped escaping itself
- β³ π The portal dashboard showed the wrong time
- β³ π’ The folder said 99 and the list showed 96