Helpdesk Reference Public

Tickets: Lifecycle & Management

The full support-ticket lifecycle: statuses, ticket types & SLAs, groups, templates, signatures, collaborators, comments, labels, and submission channels.

Guide version: r1 Module version: 0.1.0 Updated: 2026-07-22 Estimated time: 22 min 1 views

Tickets: Lifecycle & Management

Tickets are the heart of the ERPat Helpdesk. This guide walks the full support-ticket lifecycle — from creation and assignment through comments, status transitions, and closure — and every management surface around it: ticket types and their SLA durations, routing groups, canned templates, agent signatures, collaborators, labels, attachments, and the three ways a ticket can be submitted.

Scope This page covers the ticket lifecycle and management. Inbound email, IMAP polling, external-form submission internals, OTP identity verification, and the anonymous contact-support portal are documented separately on Tickets — Email & Automation. The Permissions Reference and Glossary & Legends hold the master tables.

Who uses tickets

The ticketing pillar serves several distinct roles. Access to any surface requires both the module_ticket pillar toggle to be enabled and the relevant permission grant on the user's role.

Support Agent (staff) Helpdesk Admin Collaborator Client Contact Anonymous Requester

Support Agent

Works the Browse queue, replies with comments, changes status, and is assigned tickets to resolve.

Helpdesk Admin

Defines ticket types, groups, labels, and templates; manages assignment; edits or deletes tickets and comments.

Client Contact

Raises and follows their own tickets from the client portal; a client reply flips the ticket to client_replied.

Anonymous Requester

Submits from the public contact-support portal after verifying an email OTP — no login required.

Status legend

Every ticket carries exactly one status value in the tickets table. The five statuses below are the complete set the module uses. On the list table the status label is colour-coded, and on the ticket detail view it renders as a pill (status-new, status-open, and so on).

new open on_hold client_replied closed
StatusMeaningHow a ticket reaches it
newA freshly created, un-worked ticket. On creation the module logs "Ticket marked as new". Unassigned new tickets show the Assign to me / Assign to user options.Set implicitly when a ticket is first saved (create path in Tickets/save and the inbound channels).
openThe ticket is actively being worked. This is the only status in which the in-page comment composer is shown, and the only one from which Mark as On Hold / Mark as Closed are offered.Mark as Open action, self/other assignment (tickets/assign_to forces open), or a staff comment on a new/closed ticket by the assignee/collaborator/admin.
on_holdPaused / parked — waiting on something. Excluded from overdue calculations. No SLA due label is shown while on hold.Mark as On Hold action from an open ticket (staff only).
client_repliedA client contact posted a comment, signalling the agent that the customer responded. Internal signal only.Set automatically when a user whose user_type is client saves a comment on the ticket.
closedResolved. The module stamps closed_at with the UTC close time and logs the closure. Excluded from overdue calculations.Mark as Closed action from an open ticket (staff only).
Client-side masking Clients never see client_replied. When the list row is built for a client user, that status is displayed as open so customers only ever perceive new, open, on hold, and closed.
Default queue filter The Browse table opens with new and open pre-checked and on_hold / closed unchecked. If a ticket "disappears" after you close or hold it, it hasn't gone anywhere — tick those boxes in the Status multi-select to see it again.

The ticket lifecycle

From first submission to resolution, a ticket flows through a predictable pipeline. Statuses can loop back — a closed ticket can be re-opened, and a client reply re-engages an agent at any point.

Createstatus: new
Assignuser / group
Respondstatus: open
Holdoptional
Closeclosed_at set
Reopenback to open

1. Create

A ticket is created via the Add Ticket modal (staff or client), the client portal, or an inbound channel. A ticket type is mandatory. On save the ticket is new, a consolidated "ticket created" summary comment is logged, and the description becomes the first comment.

2. Assign

Assignment can be automatic (a ticket type carries a default assigned_to user), manual (Assign to user or Assign to me), or by routing bucket (ticket groups). Assigning always forces the status to open and notifies the new assignee.

3. Respond & comment

Agents and clients converse through the comment thread. Each comment stamps last_activity_at. A staff comment on a new or closed ticket (by the assignee, a collaborator, or an admin) reopens it to open. A client comment sets client_replied.

4. On hold (optional)

When work is blocked, an agent marks the open ticket on_hold. Overdue tracking pauses while held.

5. Resolve / close

Mark as Closed sets closed, records closed_at, logs the closure, and fires a ticket_closed notification.

6. Reopen

A closed, held, or new ticket can be re-opened with Mark as Open; the module logs "Ticket reopened" and notifies via ticket_reopened. A fresh comment on a closed ticket by the assignee/collaborator/admin also reopens it automatically.

Status action matrix

The row-level and detail-view action menus are permission- and status-aware. The table below reflects the exact conditions in the controller.

ActionEndpointShown whenResulting status
Assign to metickets/assign_to/{id}staff, ticket is new and assigned_to = 0, with ticket_manageopen
Assign to usertickets/assign_to/{id}/other_userstaff, ticket is new or open, with ticket_manageopen
Mark as Opentickets/save_ticket_status/{id}/openstaff, ticket is new, on_hold, or closedopen
Mark as On Holdtickets/save_ticket_status/{id}/on_holdstaff, ticket is openon_hold
Mark as Closedtickets/save_ticket_status/{id}/closedstaff, ticket is openclosed
Quick commenttickets/quick_comment_modal_formticket is open, with ticket_commentunchanged
Note Status-changing actions require ticket_staff_update (they call save_ticket_status, which gates on it) and either ticket_manage or being the ticket's assignee. There is no direct "reassign to another ticket type" status transition — that happens through the edit modal, not the status endpoints.

Creating a ticket

The Add Ticket button opens the modal served by tickets/modal_form and posts to tickets/save. What appears in the form depends on who you are and your permissions.

Staff see the full form: title (unless hide_ticket_title is on), optional client, Requested by, Collaborators, optional project reference, ticket type, description, and — depending on grants — Assign to (needs ticket_manage), a due date (needs override_ticket_due_date), and labels (needs add_ticket_label). Selecting a client dynamically reloads the requested-by, collaborators, and project dropdowns for that client.

  • Selecting a ticket type shows its description as a tooltip (via ticket_types/get_tooltip_description).
  • If no ticket type is available to you, the modal warns you and creating may conflict — request ticket type access from an admin.
  • Files can be attached only at creation time (the dropzone is hidden when editing).

Client contacts get a trimmed form. Their client_id is forced to their own company, assigned_to is set to none, and Requested by is silently set to themselves. Clients cannot set an assignee, due date, labels, or collaborators. They need the ticket_create permission (creating) or ticket_update (editing) — see Permissions.

Description locks after creation The description field is only editable while creating. When you edit an existing ticket, the description (the opening message) is intentionally not shown — subsequent context belongs in comments, not the original request.

Ticket types & SLAs

A ticket type classifies a request (for example "Bug", "Access request", "Billing"). Types are managed from the Types tab (tickets/view_types → the ticket_types controller). Every ticket must have a type, and the type drives the SLA, default assignee, points, and labels.

List route
/ticket_types
Data
ticket_types/list_data
Add / edit
ticket_types/modal_form
Save
ticket_types/save

Type fields

FieldPurpose
titleDisplay name of the type (required).
durationThe SLA window, stored in seconds. The modal captures hours/minutes/seconds and multiplies to seconds (hours×3600 + mins×60 + sec).
pointsEffort/weight value shown next to the type (e.g. "3 points").
assigned_toDefault agent a new ticket of this type is auto-assigned to when no explicit assignee is chosen (and the ticket has no client).
labelsLabels attached to the type itself (rendered beside the type in the queue).
descriptionFree text shown as a tooltip on the create form.

How the SLA due date is derived

The queue shows a due date and an OVERDUE flag using this precedence:

Explicit due_date?use it
Else type durationcreated_at + duration(s)
Past due & not closed/on_hold?show OVERDUE

An explicit due_date (set on a ticket by a user with override_ticket_due_date) always wins. Otherwise, the effective due time is the ticket's created_at plus the type's duration seconds. A ticket is flagged overdue only if it is neither closed nor on_hold.

The two override permits override_ticket_type unlocks all ticket types in the create/group pickers (bypassing the per-user allowed-types restriction). override_ticket_due_date reveals the due-date & time fields on the ticket form so an agent can set a custom SLA deadline independent of the type duration.

Ticket groups

A ticket group is a routing/assignment bucket that bundles several ticket types together, so a queue can be filtered to (say) "Finance requests" or "IT requests" in one click. Groups live in the Groups tab and are managed by the ticket_groups controller.

List
ticket_groups/index
Data
ticket_groups/list_data
Add / edit
ticket_groups/modal_form
Types in group
ticket_groups/list_ticket_types_modal

Group fields & behaviour

FieldPurpose
titleGroup name (required).
descriptionOptional description.
ticket_types_idComma-separated list of the ticket type IDs the group contains (required). The queue's group filter shows only tickets whose type is in this set (matched via FIND_IN_SET).
accessible_byWho the group is exposed to (rendered as info labels on the list).
statusactive / inactive — the group list defaults to showing active groups.

The types count on the group row is a modal link (list_ticket_types_modal) that lists exactly which types are mapped, each shown with its duration and points. Filtering the ticket queue by a group narrows results to tickets whose type belongs to that group.

Group ↔ type mapping A group's membership is stored on the group record, not on each type — one type can belong to several groups. To reassign, edit the group and adjust its ticket-type multi-select; the modal's type picker respects your allowed types unless you hold override_ticket_type.

Templates & signatures (canned replies)

Templates are reusable comment/reply bodies. They speed up common answers and live in the Templates tab (visible to admins). Each agent also has a personal signature that pre-fills the comment composer.

Managing templates

ActionEndpoint
Listtickets/view_templates / tickets/ticket_template_list_data
Add / edit formtickets/ticket_template_modal_form
Savetickets/save_ticket_template
Viewtickets/ticket_template_view/{id}
Deletetickets/delete_ticket_template

A template has a title, a description (the body), an optional ticket_type_id binding, and a private flag. Templates with ticket_type_id = 0 apply to any type. Only the creator or an admin can edit or delete a template; a private template is visible only to its creator, while public templates are shared with all agents.

Inserting a template or KB article into a reply

Inside the open-ticket comment composer, staff with ticket_manage get two "insert" buttons:

Insert Template

tickets/insert_template_modal_form lists templates matching the ticket's type (plus type-agnostic ones). Clicking a row pastes its HTML body into the reply editor and closes the modal.

Insert Knowledge Base Content

tickets/insert_knowledge_base_content_modal_form lists your accessible KB articles (respecting knowledge_base_categories access). Clicking a row pastes the article body straight into the reply — a fast way to answer with an existing published answer.

Per-agent signature

The Signature button (tickets/settings_modal_form) opens a rich-text editor saved via tickets/save_settings to the user setting user_{id}_signature. When you open an open ticket, the comment box is pre-filled with your signature so every reply is consistently signed.

Tip Keep the signature short — it is prepended to every reply. Use a template for longer boilerplate you only want on some replies.

Collaborators

Collaborators are extra people looped in on a ticket beyond the requester and assignee. They are stored as a comma-separated list in the ticket's collaborators column and gated by the ticket_collaborator permission (which controls who can be picked as a collaborator).

Add / remove

On the ticket edit modal (staff), the Collaborators multi-select lets you add or remove people. Saving diffs the old vs new list.

Notifications

Added collaborators receive an added_as_a_ticket_collaborator notification; removed ones get removed_as_a_ticket_collaborator. Changes are also written to the ticket activity log ("Added collaborators: …" / "Removed collaborators: …").

Access & commenting

Being a collaborator grants access to a ticket even when the base ticket-type access wouldn't. Collaborators can post comments even without the blanket ticket_comment permission, and can be @mentioned.

Viewing the list

tickets/collaborators_modal and tickets/list_collaborators/{id} render the current collaborator roster with avatars.

Comments, mentions & activity log

The conversation thread on a ticket is a series of comments stored in ticket_comments. The composer is shown only while the ticket is open and you may comment.

Who can comment

You can post a comment if you hold the ticket_comment permission or you are the ticket's creator, requested-by, assignee, or a collaborator. Comments save via tickets/save_comment.

Comment features

