Skip to content

Issue 248 Department Rules Never Matched On Four Routes

Ed Mozley edited this page Nov 7, 2026 · 1 revision

A department rule never matched on portal, email, messaging or web chat tickets

Reported: #248, found while writing up #218, November 2026 Β· Module: Tickets Β· Fixed in: the release after 4.10.0 (changelog #2739)


What you saw

A ticket type rule (Tickets β†’ Settings β†’ Ticket types β†’ rules) can say "a new ticket in the IT department becomes an Incident". On a ticket raised in Tickets, that worked. On a ticket raised from the self-service portal, by email, from messaging (WhatsApp, Teams, Slack) or from web chat, the rule was skipped without any message, and the ticket got the next rule's type or the default.

Try it, at the bottom of the rules section, said the rule would match - because there you type the department in yourself. So the screen that is meant to prove a rule disagreed with what real tickets got.


What was actually wrong

Two things that each worked, in the wrong order

Since 4.9.0 (#218), a company can have a default department: a ticket that arrives without one starts there. The portal, email and chat never choose a department themselves, so on those routes the default is the only department a new ticket can have.

Each route makes a new ticket in roughly this order:

  1. pick the type - the type rules run here, given the facts the route knows;
  2. make the ticket number, which can carry the type;
  3. work out the department - the company's default;
  4. insert the ticket.

The type rules were asked at step 1 about a department that was only worked out at step 3. The routes did not pass a department at all, and a rule condition about a fact that is unknown does not match. That last part is deliberate: an unknown department must not count as "any department". So every department condition failed on these routes, default or not.

Why it was easy to miss

The default department and the type rules were built separately (#218 and #230), and each one was right on its own. The routes that create tickets from inside FreeITSM - New ticket in Tickets and the REST API, catalogue approvals and the workflow Create a ticket action - already worked out the department first, because a caller can name one there. Only the four routes that take tickets from outside had the old order.

It was four places, not one

A search for every caller of ticketTypeAutoPick() found eight. Four were broken and four were fine:

Route File Before
Portal api/self-service/create_ticket.php ❌ type first, no department passed
Email api/tickets/check_mailbox_email.php ❌ type first, no department passed
Messaging includes/messaging/ingest.php ❌ type first, no department passed
Web chat includes/webchat/webchat.php ❌ type first, no department passed
New ticket, REST API, alerts, 3CX includes/services/tickets.php βœ…
Catalogue includes/catalogue_approvals.php βœ…
Workflow "Create a ticket" workflow/includes/engine.php βœ…
The Try it button api/tickets/ticket_type_rules.php βœ… (you type the department)

The fix

On each of the four routes, the department is now worked out before the type is picked, and passed to the rules:

// includes/webchat/webchat.php
[$departmentId, $departmentNote] = ticketDepartmentForNewTicket($conn, $tenantId, null);
require_once __DIR__ . '/../ticket_type_rules.php';
$typePick = ticketTypeAutoPick($conn, $tenantId, [
    'channel' => 'webchat', 'origin_id' => $originId, 'subject' => $subject,
    'requester_email' => $email, 'department_id' => $departmentId,
]);

Moving it earlier changes nothing else. ticketDepartmentForNewTicket() only reads - the company's default and whether that department is still active - and writes nothing. The history note it may produce ("the default department is no longer active") is still written after the insert, once there is a ticket to write it on.

No database change, nothing to run after upgrading.


Files changed

File Change
πŸ”΅ api/self-service/create_ticket.php department before the type; passed to the rules
πŸ”΅ api/tickets/check_mailbox_email.php the same
πŸ”΅ includes/messaging/ingest.php the same
πŸ”΅ includes/webchat/webchat.php the same
πŸ§ͺ tests/ticket-type-rules.php part 1b: every route works out the department before the type and passes it; a sweep fails if a new route calls ticketTypeAutoPick() without being checked

Each moved block carries a TRAP: comment naming #248, so the order is not "tidied" back.


How it was proved

  • php tests/ticket-type-rules.php - 27 passed.
  • Control: the same test against the old code - all 8 checks on the four broken routes fail, and the three routes that were already right pass. So the test can tell the two apart; it is not passing because it checks nothing.
  • The sweep found its own blind spot on the first run: it flagged includes/ticket_type_rules.php, where the function is defined. That file is now listed as known, and any new caller still fails the test until somebody checks it.

Not proven: the test reads the source, it does not raise a real portal, email or chat ticket. The matching itself - a department condition against a department - was already tested in part 1. What changed is only whether the department reaches it, which is what the source check covers. A ticket raised end to end on a test install would be the stronger proof.


What this means for you

  • If you have a type rule with a Department condition and a default department set, portal, email, messaging and web chat tickets now get that rule's type.
  • On those routes the default is the only department a new ticket has, so a Department condition there means "the company's default department". Tickets created before the upgrade keep the type they were given.
  • No rule needs changing.

Related

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Responses to feedback

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally