Ticket Types For New Tickets Developer Guide - edmozley/freeitsm GitHub Wiki
๐ ๏ธ A type for every new ticket - Developer Guide
The design behind A type for every new ticket (#230, 4.8.0): a default type and ordered rules, applied before a ticket is inserted, on every path that creates one.
1. ๐ Files
| File | What it does |
|---|---|
includes/ticket_type_rules.php |
Everything that decides: TICKET_TYPE_CHANNELS, ticketTypeRuleConditions() (cleans criteria), ticketTypeRuleMatches(), ticketTypeUsable() (the types a company may give), ticketTypeRulesFor() (company's own, then shared), ticketTypeAutoPick(), ticketTypeAutoRecord() (the history line) |
api/tickets/ticket_type_rules.php |
Settings API: GET, and POST save, delete, move, set_default, test. Needs Cap::TICKETS_TICKET_TYPES |
assets/js/ticket-type-rules.js, assets/css/ticket-type-rules.css |
The section under Tickets โ Settings โ Ticket types and its editor |
workflow/includes/engine.php |
The set_ticket_type action, through action_ticket_field() |
tests/ticket-type-rules.php |
Criteria, order, default, a chosen type winning, the service, the Workflow action; two companies in a child process |
Table ticket_type_rules (tenant_id NULL = shared, name, ticket_type_id, conditions_json, display_order, is_active, created_by); setting ticket_default_type_id through tenantSetting() - a company's row in tenant_settings, else system_settings. A company row of '0' means "none for this company"; no row means "follow the shared default".
2. ๐ Why before the insert, and why not Workflow alone
The issue offered a default or a Workflow action. A Workflow action runs after the ticket exists, which is too late for three things:
- The number.
TicketNumbering::next($conn, $typeId, $tenantId)can put the type in the number ({TYPE}) or count per type. A type set afterwards leaves an incident number on a service request. - Channels that never announce. Only email, the portal and
TicketsService::createTicket()fireticket.created; web chat, messaging, the catalogue and a workflow's own "Create a ticket" do not. A rule there would silently never run. - Things that hang off the type - categories bound to a type, custom fields per type.
So the decision is a plain function every creation path calls before TicketNumbering::next(), and Workflow gets set_ticket_type for later changes.
3. ๐ช The paths, and what each passes
ticketTypeAutoPick($conn, $tenantId, $facts) is called only when no type was chosen. $facts: channel, mailbox_id, origin_id, department_id, subject, requester_email - whatever that path knows at that moment.
| Path | File | channel | Notes |
|---|---|---|---|
| New ticket form, REST API, alerts, 3CX, projects, tasks | includes/services/tickets.php createTicket(..., ?string $channel) |
form from the UI, api otherwise, or what the caller passes: monitoring (Zabbix, Domains renewal), phone (3CX), internal (project RAID issue, task follow-up) |
A parameter, never a key in $in, so an API caller cannot claim to be email |
api/tickets/check_mailbox_email.php |
email |
Mailbox, the mailbox's origin, subject, sender, the default department; generateTicketNumber() now takes the type |
|
| Portal | api/self-service/create_ticket.php |
portal |
The chosen mailbox, subject, requester, the default department |
| Messaging | includes/messaging/ingest.php |
messaging |
Origin, the built subject, the default department; messagingGenerateTicketNumber() now takes the type |
| Web chat | includes/webchat/webchat.php |
webchat |
Origin, subject, requester, the default department |
| Catalogue | includes/catalogue_approvals.php catalogueCreateTicketFromSubmission() |
catalogue, or workflow when a workflow raises from a form (_channel in the overrides) |
Only when the form / rule configured no type |
| Workflow "Create a ticket" | workflow/includes/engine.php |
workflow |
Only when the step names no type |
Merge and split copy the type from the tickets they came from, so they do not ask.
TRAP: in
createTicket()the pick comes AFTER the custom-field and category checks. Those were made against the type the caller sent (none) - which is what the form showed. Picking first would make a required-at-logging field of the rule's type required, and refuse an analyst for a field they were never shown.TRAP: resolve the department BEFORE the pick, and pass it. Every path calls
ticketDepartmentForNewTicket()first and puts the result indepartment_id. The four outside routes used to pick first, so a Department rule never matched there (#248).tests/ticket-type-rules.phppart 1b checks every path, and fails for a new caller ofticketTypeAutoPick()it does not know about.TRAP:
ticketTypeAutoPick()never throws. A missing table, a bad row, anything: it logs and returns null, and the ticket is raised as before. Ticket creation must never depend on this feature working.
4. ๐งฎ Matching
ticketTypeRulesFor($tenantId): the company's rules (tenant_id = ?) bydisplay_order, id, then the shared ones. Single-company installs only have shared rules (the settings save withtenant_idNULL in the default context, as ticket types do).- A rule is passed over when its type is not in
ticketTypeUsable($tenantId)- inactive, hidden by the company (tenant_config_hidden), or another company's - or when it has no criteria at all (the API refuses those; this is belt and braces). ticketTypeRuleMatches(): an empty criterion is ignored; a criterion whose fact is unknown (department_idnull) does not match; lists match any; subject lines match case-insensitively anywhere; a domain matches itself or*.domain.- A triage ticket (
tenant_idnull on a multi-company install) can only get a shared type.
5. ๐งพ History
ticketTypeAutoRecord() writes a ticket_audit row, field_name Ticket Type (the same name a manual change uses), analyst_id NULL, new_value like Service Request (set by the rule "Requests mailbox"). Stored in English, as the rest of the trail is.
6. โ๏ธ Settings API
Scope follows ticket types: single-company, or the default company on a multi-company install โ shared rules and system_settings; a client company โ its own rules and tenant_settings (set_default value inherit deletes its row, '' stores '0'). move renumbers the scope 10, 20, 30... and swaps, so equal orders can never make a move do nothing. test runs ticketTypeAutoPick() itself - the answer is the real one. The Company criterion is kept only on shared rules of a multi-company install.
7. ๐ Workflow: set_ticket_type
action_ticket_field($args, $payload, 'ticket_type_id', 'ticket_type_id', 'type') - the same route as team, department and category: checked against ticketTypeUsable() for the ticket's company, then TicketsService::updateTicket(), which audits, fires events, and clears a category bound to another type. The number is not changed.
8. โ How it was checked
tests/ticket-type-rules.php (33 checks on the test install): criteria cleaning and matching; first match, an inactive rule and a rule naming a retired type passed over, the default; a ticket through the real createTicket() getting the rule's type with the history line, a chosen type winning with no line, an API ticket counted as REST API; set_ticket_type changing a type and refusing one the company cannot use; then, with a second company in a child process, a company's rules before the shared ones, a shared rule naming a type the company hid passed over, a company's own default, "none" and the shared one. The settings section was driven in a browser: refusing a rule with nothing filled in, saving, the summary, the default, reordering, and Try it naming the rule and falling to the default. workflow-company-scope (87), ticket-numbering, the portal, messaging, 3CX and catalogue tests were run after the change.