FeatureHow it works
AttachmentsFiles are uploaded to a temp dir then moved to the timeline path; the serialized file list is stored in the comment's files column. Never clean/escape that serialized value.
@MentionsTyping @ pulls the ticket's members (tickets/get_allowed_members_to_mention). Mentioned users receive a mentioned_on_a_ticket notification.
Quick commentFrom the queue's row menu (open tickets), tickets/quick_comment_modal_form lets you reply without opening the ticket.
Edit commenttickets/update_comment_modal_formtickets/update_comment. Requires ticket_staff_update. Static/system comments cannot be edited.
Delete commenttickets/delete_comment — requires the ticket_staff_delete permit (not admin-only). Static/system comments can never be deleted.
Auto-refreshWhen allow_ticket_comment_auto_refresh is on, the thread polls tickets/get_latest_comments on an interval and can be paused.
Sort orderThe show_recent_ticket_comments_at_the_top setting flips the thread to newest-first and moves the composer accordingly.

Ticket activity log & flood control

Field-level changes (type, project, client, title, requester, assignee, collaborators, labels) are written to the ticket timeline as human-readable module log entries. On creation, instead of one log per field, the module posts a single consolidated "ticket created" summary comment to avoid flooding the timeline.

Separately, the ticket_logs table records submission events with the requester's IP; its submission_count query counts submissions from the same IP + user in the last 30 minutes for flood control on inbound/public submissions. That anti-abuse machinery is covered on Tickets — Email & Automation.

Labels & priority

Labels are lightweight coloured tags used for triage and priority signalling. A ticket's labels are stored as a comma-separated list of label IDs in the labels column and matched with FIND_IN_SET when filtering the queue.

  • Adding labels to a ticket via the edit form requires the add_ticket_label permission (the labels field is hidden otherwise).
  • Label definitions (name + colour) are managed through the shared Labels manager (labels/modal_form) with type=ticket for ticket labels and type=ticket_type for labels attached to ticket types.
  • The queue's Label filter dropdown lets you narrow to a single label.
Using labels as priority ERPat tickets have no dedicated numeric priority field — teams typically model priority with labels (e.g. a red "Urgent" label). Combine label colour with the SLA due date for effective triage.

File attachments & the viewer

