RT 4.0.22
[freeside.git] / rt / docs / customizing / lifecycles.pod
1 =head1 Ticket Lifecycles
2
3 By default, RT comes with ticket statuses that work for many types
4 of workflows: new, open, stalled, resolved, rejected, and deleted.
5 But there can be any number of workflows where these status values
6 don't completely fit. RT allows you to add new custom status values and
7 define their behavior with a feature called Lifecycles.
8
9 =head1 Adding a New Status
10
11 Because Statuses are controlled via lifecycles, you must manipulate the entire
12 lifecycle configuration to add a status. In earlier versions of RT new statuses
13 could be added by adding a new element to an array in RT's config file. But
14 because lifecyles are built around statuses, the entire lifecycle configuration
15 must be modified even if you only need new statuses.
16
17 =head2 Copy Lifecycle Config
18
19 First, copy the C<%Lifecycles> hash from C<RT_Config.pm> and paste it into
20 C<RT_SiteConfig.pm>.
21
22 =head2 Add Status Value
23
24 Add the status to the set where your new status belongs. This example adds
25 C<approved> to the active statuses:
26
27     active => [ 'open', 'approved', 'stalled' ],
28
29 =head2 Update Transitions
30
31 Now the transitions section must be updated so that the new status can
32 transition to the existing statuses and also so the existing statuses can
33 transition to the new status.
34
35     new      => [qw(    open approved stalled resolved rejected deleted)],
36     open     => [qw(new      approved stalled resolved rejected deleted)],
37     approved => [qw(new open          stalled resolved rejected deleted)],
38     stalled  => [qw(new open approved         rejected resolved deleted)],
39     resolved => [qw(new open approved stalled          rejected deleted)],
40     rejected => [qw(new open approved stalled resolved          deleted)],
41     deleted  => [qw(new open approved stalled rejected resolved        )],
42
43 =head1 Order Processing Example
44
45 This guide demonstrates lifecycles using an order fulfillment
46 system as a real-world example. You can find full lifecycles
47 documentation in L<RT_Config/Lifecycles>.
48
49 As with all RT custom configuration, if you are customizing the RT
50 lifecycle, make your changes in your C<RT_SiteConfig.pm> file, not
51 directly in C<RT_Config.pm>. If you are adding a new lifecycle, you can
52 add a new entry with:
53
54     Set(%Lifecycles, my_new_lifecycle => { ... } );
55
56 The detailed configuration options are discussed below. Once you add it
57 and restart the server, the new lifecycle will be available on the
58 queue configuration page.
59
60 To show how you might use custom lifecycles, we're going to configure
61 an RT lifecycle to process orders of some sort. In our order example,
62 each ticket in the queue is considered a separate order and the orders
63 have the following statuses:
64
65 =over
66
67 =item pending
68
69 The order just came in untouched, pending purchase validation
70
71 =item processing
72
73 The order is being looked at for transaction processing
74
75 =item delivery
76
77 The order is out for delivery
78
79 =item delivered
80
81 The order was successfully delivered to its destination
82
83 =item refunded
84
85 The order was delivered but subsequently refunded
86
87 =item declined
88
89 There was an error in the process validation and the order was denied purchase
90
91 =back
92
93 In this particular example, the only status an order can start with is
94 'pending.'  When a process coordinator chooses to take this order, it
95 goes into processing. The order can then either be delivered or denied
96 processing. Once denied, the lifecycle for that order ends. If it is
97 delivered, the order can still be refunded.
98
99 The following sections walk through each part of the configuration.
100 You can find the full configuration at the end in case you want to
101 see the exact syntax or use it to experiment with.
102
103 =head2 Defining Status Values
104
105 Every queue has a lifecycle assigned to it. Without changing any
106 configuration, you are given two lifecycles to choose from: "default"
107 and "approvals." The approvals lifecycle is used by the internal
108 approvals queue, and should not be changed or used by other queues. Do
109 not modify the approvals lifecycle unless you fully understand how RT
110 approvals work.
111
112 =for html <img alt="Lifecycle choices" src="../images/lifecycle-choices.png">
113
114 =for :text [Lifecycle choices F<docs/images/lifecycle-choices.png>]
115
116 =for :man [Lifecycle choices F<docs/images/lifecycle-choices.png>]
117
118 In RT 4.0, the C<@ActiveStatus> and C<@InactiveStatus> configurations
119 which were previously available are gone. The logic defined by those
120 options is now a subset of RT's lifecycle features, as described here.
121
122 A ticket naturally has three states: initial (I<new>), active (I<open> and
123 I<stalled>), and inactive (I<resolved>, I<rejected>, and I<deleted>). These
124 default settings look like this in the C<RT_Config.pm> file:
125
126     default => {
127         initial         => [ 'new' ],
128         active          => [ 'open', 'stalled' ],
129         inactive        => [ 'resolved', 'rejected', 'deleted' ],
130
131 The initial state is the default starting place for new tickets, although
132 you can create tickets with other statuses. Initial is generally used
133 to acknowledge that a request has been made, but not yet acted on. RT
134 sets the Started date on a ticket when it is moved out of the initial state.
135
136 Active tickets are currently being worked on, inactive tickets have reached
137 some final state. By default, inactive tickets don't show up in search
138 results. The AutoOpen action sets a ticket's status to the first active
139 status. You can find more details in L<RT_Config/"Lifecycle definitions">.
140
141 Now we want to set up some statuses appropriate for order fulfillment,
142 so we create a new top-level key called C<orders> and add our new status
143 values.
144
145     Set( %Lifecycles, orders => {
146              initial  => [ 'pending' ],
147              active   => [ 'processing', 'delivery' ],
148              inactive => [ 'delivered', 'returned', 'declined', 'deleted' ],
149              # ...,
150     });
151
152 We still use the initial, active and inactive categories, but we are
153 able to define status values that are appropriate for the workflow
154 we want to create. This should make the system more intuitive for users.
155
156 =head2 Transitions
157
158 The typical lifecycle follows the path initial -> active -> inactive.
159 Obviously the path of a ticket can get more complicated than this, which
160 is where transitions come into play.
161
162 Transitions manage the flow of a ticket from status to status. This
163 section of the configuration has keys, which are the current status,
164 and values that define which other statuses the ticket can transition
165 to. Here are the transitions we define for our order process.
166
167     Set( %Lifecycles, orders => {
168         # ...,
169         transitions => {
170             ''          => [qw(pending processing declined)],
171             pending     => [qw(processing declined deleted)],
172             processing  => [qw(pending declined delivery delivered deleted)],
173             delivery    => [qw(pending delivered returned deleted)],
174             delivered   => [qw(pending returned deleted)],
175             returned    => [qw(pending delivery deleted)],
176             deleted     => [qw(pending processing delivered delivery returned)],
177         },
178         # ...,
179     });
180
181 If a ticket is in the delivered status, it doesn't make sense for it to
182 transition to processing or declined since the customer already has the
183 order. However, it can transition to returned since they could send it back.
184 The configuration above defines this for RT.
185
186 The C<''> entry defines the valid statuses when a ticket is created.
187
188 Deleted is a special status in RT that allows you to remove a ticket from
189 active use. You may need to do this if a ticket is created by mistake, or
190 a duplicate is created. Once deleted, a ticket will never show up in search
191 results. As you can see, the system will allow you to
192 transition to deleted from any status.
193
194 =head2 Rights and Access Control
195
196 Your workflow may have several people working on tickets at different
197 steps, and for some you may want to make sure only certain users
198 can perform certain actions. For example, the company may have a rule
199 that only the quality assurance team is allowed to approve (or decline)
200 an order for delivery.
201
202 You can apply labels to transitions and assign rights to them to allow
203 you to apply this sort of access control. This is done with a rights
204 entry:
205
206     Set( %Lifecycles, orders => {
207         # ...,
208         rights => {
209             '* -> declined' => 'DeclineOrder',
210             '* -> delivery' => 'ApproveOrder',
211         },
212         # ...,
213     });
214
215 This configuration tells RT to require the right DeclineOrder for a
216 transition from any status (C<*>) to C<declined>. The ApproveOrder
217 right is similar, but for C<delivery>. These rights take the place of
218 the standard ModifyTicket right, not in addition to it, so keep that
219 in mind when creating  and assigning new rights.
220
221 Once these rights are configured and loaded (by restarting the web
222 server), they can be assigned in the web UI to groups, queues, and users.
223 The rights show up on the rights pages in a Status tab alongside the
224 standard RT rights tabs.
225
226 =for html <img alt="Lifecycle group rights" src="../images/global-lifecycle-group-rights.png">
227
228 =for :text [Lifecycle group rights F<docs/images/global-lifecycle-group-rights.png>]
229
230 =for :man [Lifecycle group rights F<docs/images/global-lifecycle-group-rights.png>]
231
232 After a status transition right is granted, users with the right will see
233 the status in the drop-down, and possibly any related actions (see
234 L</Actions>).
235
236 =head2 Default Status
237
238 There are interfaces to RT from which it isn't possible to define a status,
239 like sending an email to create a ticket, but tickets
240 require a status. To handle these cases, you can set
241 default status values for RT to use when the user doesn't explicitly set
242 a value.
243
244 Looking at the defaults section in the standard RT configuration,
245 you can see the events for which you can define a default status.
246 For example, 'on_create' => 'new' automatically gives newly created tickets
247 a C<new> status when the requestor doesn't supply a status. We can do the same
248 for our process.
249
250     Set( %Lifecycles, orders => {
251         defaults => {
252             on_create => 'pending',
253         },
254         # ...,
255     });
256
257 Only a small number of defaults are needed because in practice there are
258 relatively few cases where a ticket will find itself without a status or
259 in an ambiguous state.
260
261 =head2 Actions
262
263 To customize how transitions are presented in RT, lifecycles have an
264 C<actions> section where you can customize how an action (e.g. changing
265 status from new -> open) looks and functions. You can customize the action's
266 label, which is how it appears to users, and the type of update, either comment
267 or reply. As an example, in the default RT configuration the action
268 "new -> open" has the default label "Open it" and an update value of C<Respond>.
269
270 Using the lifecycles configuration, you can change the label to anything you
271 like. You can set the update option to C<Comment> or C<Respond>, which tells RT
272 to process the action as a comment (not sent to requestors) or a reply (sent
273 to requestors).
274
275 This part of the lifecycles configuration replaces the previous
276 C<$ResolveDefaultUpdateType> configuration value. To mimic that option, set
277 the update type to C<Comment> for all transitions to C<resolved>.
278
279 Here is an example of a change we might make for our order process:
280
281     Set( %Lifecycles, orders => {
282         # ...,
283         actions => [
284             'pending -> processing' => {
285                 label  => 'Open For Processing',
286                 update => 'Comment',
287             },
288             'pending -> declined' => {
289                 label  => 'Decline',
290                 update => 'Respond',
291             },
292             # ...
293         ],
294         # ...
295     });
296
297 Alternatively, supplying no update type results in a "quick"
298 action that changes the status immediately without going through the
299 ticket update page.  RT's default "Delete" action is a "quick" action,
300 for example:
301
302     # from the RT "default" lifecycle
303     'new -> deleted'   => {
304         label  => 'Delete',
305     },
306
307 If the transition has an associated right, it must be granted for a user to
308 see the action. For example, if we give a group the DeclineOrder right as
309 shown in the earlier example, members of that group will see a Decline option
310 in their Actions menu if a ticket has a pending status. The
311 L</"Full Configuration"> at the end shows other action entries that
312 make the Decline option available in more cases.
313
314 =for html <img alt="Action menu decline" src="../images/action-decline.png">
315
316 =for :text [Action menu decline F<docs/images/action-decline.png>]
317
318 =for :man [Action menu decline F<docs/images/action-decline.png>]
319
320 =head2 Mapping Between Queues
321
322 As we've demonstrated, each queue can have its own custom lifecycle, but
323 in RT you sometimes want to move a ticket from one queue to another.
324 A ticket will have a status in a given queue, but that status may not
325 exist in another queue you want to move the ticket to, or it may exist
326 but mean something different. To allow tickets to move between queues with
327 different lifecycles, RT needs to know how to set the status appropriately.
328
329 The lifecycle configuration has a C<__maps__> entry to allow you to
330 specify the mappings you want between different queues. Sometimes statuses
331 between queues don't or can't match perfectly, but if you need to move
332 tickets between those queues, it's important that you provide a complete
333 mapping, defining the most sensible mapping you can.
334
335 If you don't provide a mapping, users will see an error when they try to
336 move a ticket between queues with different lifecycles but no mapping.
337
338     Set( %Lifecycles, orders => {
339         # ...,
340         __maps__ => {
341             'default -> orders' => {
342                 'new'  => 'pending',
343                 'open' => 'processing',
344                 # ...,
345             },
346             'orders -> default' => {
347                 'pending'    => 'new',
348                 'processing' => 'open',
349                 # ...,
350             },
351             # ...,
352         },
353         # ...,
354     });
355
356 In the example above, we first define mappings between the default queue and
357 our new orders queue. The second block defines the reverse for tickets that
358 might be moved from the orders queue to a queue that uses the default lifecycle.
359
360 =head2 Full Configuration
361
362 Here is the full configuration if you want to add it to your RT instance
363 to experiment.
364
365     Set(%Lifecycles,
366
367         # 'orders' shows up as a lifecycle choice when you create a new
368         # queue or modify an existing one
369         orders => {
370             # All the appropriate order statuses
371             initial         => [ 'pending' ],
372             active          => [ 'processing', 'delivery' ],
373             inactive        => [ 'delivered', 'returned', 'declined' ],
374
375             # Default order statuses for certain actions
376             defaults => {
377                 on_create => 'pending',
378             },
379
380             # Status change restrictions
381             transitions => {
382                 ''          => [qw(pending processing declined)],
383                 pending     => [qw(processing declined deleted)],
384                 processing  => [qw(pending declined delivery delivered deleted)],
385                 delivery    => [qw(pending delivered returned deleted)],
386                 delivered   => [qw(pending returned deleted)],
387                 returned    => [qw(pending delivery deleted)],
388                 deleted     => [qw(pending processing delivered delivery returned)],
389             },
390
391             # Rights for different actions
392             rights => {
393
394                 # These rights are in the default lifecycle
395                 '* -> deleted'  => 'DeleteTicket',
396                 '* -> *'        => 'ModifyTicket',
397
398                 # Maybe we want to create rights to keep QA rigid
399                 '* -> declined' => 'DeclineOrder',
400                 '* -> delivery' => 'ApproveOrder',
401             },
402
403             # Actions for the web UI
404             actions => [
405                 'pending -> processing' => {
406                     label  => 'Open For Processing',
407                     update => 'Comment',
408                 },
409                 'pending -> delivered' => {
410                     label  => 'Mark as being delivered',
411                     update => 'Comment',
412                 },
413                 'pending -> declined' => {
414                     label  => 'Decline',
415                     update => 'Respond',
416                 },
417                 'pending -> deleted' => {
418                     label  => 'Delete',
419                 },
420                 'processing -> declined' => {
421                     label  => 'Decline',
422                     update => 'Respond',
423                 },
424                 'processing -> delivery' => {
425                     label  => 'Out for delivery',
426                     update => 'Comment',
427                 },
428                 'delivery -> delivered' => {
429                     label  => 'Mark as delivered',
430                     update => 'Comment',
431                 },
432                 'delivery -> returned' => {
433                     label  => 'Returned to Manufacturer',
434                     update => 'Respond',
435                 },
436                 'delivered -> returned' => {
437                     label  => 'Returned to Manufacturer',
438                     update => 'Respond',
439                 },
440                 'returned -> delivery' => {
441                     label  => 'Re-deliver Order',
442                     update => 'Respond',
443                 },
444                 'deleted -> pending' => {
445                     label  => 'Undelete',
446                     update => 'Respond',
447                 },
448             ],
449         },
450
451         # Status mapping different different lifecycles
452         __maps__ => {
453             'default -> orders' => {
454                 'new'      => 'pending',
455                 'open'     => 'processing',
456                 'stalled'  => 'processing',
457                 'resolved' => 'delivered',
458                 'rejected' => 'declined',
459                 'deleted'  => 'deleted',
460             },
461             'orders -> default' => {
462                 'pending'    => 'new',
463                 'processing' => 'open',
464                 'delivered'  => 'resolved',
465                 'returned'   => 'open', # closest matching we have in 'default'
466                 'declined'   => 'rejected',
467                 'deleted'    => 'deleted',
468             },
469         },
470     );
471
472 Here is an example history of a ticket following this lifecycle:
473
474 =for html <img alt="Lifecycle history" src="../images/order-history-example.png">
475
476 =for :text [Lifecycle history F<docs/images/order-history-example.png>]
477
478 =for :man [Lifecycle history F<docs/images/order-history-example.png>]