Core ticket system for user and internal request tracking.
Orchestrates the full ticket lifecycle including status transitions,
SLA deadline calculation, task management, comments, links,
templates, audit trail, problem analysis, work scoring, and
email notifications. Delegates to sub-modules for specialised
concerns.
| Column | Type | Description |
|---|---|---|
| Guid | char(50) | Primary key |
| TicketNumber | int AUTO_INCREMENT | Human-readable number |
| Package | char(255) | Package context |
| Subject | varchar(500) | Ticket title |
| Description | text | Full description |
| Status | enum | NEW, TRIAGED, IN_PROGRESS, ON_HOLD, ESCALATED, RESOLVED, CLOSED, CANCELLED |
| Priority | enum | CRITICAL, HIGH, MEDIUM, LOW, INFO |
| TicketType | enum | USER, INTERNAL, BUG, FEATURE |
| RequestedBy | varchar(255) | Who raised the ticket |
| RequestedByType | enum | INTERNAL, EMAIL, SYSTEM |
| AssignedTo | varchar(255) | Who is working on it |
| ProblemAnalysis | text | Root cause analysis |
| WorkScore | int | Effort scoring |
| SlaResponseDeadline | datetime | Calculated from SLA |
| SlaResolutionDeadline | datetime | Calculated from SLA |
| SlaResponseMet | char(2) | Y/N |
| SlaResolutionMet | char(2) | Y/N |
| CreatedDate | datetime | Ticket creation time |
| UpdatedDate | datetime | Last update time |
| ResolvedDate | datetime | When resolved |
| ClosedDate | datetime | When closed |
| Watchers | text | Comma-separated watcher emails |
| MergedGuids | text | Guids of merged tickets |
| EstimatedDate | datetime | Estimated completion date (ETA) |
| Column | Type | Description |
|---|---|---|
| Guid | char(50) | Primary key |
| TicketGuid | char(50) | Parent ticket reference |
| Action | varchar(100) | Action type (STATUS_CHANGE, ASSIGNED, COMMENT_ADDED, LINKED, etc.) |
| OldValue | text | Previous value |
| NewValue | text | New value |
| ActionBy | varchar(255) | Who performed the action |
| Package | char(255) | Package context |
| ActionDate | datetime | When the action occurred |
| Column | Type | Description |
|---|---|---|
| Guid | char(50) | Primary key |
| TicketGuid | char(50) | Parent ticket reference |
| PersonEmail | varchar(255) | Who logged the time |
| StartTime | datetime | Optional start time |
| EndTime | datetime | Optional end time |
| DurationMinutes | int | Duration in minutes |
| Description | text | Work description |
| Package | char(255) | Package context |
| CreatedDate | datetime | When logged |
| Column | Type | Description |
|---|---|---|
| Id | int AUTO_INCREMENT | Primary key |
| Package | char(255) | Package scope |
| Project | varchar(255) | Project filter (empty = any) |
| Priority | varchar(20) | Priority filter (empty = any) |
| TicketType | varchar(50) | Type filter (empty = any) |
| AssignTo | varchar(255) | Email of assignee |
| Active | char(2) | Y/N |
NEW -> TRIAGED -> IN_PROGRESS -> RESOLVED -> CLOSED
-> ON_HOLD -> IN_PROGRESS
-> ESCALATED -> IN_PROGRESS
Any status -> CANCELLED
Read(guid) - Load ticket by guidReadByNumber(ticket_number) - Load by human-readable numberCreate(subject, ...) - Create ticket with SLA deadlines (transaction-safe)Update() - Update ticket fieldschange_status(new_status, action_by) - Transition statusresolve(action_by, notes) - Resolve with SLA checkclose(action_by) - Close ticketescalate(action_by) - Escalate ticket + send escalation alertassign(user, action_by) - Assign to useradd_analysis(text, action_by) - Set problem analysisset_work_score(score, action_by) - Set effort scoreadd_task(name, description, assigned_to) - Add sub-taskget_tasks() - List all tasksadd_comment(text, commented_by, is_internal) - Add comment (tracks COMMENT_ADDED)get_comments(include_internal) - List commentsadd_link(target_guid, link_type, created_by) - Link to another ticket (tracks LINKED)get_links() - List all linkscreate_from_template(template_name, requested_by, ...) - Create ticket from template with auto-taskslink_pull_request(ticket_guid, pr_id, repo="") - Link a Bitbucket PR to a ticket task; updates an existing task or creates oneget_linked_prs(ticket_guid) - Return tasks with linked PRs for a ticketcreate_from_pr(pr_data) - Create a ticket from a Bitbucket PR payload (title becomes subject, description from body)log_time(ticket_guid, person_email, duration_minutes, description, start_time, end_time) - Log a time entry against a ticket, returns entry guidget_time_entries(ticket_guid) - List all time entries for a ticketget_total_time(ticket_guid) - Return total minutes logged against a ticketset_eta(ticket_guid, eta_date) - Set the estimated completion dateget_eta_display(estimated_date) - Return human-readable ETA string ("X day(s) remaining", "overdue by X day(s)", "due today")auto_assign(package, project, priority, ticket_type) - Find best-matching assignment rule via 5-level specificity fallbackauto_assign_list_rules(package) - List all auto-assign rules for a packageauto_assign_add_rule(package, assign_to, project, priority, ticket_type) - Add a new ruleauto_assign_remove_rule(rule_id, package) - Remove a rule by IDAuto-assign is automatically called during create() when no explicit assignee is set. See ObjTicketAutoAssign.md for the fallback hierarchy.
create_recurring(template_name, cron_expression, package, project, created_by) - Schedule a recurring ticket from a templatecheck_due_recurring() - Find and create all due recurring ticketslist_recurring(package) - List all recurring schedulescancel_recurring(guid) - Deactivate a recurring scheduleUses the generic ObjRecurringMixin with _recurring_module_type = "TICKET". See ObjRecurringMixin.md.
add_watcher(email) - Add watcher emailremove_watcher(email) - Remove watcher emailget_watchers() - Parse watchers CSV into listcheck_escalation_due() - Get tickets due for escalation_send_escalation_email(contact) - Send escalation alert to SLA contactnotify_event(notify_code, action_by) - Send notification with full ticket context as JSON via ObjNotify; also sends Slack notification if slack.bottoken is configured_notify_slack(notify_code, context) - Send Slack notification via ObjServiceSlack (optional, fails silently)_build_ticket_context() - Build dict of all ticket fields for notification payload_resolve_requester_email() - Resolve email from RequestedByType_resolve_user_email(user) - Look up email from sys_user_load_email_template() - Load ticket.html from resource.templates/package.{package}/_render_email(event, context) - Render email subject and HTML body_send_ticket_email(subject, body) - Send to requester + assigned + watchersEmails are sent automatically when:
Create() is called (event: "Created")change_status() is called (event: "Status Changed")assign() is called (event: "Assigned")resolve() is called (event: "Resolved")close() is called (event: "Closed")escalate() is called (event: "Escalated" + escalation alert)Template path: resource.templates/package.{package}/ticket.html
Template variables: {ticket_number}, {subject}, {event}, {detail}, {status}, {priority}, {assigned_to}, {action_by}
send_ticket_report(recipients) - Generate and send a branded HTML email report including ticket details, history, tasks, comments, links, SLA status, RACI matrix, and signoff status. Uses ObjTemplate.build_email_html() for wrapping. If recipients omitted, sends to requester + assigned + watchers.get_raci_contacts(package, priority) - Load RACI contacts (responsible, accountable, consulted, informed) from def_ticket_slaescalate() now uses RACI contacts to determine escalation recipients, falling back to EscalationContact when RACI columns are emptyget_history(guid) - Get audit trailget_open_tickets(package) - List open ticketsget_overdue_tickets() - List SLA-breached ticketsget_status_summary(package) - Ticket counts by statusget_tickets_by_assignee(assignee) - Active tickets for an assigneeget_unassigned_tickets() - Active tickets with no assigneeget_sla_breach_count() - Count SLA-breached ticketsget_tickets_by_filter(package, status, priority, assignee) - Dynamic filterget_provider() - Return the configured ObjTicketProvider instance (internal/asana/sync) based on ticket.provider in config.yamlself._provider_name - Cached provider name from configObjTicket inherits ObjVersionMixin and is registered in VERSION_REGISTRY with:
main_table: data_ticketnodes_table: data_ticket_taskhistory_table: data_ticket_historyname_column: TicketNumberensure_sla_raci() - Called from __init__, adds RaciResponsible, RaciAccountable, RaciConsulted, RaciInformed columns to def_ticket_sla if missingassign() now attempts to resolve the assignee via ObjPerson.resolve_by_email(). If a matching person is found, the PersonGuid is used instead of the raw email/username._ensure_loaded() - Guard check, raises ValueError if no ticket loaded_parse_datetime(value) - Safe datetime parsing, returns None on failureAll HTML fragments live in the html: section of ObjTicket.yaml and are loaded via self.get_html("template_name"):
ticket_card - Full ticket detail card with status/priority badgesticket_status_badge - Coloured status badgeticket_priority_badge - Coloured priority badgeticket_history_row - Single audit trail rowticket_task_row - Single task rowticket_comment_row - Single comment rowticket_report_section - Section wrapper with header cellsticket_report_header_cell - Table header cellticket_email_fallback - Fallback email template (was inline HTML)python factory.core/ObjTicket.py read <guid>
python factory.core/ObjTicket.py create "Subject" --priority HIGH
python factory.core/ObjTicket.py open-tickets --package CORE
python factory.core/ObjTicket.py overdue
python factory.core/ObjTicket.py check-escalations
python factory.core/ObjTicket.py send-report <guid> --recipients "a@b.com,c@d.com"
python factory.core/ObjTicket.py log-time <guid> 45 --description "Fix auth bug"
python factory.core/ObjTicket.py set-eta <guid> "2026-05-01"
python factory.core/ObjTicket.py list-rules
python factory.core/ObjTicket.py add-rule ops@example.com --project INFRA --priority CRITICAL
python factory.core/ObjTicket.py remove-rule 3
python factory.core/ObjTicket.py list-recurring --package homechoice
python factory.core/ObjTicket.py add-recurring "TEMPLATE_NAME" "0 9 * * 1"
python factory.core/ObjTicket.py check-recurring
ObjTicket supports pluggable external providers via ObjTicketProvider. Set ticket.provider in config.yaml to route ticket operations to an external system.
| Provider | Config value | Service module | Description |
|---|---|---|---|
| Internal DB | internal (default) |
— | Local data_ticket table, full lifecycle |
| Asana | asana |
ObjServiceAsana |
Tasks in Asana projects, status → sections, priority → tags |
| Todoist | todoist |
ObjServiceTodoist |
Tasks via REST API v2, priority 1-4 mapping, labels for type |
| Google Tasks | google_tasks / google / gtasks |
ObjServiceGoogle |
Tasks API v1 via existing OAuth2 service account. Syncs with Gmail sidebar and Calendar. |
| Sync (dual-write) | sync |
— | Write to both internal DB and external provider |
| Provider | API | Notes |
|---|---|---|
| GitLab Issues | REST API v4 | Issue tracker with labels, milestones, boards. Natural fit for dev teams already on GitLab. |
| Gitea / Forgejo | REST API | Self-hosted Git with lightweight issue tracking. Good for air-gapped or on-prem deployments. |
| Redmine | REST API | Classic enterprise project management. Widely deployed in regulated industries. |
| Taiga | REST API | Open-source agile PM (kanban + scrum). Clean API, good community. |
| OpenProject | REST API v3 | Enterprise work package management with Gantt, budgets, time tracking. |
| Plane | REST API | Modern open-source Jira alternative. Clean UI, growing fast, self-hostable. |
| Vikunja | REST API | Lightweight task/to-do app. Simple API, self-hostable, good for small teams. |
ObjService<Name>.py in factory.service/package.core/ — REST API client with auth, rate limiting, sync trackingObjTicket<Name>.py in factory.core/extend.ticket/ — implements ObjTicketProvider abstract interfaceget_provider() in ObjTicketProvider.pyconfig.yaml (token, project ID, etc.)ObjTicketSla.py - SLA configuration per package/priority (includes RACI columns)ObjTicketTask.py - Task management within ticketsObjTicketComment.py - Comment managementObjTicketLink.py - Link/dependency managementObjTicketTemplate.py - Template management with auto-tasksObjNotify.py - Notification dispatch (used by notify_event)ObjWebMail.py - HTML email dispatch (used by email notifications)ObjServiceSlack.py - Slack notifications (optional, via notify_ticket() and send_approval_request())ObjTemplate.py - Branded email wrapping via build_email_html()ObjRaci - RACI mixin on ObjData providing get_raci(), build_raci_html(), build_signoff_html()ObjVersionMixin.py - Version history tracking mixinObjRecurringMixin.py - Generic cron-based recurring schedule mixin (shared def_recurring table)ObjTicketAutoAssign.py - Rule-based auto-assignment mixinObjTicketRecurringMixin.py - Ticket-specific recurring adapter (module type TICKET)ObjPerson.py - Person lookup (used by assign() for email resolution)ObjTicketProvider.py - Abstract provider base + factory functionObjEnum.py - Enum constantsA pre-built template in ObjTicket.yaml testdata section for homechoice collections bugs:
HCCOLLECTIONS_BUG, subject "Collections Bug", type BUG, priority HIGH