feat(workflows): ticket.created workflow rules with in-line executor (PMS-448 phase 1) #341
No reviewers
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
psa-systems/mokosh-server!341
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/PMS-448-ticket-created-rules"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds the first cut of the workflow engine the ticket asked for. A focused single trigger (
ticket.created), structured conditions on the new ticket's well-known dimensions (priority / queue / company / source / type), and structured actions that mutate the same row (assign_to_user_id,assign_to_team_id,set_priority_id,add_tag,add_internal_note). The TicketService's create path runs the matching rules in-line INSIDE the same transaction, so an action that reassigns the ticket commits atomically with the ticket row - there is no observable window in which the ticket exists without its workflow side-effects.Two tables ship:
workflow_rules- the operator-facing rule definitions. Conditions + actions are JSONB so the SPA's rule-builder UI can iterate without a server redeploy; the executor validates them at run time.priority(lower runs first) + tie-break oncreated_atgives deterministic ordering. Phase 2 will widentrigger_eventto other events (ticket.status_changed,ticket.priority_changed,time_entry.created); the data model already supports it.workflow_rule_runs- one row per rule the executor processed against a given entity. Captures the actions blob that was applied + any per-action error so the SPA can render "what auto-routed this ticket" on the ticket detail page. A buggy rule's failure lands here as theerrorfield rather than aborting the create; the new/api/v1/tickets/{id}/workflow-runsendpoint surfaces the timeline.CRUD endpoints under
/api/v1/workflow-rulesare admin-only because the rule definitions can reassign tickets and add internal notes - that surface should not be reachable by a typical agent. TheRequireAdminextractor is wrapped with a parallelRequireAuthso the handler can pull the user id out of the auth state without taking the inner tuple ofRequireRole.Conditions evaluation has a deliberate fail-closed posture: an unknown key (typo) returns "miss", not "match", so a bad rule does not silently fire on every ticket. Unit tests under
executor.rspin the matrix (empty=match-all, in-list-match, out-of-list miss, AND across keys, source string match, unknown key miss). The integration test undertests/workflow_rules.rsexercises the full pipeline (real ticket POST, real rule SELECT, real action UPDATE, realworkflow_rule_runsINSERT) and confirms a mismatched rule produces NO run row while a matching rule produces exactly one.Phase 2 of PMS-448, tracked under the same ticket:
ticket.status_changed,ticket.priority_changed,time_entry.created);#PMS-448
c8b789e97a7f28f3475b