Attachments ride along with comments (and the first comment created from a new ticket's description). Uploads are storage-aware: files land in the configured timeline file path, which may be local or S3-backed, so the same viewer/download logic works in both.

Upload

The dropzone posts to tickets/upload_file (temp) and tickets/validate_ticket_file validates it; on save the files are moved to permanent storage and serialized into the comment.

Preview vs viewer

Images, video, PDF, TXT and CSS render inline in a lightbox. Documents like spreadsheets and .docx open the rich in-modal file viewer (tickets/file_viewer/{comment_id}?file_name=…) — spreadsheets via SheetJS, docx via Mammoth, text via highlight.js, with a download fallback.

Download

tickets/download_comment_files/{id} zips and downloads all files on a comment. Access to the viewer is validated against the parent ticket exactly like the detail view.

Editing limitation Files can only be attached when creating a ticket or when posting a comment — the ticket edit modal hides the uploader. To add a file to an existing ticket, post a comment with the attachment.

Submission channels

Tickets can enter the system three ways. All three land in the same tickets table and lifecycle, but the entry surface and identity handling differ.

Agents work tickets from the internal /tickets page. It's a tabbed panel: Browse (the live DataTable queue), Templates (admin), Types, and Groups. The Browse table supports status multi-select, filters (type, group, label, assigned-to, requested-by, collaborators, created date), an Overdue checkbox, a display toggle (picture/name), auto-refresh, and per-row action menus. Staff create tickets with the full modal and can assign, label, set due dates, and manage collaborators.

Client contacts see a trimmed ticket list rendered from the client area (clients/tickets/index). They can raise a ticket for their own company with ticket_create, follow the thread, and reply — a client reply flips the ticket to client_replied for the agent. Clients never see the client_replied label themselves, nor assignment/label/collaborator controls.

The public contact-support portal (the Ticketing Portal button on the queue) is an anonymous, marketing-styled form. The requester chooses a destination — the main platform or a specific tenant via a verified company key — picks a ticket type, writes a description, and, if not logged in, verifies their email with a one-time code before submitting. reCAPTCHA and flood control protect it. The full external + email pipeline is documented on Tickets — Email & Automation.

Routes & endpoints

Ticket routes are declared in the module's config/routes.php and merged into the core route table. The tickets/(:any) chains cover the multi-segment methods; only the notable linkable entry points are listed below.

RouteResolves toPurpose
/ticketsTickets/indexMain tabbed ticket workspace (Browse / Templates / Types / Groups).
tickets/view_browseTickets/view_browseThe live queue DataTable partial.
tickets/view/{id}Tickets/view/$1Single-ticket detail view with the comment thread.
tickets/modal_formTickets/modal_formAdd/edit ticket modal.
tickets/saveTickets/saveCreate/update a ticket.
tickets/list_dataTickets/list_dataDataTable JSON for the staff queue.
tickets/save_commentTickets/save_commentPost a comment (may change status).
tickets/save_ticket_status/{id}/{status}Tickets/save_ticket_statusOpen / on_hold / closed transition.
tickets/assign_to/{id}[/other_user]Tickets/assign_toSelf- or other-user assignment (forces open).
tickets/deleteTickets/deleteSoft-delete a ticket and its comments.
tickets/file_viewer/{comment_id}Tickets/file_viewerRich in-modal attachment viewer.
/ticket_typesTicket_types/indexTicket type management.
ticket_types/saveTicket_types/saveCreate/update a type (duration in seconds).
ticket_groups/indexTicket_groups/indexTicket group management (default routing dispatch).
ticket_groups/list_ticket_types_modalTicket_groups/list_ticket_types_modalShow the types inside a group.
contact-supportpublic portalAnonymous/external ticket submission (see email page).
Legacy alias A historical css route also maps to tickets/index and is preserved for backward compatibility — you can ignore it in day-to-day use.

Permissions (RBAC)

These are the exact ticket-related permission keys the module surfaces to the Roles UI (from config/permissions.php). They appear only when module_helpdesk is enabled. Keys marked specific render an all/specific dropdown; the rest render an enable toggle.

Permission keyLabelWhat it gates
ticket specificTicket Type (Access)Base access to the tickets pillar and which ticket types you may work (all vs specific).
ticket_createCreate TicketCreate a ticket type; also the client-side create-ticket permit.
ticket_updateEdit TicketEdit a ticket type; also the client-side edit-ticket permit.
ticket_deleteRemove TicketDelete a ticket type; also the client-side delete permit.
ticket_staff specificTicket Staff (Access)Which staff/members a user can see and act on across tickets (assignment/requester scope).
ticket_staff_createCreate Ticket StaffStaff permission to create a ticket from the internal form.
ticket_staff_updateEdit Ticket StaffStaff permission to edit a ticket, change status, edit comments.
ticket_staff_deleteRemove Ticket StaffStaff permission to delete a ticket and to delete comments.
ticket_collaborator specificTicket Collaborators (Access)Who can be selected as a collaborator on tickets.
ticket_manageManage TicketsAssign, edit, and run the full action menu; shows the assignee field and insert-template buttons.
ticket_commentTicket CommentsPost comments on any accessible ticket (participants can comment without it).
add_ticket_labelAdd Ticket LabelReveals the labels field on the ticket form so an agent can tag tickets.
override_ticket_due_dateOverride Ticket Due DateShows the custom due-date & time fields, overriding the type's SLA duration.
override_ticket_typeOverride Ticket TypeUnlocks all ticket types in pickers, bypassing the per-user allowed-types restriction.
ticket_groupsTicket GroupsAccess to the Groups tab and group filtering.
ticket_groups_createCreate Ticket GroupCreate a new routing group.
ticket_groups_updateEdit Ticket GroupEdit an existing group's title/types/access.
ticket_groups_deleteRemove Ticket GroupDelete a group.
Access = toggle AND permit A user reaches any ticket surface only when the module_ticket pillar sub-toggle is enabled and their role grants the matching permit. Turning off module_ticket (or the master module_helpdesk) hides the whole pillar regardless of role.

Standard operating procedures

SOP — Handle an incoming ticket
  1. Open /tickets and confirm the Browse tab shows new tickets (default filter).
  2. Click the ticket ID or title to open the detail view; read the original description (first comment).
  3. If it is unassigned, use Assign to me (or Assign to user) — this moves it to open.
  4. Post your first reply in the composer. Your signature is pre-filled; use Insert Template or Insert Knowledge Base Content for canned answers.
  5. Attach any needed files to the comment (uploader lives in the composer, not the edit modal).
  6. Add labels for triage if you have add_ticket_label, and loop in collaborators as needed.
  7. Watch the SLA due date; if you can't resolve promptly, either keep working or set On Hold with a comment explaining why.
SOP — Escalate / reassign
  1. From the queue row menu or the ticket's Actions menu, choose Assign to user (requires ticket_manage).
  2. Pick the target agent from the Select2 dropdown and save — the assignee changes and the ticket goes/stays open.
  3. The new assignee is notified (ticket_assigned); the previous assignee, if any, is notified they were removed.
  4. Add a collaborator instead if you want a specialist to advise without owning the ticket — they'll be able to comment and are notified.
  5. For cross-type routing, edit the ticket and change its Ticket Type; this may re-scope who can see it (an agent without access to the new type loses the row).
SOP — Close & reopen
  1. Confirm the resolution in a final comment while the ticket is open.
  2. Choose Mark as Closed — the ticket becomes closed, closed_at is stamped, and a ticket_closed notification fires.
  3. To reopen: tick closed in the status filter to find it, then use Mark as Open. The module logs "Ticket reopened" and notifies via ticket_reopened.
  4. Alternatively, when the assignee/collaborator/admin posts a new comment on a closed ticket, it reopens automatically to open.

Use cases

Internal IT desk

Employees raise "Bug" and "Access request" types; groups route them to the right agents; SLA durations flag overdue work in red.

Client support

Client contacts open tickets from the portal; a client reply surfaces client_replied so agents know a customer is waiting.

Public intake

Prospects and non-users submit via contact-support with email OTP verification — no account needed.

SLA tracking

Type durations (in seconds) drive due dates; the Overdue filter surfaces breaches, and On Hold pauses the clock.

Gotchas & troubleshooting

Duration is stored in seconds A ticket type's duration is seconds, not hours. The modal captures hours/minutes/seconds and converts. If SLAs look wildly off, verify the saved seconds value rather than assuming hours.

The Browse queue defaults to showing only new and open. Closed and on-hold tickets are hidden until you tick them in the Status multi-select. Changing a ticket's type can also drop it from your view if you don't have access to the new type.

The composer only appears when the ticket is open and you may comment. If it's new, on hold, or closed, open it first (or use Quick comment from the row menu on open tickets). You must also hold ticket_comment or be a participant (creator/requester/assignee/collaborator).

Those fields are permission-gated: assignee needs ticket_manage, the due-date fields need override_ticket_due_date, and labels need add_ticket_label. Clients never see any of them. Ask an admin to grant the permit.

Your role's ticket access is specific and no ticket types are in your allowed list. Ask an admin to grant you the relevant types, or the override_ticket_type permit to see all types.

By design. The description is the original request and is only editable at creation. Add context through comments instead.

Comment deletion requires the ticket_staff_delete permit — it is not restricted to admins. Static/system-generated comments (like the creation summary) can't be edited or deleted by anyone.

Only images, video, PDF, TXT and CSS preview in the lightbox. Spreadsheets, .docx, and other documents open the rich in-modal file viewer, which always offers a download fallback.

Terminology

Ticket type required

The classification a ticket must have. Carries the SLA duration (seconds), points, default assignee, and labels.

Ticket group

A routing bucket bundling several ticket types (comma-separated ticket_types_id) for one-click queue filtering.

Collaborator

An extra participant on a ticket (CSV collaborators column) who gains access and can comment even without the blanket comment permit.

Template

A reusable reply body, optionally bound to a type and optionally private to its creator, inserted into the comment editor.

Signature

A per-agent block (user_{id}_signature) that pre-fills the reply composer.

Overdue

A non-closed, non-held ticket whose effective due date (explicit due_date, else created_at + type duration) is in the past.

Requested by

The person the ticket is raised on behalf of (requested_by), distinct from created_by and the assigned_to agent.

Keep exploring Ready for automation? Head to Tickets — Email & Automation for inbound email, IMAP, the public portal internals, OTP verification, and flood control. For the master permission grid see Permissions Reference, and for every status/badge meaning see the Glossary & Legends.
Was this guide helpful?

Report a content problem