X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=rt%2Fdocs%2Fcustomizing%2Flifecycles.pod;fp=rt%2Fdocs%2Fcustomizing%2Flifecycles.pod;h=76e60003aa50906daed93fb5b6514dce4ac750b8;hp=0000000000000000000000000000000000000000;hb=ed1f84b4e8f626245995ecda5afcf83092c153b2;hpb=fe9ea9183e8a16616d6d04a7b5c7498d28e78248 diff --git a/rt/docs/customizing/lifecycles.pod b/rt/docs/customizing/lifecycles.pod new file mode 100644 index 000000000..76e60003a --- /dev/null +++ b/rt/docs/customizing/lifecycles.pod @@ -0,0 +1,478 @@ +=head1 Ticket Lifecycles + +By default, RT comes with ticket statuses that work for many types +of workflows: new, open, stalled, resolved, rejected, and deleted. +But there can be any number of workflows where these status values +don't completely fit. RT allows you to add new custom status values and +define their behavior with a feature called Lifecycles. + +=head1 Adding a New Status + +Because Statuses are controlled via lifecycles, you must manipulate the entire +lifecycle configuration to add a status. In earlier versions of RT new statuses +could be added by adding a new element to an array in RT's config file. But +because lifecyles are built around statuses, the entire lifecycle configuration +must be modified even if you only need new statuses. + +=head2 Copy Lifecycle Config + +First, copy the C<%Lifecycles> hash from C and paste it into +C. + +=head2 Add Status Value + +Add the status to the set where your new status belongs. This example adds +C to the active statuses: + + active => [ 'open', 'approved', 'stalled' ], + +=head2 Update Transitions + +Now the transitions section must be updated so that the new status can +transition to the existing statuses and also so the existing statuses can +transition to the new status. + + new => [qw( open approved stalled resolved rejected deleted)], + open => [qw(new approved stalled resolved rejected deleted)], + approved => [qw(new open stalled resolved rejected deleted)], + stalled => [qw(new open approved rejected resolved deleted)], + resolved => [qw(new open approved stalled rejected deleted)], + rejected => [qw(new open approved stalled resolved deleted)], + deleted => [qw(new open approved stalled rejected resolved )], + +=head1 Order Processing Example + +This guide demonstrates lifecycles using an order fulfillment +system as a real-world example. You can find full lifecycles +documentation in L. + +As with all RT custom configuration, if you are customizing the RT +lifecycle, make your changes in your C file, not +directly in C. If you are adding a new lifecycle, you can +add a new entry with: + + Set(%Lifecycles, my_new_lifecycle => { ... } ); + +The detailed configuration options are discussed below. Once you add it +and restart the server, the new lifecycle will be available on the +queue configuration page. + +To show how you might use custom lifecycles, we're going to configure +an RT lifecycle to process orders of some sort. In our order example, +each ticket in the queue is considered a separate order and the orders +have the following statuses: + +=over + +=item pending + +The order just came in untouched, pending purchase validation + +=item processing + +The order is being looked at for transaction processing + +=item delivery + +The order is out for delivery + +=item delivered + +The order was successfully delivered to its destination + +=item refunded + +The order was delivered but subsequently refunded + +=item declined + +There was an error in the process validation and the order was denied purchase + +=back + +In this particular example, the only status an order can start with is +'pending.' When a process coordinator chooses to take this order, it +goes into processing. The order can then either be delivered or denied +processing. Once denied, the lifecycle for that order ends. If it is +delivered, the order can still be refunded. + +The following sections walk through each part of the configuration. +You can find the full configuration at the end in case you want to +see the exact syntax or use it to experiment with. + +=head2 Defining Status Values + +Every queue has a lifecycle assigned to it. Without changing any +configuration, you are given two lifecycles to choose from: "default" +and "approvals." The approvals lifecycle is used by the internal +approvals queue, and should not be changed or used by other queues. Do +not modify the approvals lifecycle unless you fully understand how RT +approvals work. + +=for html Lifecycle choices + +=for :text [Lifecycle choices F] + +=for :man [Lifecycle choices F] + +In RT 4.0, the C<@ActiveStatus> and C<@InactiveStatus> configurations +which were previously available are gone. The logic defined by those +options is now a subset of RT's lifecycle features, as described here. + +A ticket naturally has three states: initial (I), active (I and +I), and inactive (I, I, and I). These +default settings look like this in the C file: + + default => { + initial => [ 'new' ], + active => [ 'open', 'stalled' ], + inactive => [ 'resolved', 'rejected', 'deleted' ], + +The initial state is the default starting place for new tickets, although +you can create tickets with other statuses. Initial is generally used +to acknowledge that a request has been made, but not yet acted on. RT +sets the Started date on a ticket when it is moved out of the initial state. + +Active tickets are currently being worked on, inactive tickets have reached +some final state. By default, inactive tickets don't show up in search +results. The AutoOpen action sets a ticket's status to the first active +status. You can find more details in L. + +Now we want to set up some statuses appropriate for order fulfillment, +so we create a new top-level key called C and add our new status +values. + + Set( %Lifecycles, orders => { + initial => [ 'pending' ], + active => [ 'processing', 'delivery' ], + inactive => [ 'delivered', 'returned', 'declined', 'deleted' ], + # ..., + }); + +We still use the initial, active and inactive categories, but we are +able to define status values that are appropriate for the workflow +we want to create. This should make the system more intuitive for users. + +=head2 Transitions + +The typical lifecycle follows the path initial -> active -> inactive. +Obviously the path of a ticket can get more complicated than this, which +is where transitions come into play. + +Transitions manage the flow of a ticket from status to status. This +section of the configuration has keys, which are the current status, +and values that define which other statuses the ticket can transition +to. Here are the transitions we define for our order process. + + Set( %Lifecycles, orders => { + # ..., + transitions => { + '' => [qw(pending processing declined)], + pending => [qw(processing declined deleted)], + processing => [qw(pending declined delivery delivered deleted)], + delivery => [qw(pending delivered returned deleted)], + delivered => [qw(pending returned deleted)], + returned => [qw(pending delivery deleted)], + deleted => [qw(pending processing delivered delivery returned)], + }, + # ..., + }); + +If a ticket is in the delivered status, it doesn't make sense for it to +transition to processing or declined since the customer already has the +order. However, it can transition to returned since they could send it back. +The configuration above defines this for RT. + +The C<''> entry defines the valid statuses when a ticket is created. + +Deleted is a special status in RT that allows you to remove a ticket from +active use. You may need to do this if a ticket is created by mistake, or +a duplicate is created. Once deleted, a ticket will never show up in search +results. As you can see, the system will allow you to +transition to deleted from any status. + +=head2 Rights and Access Control + +Your workflow may have several people working on tickets at different +steps, and for some you may want to make sure only certain users +can perform certain actions. For example, the company may have a rule +that only the quality assurance team is allowed to approve (or decline) +an order for delivery. + +You can apply labels to transitions and assign rights to them to allow +you to apply this sort of access control. This is done with a rights +entry: + + Set( %Lifecycles, orders => { + # ..., + rights => { + '* -> declined' => 'DeclineOrder', + '* -> delivery' => 'ApproveOrder', + }, + # ..., + }); + +This configuration tells RT to require the right DeclineOrder for a +transition from any status (C<*>) to C. The ApproveOrder +right is similar, but for C. These rights take the place of +the standard ModifyTicket right, not in addition to it, so keep that +in mind when creating and assigning new rights. + +Once these rights are configured and loaded (by restarting the web +server), they can be assigned in the web UI to groups, queues, and users. +The rights show up on the rights pages in a Status tab alongside the +standard RT rights tabs. + +=for html Lifecycle group rights + +=for :text [Lifecycle group rights F] + +=for :man [Lifecycle group rights F] + +After a status transition right is granted, users with the right will see +the status in the drop-down, and possibly any related actions (see +L). + +=head2 Default Status + +There are interfaces to RT from which it isn't possible to define a status, +like sending an email to create a ticket, but tickets +require a status. To handle these cases, you can set +default status values for RT to use when the user doesn't explicitly set +a value. + +Looking at the defaults section in the standard RT configuration, +you can see the events for which you can define a default status. +For example, 'on_create' => 'new' automatically gives newly created tickets +a C status when the requestor doesn't supply a status. We can do the same +for our process. + + Set( %Lifecycles, orders => { + defaults => { + on_create => 'pending', + }, + # ..., + }); + +Only a small number of defaults are needed because in practice there are +relatively few cases where a ticket will find itself without a status or +in an ambiguous state. + +=head2 Actions + +To customize how transitions are presented in RT, lifecycles have an +C section where you can customize how an action (e.g. changing +status from new -> open) looks and functions. You can customize the action's +label, which is how it appears to users, and the type of update, either comment +or reply. As an example, in the default RT configuration the action +"new -> open" has the default label "Open it" and an update value of C. + +Using the lifecycles configuration, you can change the label to anything you +like. You can set the update option to C or C, which tells RT +to process the action as a comment (not sent to requestors) or a reply (sent +to requestors). + +This part of the lifecycles configuration replaces the previous +C<$ResolveDefaultUpdateType> configuration value. To mimic that option, set +the update type to C for all transitions to C. + +Here is an example of a change we might make for our order process: + + Set( %Lifecycles, orders => { + # ..., + actions => [ + 'pending -> processing' => { + label => 'Open For Processing', + update => 'Comment', + }, + 'pending -> declined' => { + label => 'Decline', + update => 'Respond', + }, + # ... + ], + # ... + }); + +Alternatively, supplying no update type results in a "quick" +action that changes the status immediately without going through the +ticket update page. RT's default "Delete" action is a "quick" action, +for example: + + # from the RT "default" lifecycle + 'new -> deleted' => { + label => 'Delete', + }, + +If the transition has an associated right, it must be granted for a user to +see the action. For example, if we give a group the DeclineOrder right as +shown in the earlier example, members of that group will see a Decline option +in their Actions menu if a ticket has a pending status. The +L at the end shows other action entries that +make the Decline option available in more cases. + +=for html Action menu decline + +=for :text [Action menu decline F] + +=for :man [Action menu decline F] + +=head2 Mapping Between Queues + +As we've demonstrated, each queue can have its own custom lifecycle, but +in RT you sometimes want to move a ticket from one queue to another. +A ticket will have a status in a given queue, but that status may not +exist in another queue you want to move the ticket to, or it may exist +but mean something different. To allow tickets to move between queues with +different lifecycles, RT needs to know how to set the status appropriately. + +The lifecycle configuration has a C<__maps__> entry to allow you to +specify the mappings you want between different queues. Sometimes statuses +between queues don't or can't match perfectly, but if you need to move +tickets between those queues, it's important that you provide a complete +mapping, defining the most sensible mapping you can. + +If you don't provide a mapping, users will see an error when they try to +move a ticket between queues with different lifecycles but no mapping. + + Set( %Lifecycles, orders => { + # ..., + __maps__ => { + 'default -> orders' => { + 'new' => 'pending', + 'open' => 'processing', + # ..., + }, + 'orders -> default' => { + 'pending' => 'new', + 'processing' => 'open', + # ..., + }, + # ..., + }, + # ..., + }); + +In the example above, we first define mappings between the default queue and +our new orders queue. The second block defines the reverse for tickets that +might be moved from the orders queue to a queue that uses the default lifecycle. + +=head2 Full Configuration + +Here is the full configuration if you want to add it to your RT instance +to experiment. + + Set(%Lifecycles, + + # 'orders' shows up as a lifecycle choice when you create a new + # queue or modify an existing one + orders => { + # All the appropriate order statuses + initial => [ 'pending' ], + active => [ 'processing', 'delivery' ], + inactive => [ 'delivered', 'returned', 'declined' ], + + # Default order statuses for certain actions + defaults => { + on_create => 'pending', + }, + + # Status change restrictions + transitions => { + '' => [qw(pending processing declined)], + pending => [qw(processing declined deleted)], + processing => [qw(pending declined delivery delivered deleted)], + delivery => [qw(pending delivered returned deleted)], + delivered => [qw(pending returned deleted)], + returned => [qw(pending delivery deleted)], + deleted => [qw(pending processing delivered delivery returned)], + }, + + # Rights for different actions + rights => { + + # These rights are in the default lifecycle + '* -> deleted' => 'DeleteTicket', + '* -> *' => 'ModifyTicket', + + # Maybe we want to create rights to keep QA rigid + '* -> declined' => 'DeclineOrder', + '* -> delivery' => 'ApproveOrder', + }, + + # Actions for the web UI + actions => [ + 'pending -> processing' => { + label => 'Open For Processing', + update => 'Comment', + }, + 'pending -> delivered' => { + label => 'Mark as being delivered', + update => 'Comment', + }, + 'pending -> declined' => { + label => 'Decline', + update => 'Respond', + }, + 'pending -> deleted' => { + label => 'Delete', + }, + 'processing -> declined' => { + label => 'Decline', + update => 'Respond', + }, + 'processing -> delivery' => { + label => 'Out for delivery', + update => 'Comment', + }, + 'delivery -> delivered' => { + label => 'Mark as delivered', + update => 'Comment', + }, + 'delivery -> returned' => { + label => 'Returned to Manufacturer', + update => 'Respond', + }, + 'delivered -> returned' => { + label => 'Returned to Manufacturer', + update => 'Respond', + }, + 'returned -> delivery' => { + label => 'Re-deliver Order', + update => 'Respond', + }, + 'deleted -> pending' => { + label => 'Undelete', + update => 'Respond', + }, + ], + }, + + # Status mapping different different lifecycles + __maps__ => { + 'default -> orders' => { + 'new' => 'pending', + 'open' => 'processing', + 'stalled' => 'processing', + 'resolved' => 'delivered', + 'rejected' => 'declined', + 'deleted' => 'deleted', + }, + 'orders -> default' => { + 'pending' => 'new', + 'processing' => 'open', + 'delivered' => 'resolved', + 'returned' => 'open', # closest matching we have in 'default' + 'declined' => 'rejected', + 'deleted' => 'deleted', + }, + }, + ); + +Here is an example history of a ticket following this lifecycle: + +=for html Lifecycle history + +=for :text [Lifecycle history F] + +=for :man [Lifecycle history F]