[Rt-commit] rt branch, master, updated. rt-4.2.11-160-g41e59c4

Shawn Moore shawn at bestpractical.com
Fri Aug 14 13:07:08 EDT 2015


The branch, master has been updated
       via  41e59c48bbd115a0b4bec37b9c112dad2470f7da (commit)
       via  e4b30a514897f871b5f07b59ac5b4a2b0431e6cf (commit)
       via  3f47233c2aa4bd5315f0054df85f465aabce645b (commit)
       via  8bdf41d79748ca6cc2fb4485a6006798e6b5e3a1 (commit)
       via  e8896b7f80879e03bff5790311eb256016d985dd (commit)
       via  81ad7f77387f7e54f90af92472c336e7c071347a (commit)
       via  b2e6735af10e29fa8451302d15adc6af116c0cf9 (commit)
       via  633e4389159c26e10b995854fda304737c67972e (commit)
       via  a07510707e6f8db00684127c0f13c33528794f11 (commit)
       via  821d3edbd6d7dfbfc9bf16501ddcc242b17707df (commit)
       via  df41846df9562a7386627db03730fbd35190b0aa (commit)
      from  082c018570bc54484f21e52bf2a410517da122a5 (commit)

Summary of changes:
 .gitignore                                         |   1 +
 configure.ac                                       |   1 +
 docs/UPGRADING-4.4                                 |  11 +
 etc/RT_Config.pm.in                                | 334 ++++++++++++++++-
 etc/RT_SiteConfig.pm                               |   1 -
 etc/initialdata                                    |  26 ++
 etc/schema.Oracle                                  |   2 +
 etc/schema.Pg                                      |   2 +
 etc/schema.SQLite                                  |   2 +
 etc/schema.mysql                                   |   2 +
 etc/upgrade/4.3.8/content                          |  37 ++
 etc/upgrade/4.3.8/schema.Oracle                    |   2 +
 etc/upgrade/4.3.8/schema.Pg                        |   2 +
 etc/upgrade/4.3.8/schema.SQLite                    |   2 +
 etc/upgrade/4.3.8/schema.mysql                     |   2 +
 etc/upgrade/upgrade-sla.in                         | 142 ++++++++
 lib/RT.pm                                          |   7 +
 lib/RT/Action/{OpenOnStarted.pm => SLA.pm}         |  59 +--
 lib/RT/Action/SLA_SetDue.pm                        | 159 +++++++++
 .../Action/{OpenOnStarted.pm => SLA_SetStarts.pm}  |  49 +--
 lib/RT/{I18N/de.pm => Condition/SLA.pm}            |  12 +-
 .../Condition/{Overdue.pm => SLA_RequireDueSet.pm} |  45 +--
 .../SLA_RequireStartsSet.pm}                       |  38 +-
 lib/RT/Interface/Web.pm                            |   2 +
 lib/RT/Queue.pm                                    |  26 ++
 lib/RT/SLA.pm                                      | 252 +++++++++++++
 lib/RT/Ticket.pm                                   |   9 +-
 lib/RT/Tickets.pm                                  |   1 +
 sbin/rt-test-dependencies.in                       |   1 +
 share/html/Admin/Queues/Modify.html                |  16 +-
 .../EditCustomFields => Elements/SelectSLA}        |  33 +-
 share/html/Ticket/Create.html                      |  12 +-
 share/html/Ticket/Elements/EditBasics              |  10 +
 share/html/Ticket/Elements/ShowBasics              |   6 +
 t/sla/business_hours.t                             |  64 ++++
 t/sla/due.t                                        | 396 +++++++++++++++++++++
 t/sla/ignore-on-statuses.t                         | 261 ++++++++++++++
 t/sla/queue.t                                      |  52 +++
 t/sla/starts.t                                     |  79 ++++
 t/sla/timezone.t                                   |  51 +++
 t/sla/web.t                                        | 106 ++++++
 41 files changed, 2193 insertions(+), 122 deletions(-)
 create mode 100644 etc/upgrade/4.3.8/content
 create mode 100644 etc/upgrade/4.3.8/schema.Oracle
 create mode 100644 etc/upgrade/4.3.8/schema.Pg
 create mode 100644 etc/upgrade/4.3.8/schema.SQLite
 create mode 100644 etc/upgrade/4.3.8/schema.mysql
 create mode 100644 etc/upgrade/upgrade-sla.in
 copy lib/RT/Action/{OpenOnStarted.pm => SLA.pm} (69%)
 create mode 100644 lib/RT/Action/SLA_SetDue.pm
 copy lib/RT/Action/{OpenOnStarted.pm => SLA_SetStarts.pm} (71%)
 copy lib/RT/{I18N/de.pm => Condition/SLA.pm} (94%)
 copy lib/RT/Condition/{Overdue.pm => SLA_RequireDueSet.pm} (71%)
 copy lib/RT/{Principals.pm => Condition/SLA_RequireStartsSet.pm} (84%)
 create mode 100644 lib/RT/SLA.pm
 copy share/html/{Ticket/Elements/EditCustomFields => Elements/SelectSLA} (79%)
 create mode 100644 t/sla/business_hours.t
 create mode 100644 t/sla/due.t
 create mode 100644 t/sla/ignore-on-statuses.t
 create mode 100644 t/sla/queue.t
 create mode 100644 t/sla/starts.t
 create mode 100644 t/sla/timezone.t
 create mode 100644 t/sla/web.t

- Log -----------------------------------------------------------------
commit b2e6735af10e29fa8451302d15adc6af116c0cf9
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Mon Aug 3 00:59:22 2015 +0800

    update doc/config and move it to RT_Config.pm

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 5deaade..762ab93 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2918,9 +2918,338 @@ Set(%Lifecycles,
     },
 );
 
+=head1 SLA
 
+=over 4
+
+=item C<%ServiceAgreements>
+
+    Set( %ServiceAgreements = (
+        Default => '4h',
+        QueueDefault => {
+            'Incident' => '2h',
+        },
+        Levels => {
+            '2h' => { Resolve => { RealMinutes => 60*2 } },
+            '4h' => { Resolve => { RealMinutes => 60*4 } },
+        },
+    ));
+
+In this example I<Incident> is the name of the queue, and I<2h> is the name of
+the SLA which will be applied to this queue by default.
+
+Each service level can be described using several options:
+L<Starts|/"Starts (interval, first business minute)">,
+L<Resolve|/"Resolve and Response (interval, no defaults)">,
+L<Response|/"Resolve and Response (interval, no defaults)">,
+L<KeepInLoop|/"Keep in loop (interval, no defaults)">,
+L<OutOfHours|/"OutOfHours (struct, no default)">
+and L<ServiceBusinessHours|/"Configuring business hours">.
+
+=over 4
+
+=item Starts (interval, first business minute)
+
+By default when a ticket is created Starts date is set to
+first business minute after time of creation. In other
+words if a ticket is created during business hours then
+Starts will be equal to Created time, otherwise Starts will
+be beginning of the next business day.
+
+However, if you provide 24/7 support then you most
+probably would be interested in Starts to be always equal
+to Created time.
+
+Starts option can be used to adjust behaviour. Format
+of the option is the same as format for deadlines which
+described later in details. RealMinutes, BusinessMinutes
+options and OutOfHours modifiers can be used here like
+for any other deadline. For example:
+
+    'standard' => {
+        # give people 15 minutes
+        Starts   => { BusinessMinutes => 15  },
+    },
+
+You can still use old option StartImmediately to set
+Starts date equal to Created date.
+
+Example:
+
+    '24/7' => {
+        StartImmediately => 1,
+        Response => { RealMinutes => 30 },
+    },
+
+But it's the same as:
+
+    '24/7' => {
+        Starts => { RealMinutes => 0 },
+        Response => { RealMinutes => 30 },
+    },
+
+=item Resolve and Response (interval, no defaults)
+
+These two options define deadlines for resolve of a ticket
+and reply to customer(requestors) questions accordingly.
+
+You can define them using real time, business or both. Read more
+about the latter L<below|/"Using both Resolve and Response in the same level">.
+
+The Due date field is used to store calculated deadlines.
+
+=over 4
+
+=item Resolve
+
+Defines deadline when a ticket should be resolved. This option is
+quite simple and straightforward when used without L</Response>.
+
+Example:
+
+    # 8 business hours
+    'simple' => { Resolve => 60*8 },
+    ...
+    # one real week
+    'hard' => { Resolve => { RealMinutes => 60*24*7 } },
+
+=item Response
+
+In many companies providing support service(s) resolve time of a ticket
+is less important than time of response to requestors from staff
+members.
+
+You can use Response option to define such deadlines.  The Due date is
+set when a ticket is created, unset when a worker replies, and re-set
+when the requestor replies again -- until the ticket is closed, when the
+ticket's Due date is unset.
+
+B<NOTE> that this behaviour changes when Resolve and Response options
+are combined; see L</"Using both Resolve and Response in the same
+level">.
+
+Note that by default, only the requestors on the ticket are considered
+"outside actors" and thus require a Response due date; all other email
+addresses are treated as workers of the ticket, and thus count as
+meeting the SLA.  If you'd like to invert this logic, so that the Owner
+and AdminCcs are the only worker email addresses, and all others are
+external, see the L</AssumeOutsideActor> configuration.
+
+The owner is never treated as an outside actor; if they are also the
+requestor of the ticket, it will have no SLA.
+
+If an outside actor replies multiple times, their later replies are
+ignored; the deadline is always calculated from the oldest
+correspondence from the outside actor.
+
+
+=item Using both Resolve and Response in the same level
+
+Resolve and Response can be combined. In such case due date is set
+according to the earliest of two deadlines and never is dropped to
+'not set'.
+
+If a ticket met its Resolve deadline then due date stops "flipping",
+is freezed and the ticket becomes overdue. Before that moment when
+an inside actor replies to a ticket, due date is changed to Resolve
+deadline instead of 'Not Set', as well this happens when a ticket
+is closed. So all the time due date is defined.
+
+Example:
+
+    'standard delivery' => {
+        Response => { RealMinutes => 60*1  }, # one hour
+        Resolve  => { RealMinutes => 60*24 }, # 24 real hours
+    },
+
+A client orders goods and due date of the order is set to the next one
+hour, you have this hour to process the order and write a reply.
+As soon as goods are delivered you resolve tickets and usually meet
+Resolve deadline, but if you don't resolve or user replies then most
+probably there are problems with delivery of the goods. And if after
+a week you keep replying to the client and always meeting one hour
+response deadline that doesn't mean the ticket is not over due.
+Due date was frozen 24 hours after creation of the order.
+
+=item Using business and real time in one option
+
+It's quite rare situation when people need it, but we've decided
+that business is applied first and then real time when deadline
+described using both types of time. For example:
+
+    'delivery' => {
+        Resolve => { BusinessMinutes => 0, RealMinutes => 60*8 },
+    },
+    'fast delivery' {
+        StartImmediately => 1,
+        Resolve => { RealMinutes => 60*8 },
+    },
+
+For delivery requests which come into the system during business
+hours these levels define the same deadlines, otherwise the first
+level set deadline to 8 real hours starting from the next business
+day, when tickets with the second level should be resolved in the
+next 8 hours after creation.
+
+=back
+
+=item Keep in loop (interval, no defaults)
+
+If response deadline is used then Due date is changed to repsonse
+deadline or to "Not Set" when staff replies to a ticket. In some
+cases you want to keep requestors in loop and keed them up to date
+every few hours. KeepInLoop option can be used to achieve this.
+
+    'incident' => {
+        Response   => { RealMinutes => 60*1  }, # one hour
+        KeepInLoop => { RealMinutes => 60*2 }, # two hours
+        Resolve    => { RealMinutes => 60*24 }, # 24 real hours
+    },
+
+In the above example Due is set to one hour after creation, reply
+of a inside actor moves Due date two hours forward, outside actors'
+replies move Due date to one hour and resolve deadine is 24 hours.
 
+=item Modifying Agreements
 
+=over 4
+
+=item OutOfHours (struct, no default)
+
+Out of hours modifier. Adds more real or business minutes to resolve
+and/or reply options if event happens out of business hours, read also
+</"Configuring business hours"> below.
+
+Example:
+
+    'level x' => {
+        OutOfHours => { Resolve => { RealMinutes => +60*24 } },
+        Resolve    => { RealMinutes => 60*24 },
+    },
+
+If a request comes into the system during night then supporters have two
+hours, otherwise only one.
+
+    'level x' => {
+        OutOfHours => { Response => { BusinessMinutes => +60*2 } },
+        Resolve    => { BusinessMinutes => 60 },
+    },
+
+Supporters have two additional hours in the morning to deal with bunch
+of requests that came into the system during the last night.
+
+=item IgnoreOnStatuses (array, no default)
+
+Allows you to ignore a deadline when ticket has certain status. Example:
+
+    'level x' => {
+        KeepInLoop => { BusinessMinutes => 60, IgnoreOnStatuses => ['stalled'] },
+    },
+
+In above example KeepInLoop deadline is ignored if ticket is stalled.
+
+B<NOTE>: When a ticket goes from an ignored status to a normal status, the new
+Due date is calculated from the last action (reply, SLA change, etc) which fits
+the SLA type (Response, Starts, KeepInLoop, etc).  This means if a ticket in
+the above example flips from stalled to open without a reply, the ticket will
+probably be overdue.  In most cases this shouldn't be a problem since moving
+out of stalled-like statuses is often the result of RT's auto-open on reply
+scrip, therefore ensuring there's a new reply to calculate Due from.  The
+overall effect is that ignored statuses don't let the Due date drift
+arbitrarily, which could wreak havoc on your SLA performance.
+C<ExcludeTimeOnIgnoredStatuses> option could get around the "probably be
+overdue" issue by excluding the time spent on ignored statuses, e.g.
+
+    'level x' => {
+        KeepInLoop => {
+            BusinessMinutes => 60,
+            ExcludeTimeOnIgnoredStatuses => 1,
+            IgnoreOnStatuses => ['stalled'],
+        },
+    },
+
+=back
+
+=item Defining service levels per queue
+
+In the config you can set per queue defaults, using:
+
+    Set( %ServiceAgreements, (
+        Default => 'global default level of service',
+        QueueDefault => {
+            'queue name' => 'default value for this queue',
+            ...
+        },
+        ...
+    ));
+
+=item AssumeOutsideActor
+
+When using a L<Response|/"Resolve and Response (interval, no defaults)">
+configuration, the due date is unset when anyone who is not a requestor
+replies.  If it is common for non-requestors to reply to tickets, and
+this should I<not> satisfy the SLA, you may wish to set
+C<AssumeOutsideActor>.  This causes the extension to assume that the
+Response SLA has only been met when the owner or AdminCc reply.
+
+    Set ( %ServiceAgreements = (
+        AssumeOutsideActor => 1,
+        ...
+    );
+
+=back
+
+=cut
+
+Set( %ServiceAgreements, );
+
+=item C<%ServiceBusinessHours>
+
+In the config you can set one or more work schedules, e.g.
+
+    Set( %ServiceBusinessHours, (
+        'Default' => {
+            ... description ...
+        },
+        'Support' => {
+            ... description ...
+        },
+        'Sales' => {
+            ... description ...
+        },
+    );
+
+Read more about how to describe a schedule in L<Business::Hours>.
+
+=over 4
+
+=item Defining different business hours for service levels
+
+Each level supports BusinessHours option to specify your own business
+hours.
+
+    'level x' => {
+        BusinessHours => 'work just in Monday',
+        Resolve    => { BusinessMinutes => 60 },
+    },
+
+then L<%ServiceBusinessHours> should have the corresponding definition:
+
+    Set( %ServiceBusinessHours, (
+        'work just in Monday' => {
+            1 => { Name => 'Monday', Start => '9:00', End => '18:00' },
+        },
+    );
+
+Default Business Hours setting is in $ServiceBusinessHours{'Default'}.
+
+=back
+
+=cut
+
+Set( %ServiceBusinessHours, );
+
+=back
 
 =head1 Administrative interface
 
diff --git a/lib/RT/SLA.pm b/lib/RT/SLA.pm
index 215a87a..c50d696 100644
--- a/lib/RT/SLA.pm
+++ b/lib/RT/SLA.pm
@@ -58,325 +58,6 @@ RT::SLA - Service Level Agreements for RT
 
 Automated due dates using service levels.
 
-=head1 CONFIGURATION
-
-Service level agreements of tickets is controlled by an SLA custom field (CF).
-This field is created during C<make initdb> step (above) and applied globally.
-This CF MUST be of C<select one value> type. Values of the CF define the
-service levels.
-
-It's possible to define different set of levels for different
-queues. You can create several CFs with the same name and
-different set of values. But if you move tickets between
-queues a lot then it's going to be a problem and it's preferred
-to use B<ONE> SLA custom field.
-
-There is no WebUI in the current version. Almost everything is
-controlled in the RT's config using option C<%RT::ServiceAgreements>
-and C<%RT::ServiceBusinessHours>. For example:
-
-    %RT::ServiceAgreements = (
-        Default => '4h',
-        QueueDefault => {
-            'Incident' => '2h',
-        },
-        Levels => {
-            '2h' => { Resolve => { RealMinutes => 60*2 } },
-            '4h' => { Resolve => { RealMinutes => 60*4 } },
-        },
-    );
-
-In this example I<Incident> is the name of the queue, and I<2h> is the name of
-the SLA which will be applied to this queue by default.
-
-Each service level can be described using several options:
-L<Starts|/"Starts (interval, first business minute)">,
-L<Resolve|/"Resolve and Response (interval, no defaults)">,
-L<Response|/"Resolve and Response (interval, no defaults)">,
-L<KeepInLoop|/"Keep in loop (interval, no defaults)">,
-L<OutOfHours|/"OutOfHours (struct, no default)">
-and L<ServiceBusinessHours|/"Configuring business hours">.
-
-=head2 Starts (interval, first business minute)
-
-By default when a ticket is created Starts date is set to
-first business minute after time of creation. In other
-words if a ticket is created during business hours then
-Starts will be equal to Created time, otherwise Starts will
-be beginning of the next business day.
-
-However, if you provide 24/7 support then you most
-probably would be interested in Starts to be always equal
-to Created time.
-
-Starts option can be used to adjust behaviour. Format
-of the option is the same as format for deadlines which
-described later in details. RealMinutes, BusinessMinutes
-options and OutOfHours modifiers can be used here like
-for any other deadline. For example:
-
-    'standard' => {
-        # give people 15 minutes
-        Starts   => { BusinessMinutes => 15  },
-    },
-
-You can still use old option StartImmediately to set
-Starts date equal to Created date.
-
-Example:
-
-    '24/7' => {
-        StartImmediately => 1,
-        Response => { RealMinutes => 30 },
-    },
-
-But it's the same as:
-
-    '24/7' => {
-        Starts => { RealMinutes => 0 },
-        Response => { RealMinutes => 30 },
-    },
-
-=head2 Resolve and Response (interval, no defaults)
-
-These two options define deadlines for resolve of a ticket
-and reply to customer(requestors) questions accordingly.
-
-You can define them using real time, business or both. Read more
-about the latter L<below|/"Using both Resolve and Response in the same level">.
-
-The Due date field is used to store calculated deadlines.
-
-=head3 Resolve
-
-Defines deadline when a ticket should be resolved. This option is
-quite simple and straightforward when used without L</Response>.
-
-Example:
-
-    # 8 business hours
-    'simple' => { Resolve => 60*8 },
-    ...
-    # one real week
-    'hard' => { Resolve => { RealMinutes => 60*24*7 } },
-
-=head3 Response
-
-In many companies providing support service(s) resolve time of a ticket
-is less important than time of response to requestors from staff
-members.
-
-You can use Response option to define such deadlines.  The Due date is
-set when a ticket is created, unset when a worker replies, and re-set
-when the requestor replies again -- until the ticket is closed, when the
-ticket's Due date is unset.
-
-B<NOTE> that this behaviour changes when Resolve and Response options
-are combined; see L</"Using both Resolve and Response in the same
-level">.
-
-Note that by default, only the requestors on the ticket are considered
-"outside actors" and thus require a Response due date; all other email
-addresses are treated as workers of the ticket, and thus count as
-meeting the SLA.  If you'd like to invert this logic, so that the Owner
-and AdminCcs are the only worker email addresses, and all others are
-external, see the L</AssumeOutsideActor> configuration.
-
-The owner is never treated as an outside actor; if they are also the
-requestor of the ticket, it will have no SLA.
-
-If an outside actor replies multiple times, their later replies are
-ignored; the deadline is awlways calculated from the oldest
-correspondence from the outside actor.
-
-
-=head3 Using both Resolve and Response in the same level
-
-Resolve and Response can be combined. In such case due date is set
-according to the earliest of two deadlines and never is dropped to
-'not set'.
-
-If a ticket met its Resolve deadline then due date stops "flipping",
-is freezed and the ticket becomes overdue. Before that moment when
-an inside actor replies to a ticket, due date is changed to Resolve
-deadline instead of 'Not Set', as well this happens when a ticket
-is closed. So all the time due date is defined.
-
-Example:
-
-    'standard delivery' => {
-        Response => { RealMinutes => 60*1  }, # one hour
-        Resolve  => { RealMinutes => 60*24 }, # 24 real hours
-    },
-
-A client orders goods and due date of the order is set to the next one
-hour, you have this hour to process the order and write a reply.
-As soon as goods are delivered you resolve tickets and usually meet
-Resolve deadline, but if you don't resolve or user replies then most
-probably there are problems with delivery of the goods. And if after
-a week you keep replying to the client and always meeting one hour
-response deadline that doesn't mean the ticket is not over due.
-Due date was frozen 24 hours after creation of the order.
-
-=head3 Using business and real time in one option
-
-It's quite rare situation when people need it, but we've decided
-that business is applied first and then real time when deadline
-described using both types of time. For example:
-
-    'delivery' => {
-        Resolve => { BusinessMinutes => 0, RealMinutes => 60*8 },
-    },
-    'fast delivery' {
-        StartImmediately => 1,
-        Resolve => { RealMinutes => 60*8 },
-    },
-
-For delivery requests which come into the system during business
-hours these levels define the same deadlines, otherwise the first
-level set deadline to 8 real hours starting from the next business
-day, when tickets with the second level should be resolved in the
-next 8 hours after creation.
-
-=head2 Keep in loop (interval, no defaults)
-
-If response deadline is used then Due date is changed to repsonse
-deadline or to "Not Set" when staff replies to a ticket. In some
-cases you want to keep requestors in loop and keed them up to date
-every few hours. KeepInLoop option can be used to achieve this.
-
-    'incident' => {
-        Response   => { RealMinutes => 60*1  }, # one hour
-        KeepInLoop => { RealMinutes => 60*2 }, # two hours
-        Resolve    => { RealMinutes => 60*24 }, # 24 real hours
-    },
-
-In the above example Due is set to one hour after creation, reply
-of a inside actor moves Due date two hours forward, outside actors'
-replies move Due date to one hour and resolve deadine is 24 hours.
-
-=head2 Modifying Agreements
-
-=head3 OutOfHours (struct, no default)
-
-Out of hours modifier. Adds more real or business minutes to resolve
-and/or reply options if event happens out of business hours, read also
-</"Configuring business hours"> below.
-
-Example:
-
-    'level x' => {
-        OutOfHours => { Resolve => { RealMinutes => +60*24 } },
-        Resolve    => { RealMinutes => 60*24 },
-    },
-
-If a request comes into the system during night then supporters have two
-hours, otherwise only one.
-
-    'level x' => {
-        OutOfHours => { Response => { BusinessMinutes => +60*2 } },
-        Resolve    => { BusinessMinutes => 60 },
-    },
-
-Supporters have two additional hours in the morning to deal with bunch
-of requests that came into the system during the last night.
-
-=head3 IgnoreOnStatuses (array, no default)
-
-Allows you to ignore a deadline when ticket has certain status. Example:
-
-    'level x' => {
-        KeepInLoop => { BusinessMinutes => 60, IgnoreOnStatuses => ['stalled'] },
-    },
-
-In above example KeepInLoop deadline is ignored if ticket is stalled.
-
-B<NOTE>: When a ticket goes from an ignored status to a normal status, the new
-Due date is calculated from the last action (reply, SLA change, etc) which fits
-the SLA type (Response, Starts, KeepInLoop, etc).  This means if a ticket in
-the above example flips from stalled to open without a reply, the ticket will
-probably be overdue.  In most cases this shouldn't be a problem since moving
-out of stalled-like statuses is often the result of RT's auto-open on reply
-scrip, therefore ensuring there's a new reply to calculate Due from.  The
-overall effect is that ignored statuses don't let the Due date drift
-arbitrarily, which could wreak havoc on your SLA performance.
-C<ExcludeTimeOnIgnoredStatuses> option could get around the "probably be
-overdue" issue by excluding the time spent on ignored statuses.
-
-        'level x' => {
-            KeepInLoop => {
-                BusinessMinutes => 60,
-                ExcludeTimeOnIgnoredStatuses => 1,
-                IgnoreOnStatuses => ['stalled'],
-            },
-        },
-
-=head2 Configuring business hours
-
-In the config you can set one or more work schedules. Use the following
-format:
-
-    %RT::ServiceBusinessHours = (
-        'Default' => {
-            ... description ...
-        },
-        'Support' => {
-            ... description ...
-        },
-        'Sales' => {
-            ... description ...
-        },
-    );
-
-Read more about how to describe a schedule in L<Business::Hours>.
-
-=head3 Defining different business hours for service levels
-
-Each level supports BusinessHours option to specify your own business
-hours.
-
-    'level x' => {
-        BusinessHours => 'work just in Monday',
-        Resolve    => { BusinessMinutes => 60 },
-    },
-
-then %RT::ServiceBusinessHours should have the corresponding definition:
-
-    %RT::ServiceBusinessHours = (
-        'work just in Monday' => {
-            1 => { Name => 'Monday', Start => '9:00', End => '18:00' },
-        },
-    );
-
-Default Business Hours setting is in $RT::ServiceBusinessHours{'Default'}.
-
-=head2 Defining service levels per queue
-
-In the config you can set per queue defaults, using:
-
-    %RT::ServiceAgreements = (
-        Default => 'global default level of service',
-        QueueDefault => {
-            'queue name' => 'default value for this queue',
-            ...
-        },
-        ...
-    };
-
-=head2 AssumeOutsideActor
-
-When using a L<Response|/"Resolve and Response (interval, no defaults)">
-configuration, the due date is unset when anyone who is not a requestor
-replies.  If it is common for non-requestors to reply to tickets, and
-this should I<not> satisfy the SLA, you may wish to set
-C<AssumeOutsideActor>.  This causes the extension to assume that the
-Response SLA has only been met when the owner or AdminCc reply.
-
-    %RT::ServiceAgreements = (
-        AssumeOutsideActor => 1,
-        ...
-    };
-
 =cut
 
 sub BusinessHours {

commit 81ad7f77387f7e54f90af92472c336e7c071347a
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Mon Aug 3 01:52:58 2015 +0800

    upgrade-sla script to migrate old data used in SLA extension

diff --git a/.gitignore b/.gitignore
index 2156779..54bde7e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@
 /etc/upgrade/switch-templates-to
 /etc/upgrade/time-worked-history
 /etc/upgrade/upgrade-articles
+/etc/upgrade/upgrade-sla
 /etc/upgrade/vulnerable-passwords
 /lib/RT/Generated.pm
 /Makefile
diff --git a/configure.ac b/configure.ac
index 9c57eda..064274e 100755
--- a/configure.ac
+++ b/configure.ac
@@ -437,6 +437,7 @@ AC_CONFIG_FILES([
                  etc/upgrade/time-worked-history
                  etc/upgrade/upgrade-articles
                  etc/upgrade/vulnerable-passwords
+                 etc/upgrade/upgrade-sla
                  sbin/rt-attributes-viewer
                  sbin/rt-preferences-viewer
                  sbin/rt-session-viewer
diff --git a/etc/upgrade/upgrade-sla.in b/etc/upgrade/upgrade-sla.in
new file mode 100644
index 0000000..6863fb8
--- /dev/null
+++ b/etc/upgrade/upgrade-sla.in
@@ -0,0 +1,142 @@
+#!@PERL@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+use 5.10.1;
+use strict;
+use warnings;
+
+use lib "@LOCAL_LIB_PATH@";
+use lib "@RT_LIB_PATH@";
+
+use RT::Interface::CLI qw(Init);
+Init();
+
+my $tickets = RT::Tickets->new(RT->SystemUser);
+$tickets->FromSQL('CF.SLA IS NOT NULL AND SLA IS NULL');
+while ( my $ticket = $tickets->Next ) {
+    my ($ret, $msg) = $ticket->SetSLA($ticket->FirstCustomFieldValue('SLA'));
+    unless ( $ret ) {
+        RT->Logger->error("Failed to upgrade SLA for ticket #" . $ticket->id . ": $msg");
+    }
+}
+
+my $queues = RT::Queues->new(RT->SystemUser);
+$queues->UnLimit;
+
+my %cfs_to_disable;
+while ( my $queue = $queues->Next ) {
+    my $cfs = $queue->TicketCustomFields;
+    $cfs->Limit(FIELD => 'Name', VALUE => 'SLA', CASESENSITIVE => 0 );
+    if ( my $cf = $cfs->First ) {
+        $cfs_to_disable{$cf->id} ||= $cf;
+    }
+    elsif ( !$queue->SLADisabled ) {
+        my ($ret, $msg) = $queue->SetSLADisabled(1);
+        if ( $ret ) {
+            RT->Logger->info("Disabled SLA for queue #" . $queue->id . " because it doesn't have custom field SLA applied");
+        }
+        else {
+            RT->Logger->error("Failed to disable SLA for queue #" . $queue->id . ": $msg");
+        }
+    }
+}
+
+for my $cf ( values %cfs_to_disable ) {
+    my ($ret, $msg) = $cf->SetDisabled(1);
+    if ( $ret ) {
+        RT->Logger->info("Disabled custom field SLA #" . $cf->id);
+    }
+    else {
+        RT->Logger->error("Failed to disable custom field SLA #" . $cf->id . ": $msg");
+    }
+}
+
+my @old_scrips = ( '[SLA] Set default service level if needed', '[SLA] Set starts date if needed', '[SLA] Set due date if needed' );
+for my $item ( @old_scrips ) {
+    my $scrip = RT::Scrip->new(RT->SystemUser);
+    $scrip->LoadByCols( Description => $item );
+    if ( $scrip->id ) {
+        my ($ret, $msg) = $scrip->RT::Record::Delete();
+        if ( $ret ) {
+            RT->Logger->info(qq{Deleted scrip "$item"});
+        }
+        else {
+            RT->Logger->error(qq{Failed to delete scrip "$item": $msg});
+        }
+    }
+}
+
+my @old_conditions = ( '[SLA] Require default', '[SLA] Require Starts set', '[SLA] Require Due set' );
+for my $item ( @old_conditions ) {
+    my $condition = RT::ScripCondition->new(RT->SystemUser);
+    $condition->Load($item);
+    if ( $condition->id ) {
+        my ($ret, $msg) = $condition->RT::Record::Delete();
+        if ( $ret ) {
+            RT->Logger->info(qq{Deleted condition "$item"});
+        }
+        else {
+            RT->Logger->error(qq{Failed to delete condition "$item": $msg});
+        }
+    }
+}
+
+my @old_actions = ('[SLA] Set default service level', '[SLA] Set starts date', '[SLA] Set due date' );
+for my $item ( @old_actions ) {
+    my $action = RT::ScripAction->new(RT->SystemUser);
+    $action->Load($item);
+    if ( $action->id ) {
+        my ($ret, $msg) = $action->RT::Record::Delete();
+        if ( $ret ) {
+            RT->Logger->info(qq{Deleted action "$item"});
+        }
+        else {
+            RT->Logger->error(qq{Failed to delete action "$item": $msg});
+        }
+    }
+}

commit e8896b7f80879e03bff5790311eb256016d985dd
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Mon Aug 3 02:07:26 2015 +0800

    upgrading note for core SLA

diff --git a/docs/UPGRADING-4.4 b/docs/UPGRADING-4.4
index 78c2f85..3827cf4 100644
--- a/docs/UPGRADING-4.4
+++ b/docs/UPGRADING-4.4
@@ -37,6 +37,17 @@ Homepage component "Quicksearch" has been renamed to "QueueList" to reflect
 what it actually is. Please update C<$HomepageComponents> accordingly if you
 customized it in site config.
 
+=item *
+
+SLA is in core now, so C<SLA> became a core field. if you installed
+C<RT::Extension::SLA> before, you need to remove it from your plugins, adjust
+configs accordingly and run F<etc/upgrade/upgrade-sla>. see also the SLA
+section in F<RT_Config.pm>.
+
+Note that with core SLA, you can't define different set of levels for
+different queues. i.e. all the queues share the same set of levels defined in
+C<%ServiceAgreements>.
+
 =back
 
 =cut

commit 8bdf41d79748ca6cc2fb4485a6006798e6b5e3a1
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Aug 12 00:35:40 2015 +0800

    warn if we still have SLA extension enabled
    
    we can't die here because it's possible that someone wrote his own version
    of SLA extension that's not related to ours at all.

diff --git a/lib/RT.pm b/lib/RT.pm
index 4147992..f0dbe37 100644
--- a/lib/RT.pm
+++ b/lib/RT.pm
@@ -722,11 +722,18 @@ their lib and L<HTML::Mason> component roots.
 
 =cut
 
+our %CORED_PLUGINS = (
+    'RT::Extension::SLA' => '4.4',
+);
+
 sub InitPlugins {
     my $self    = shift;
     my @plugins;
     require RT::Plugin;
     foreach my $plugin (grep $_, RT->Config->Get('Plugins')) {
+        if ( $CORED_PLUGINS{$plugin} ) {
+            RT->Logger->warning( "$plugin has been cored since RT $CORED_PLUGINS{$plugin}, please check the upgrade document for more details" );
+        }
         $plugin->require;
         die $UNIVERSAL::require::ERROR if ($UNIVERSAL::require::ERROR);
         push @plugins, RT::Plugin->new(name =>$plugin);

commit 3f47233c2aa4bd5315f0054df85f465aabce645b
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Aug 13 19:45:00 2015 +0800

    better name of SLA conditions/actions/scrips

diff --git a/etc/initialdata b/etc/initialdata
index 5787fe6..21c8888 100644
--- a/etc/initialdata
+++ b/etc/initialdata
@@ -111,11 +111,11 @@
     { Name        => 'Send Forward',                 # loc
       Description => 'Send forwarded message',       # loc
       ExecModule  => 'SendForward', },
-    {  Name        => '[SLA] Set starts date', # loc
+    {  Name        => 'Set starts date according to SLA', # loc
        Description => 'Set the starts date according to an agreement' , # loc
        ExecModule  => 'SLA_SetStarts',
     },
-    {  Name        => '[SLA] Set due date', # loc
+    {  Name        => 'Set due date according to SLA', # loc
        Description => 'Set the due date according to an agreement' , # loc
        ExecModule  => 'SLA_SetDue',
     },
@@ -227,12 +227,12 @@
        ApplicableTransTypes => 'Status,Set',
        ExecModule           => 'ReopenTicket',
     },
-    {  Name        => '[SLA] Require Starts set', # loc
+    {  Name        => 'Require Starts set according to SLA', # loc
        Description => 'Detect a situation when we should set Starts date' , # loc
        ApplicableTransTypes => 'Create,Set',
        ExecModule => 'SLA_RequireStartsSet',
     },
-    {  Name        => '[SLA] Require Due set', # loc
+    {  Name        => 'Require Due set according to SLA', # loc
        Description => 'Detect a situation when we should set Due date' , # loc
        ApplicableTransTypes => 'Create,Correspond,Set,Status',
        ExecModule => 'SLA_RequireDueSet',
@@ -810,13 +810,13 @@ Hour:         { $SubscriptionObj->SubValue('Hour') }
        ScripCondition => 'On Forward Ticket',
        ScripAction    => 'Send Forward',
        Template       => 'Forward Ticket' },
-    {  Description       => "[SLA] Set starts date if needed",
-       ScripCondition    => '[SLA] Require starts set',
-       ScripAction       => '[SLA] Set starts date',
+    {  Description       => "Set starts date if needed according to SLA",
+       ScripCondition    => 'Require starts set according to SLA',
+       ScripAction       => 'Set starts date according to SLA',
        Template          => 'Blank' },
-    {  Description       => "[SLA] Set due date if needed",
-       ScripCondition    => '[SLA] Require due set',
-       ScripAction       => '[SLA] Set due date',
+    {  Description       => "Set due date if needed according to SLA",
+       ScripCondition    => 'Require due set according to SLA',
+       ScripAction       => 'Set due date according to SLA',
        Template          => 'Blank' },
 );
 
diff --git a/etc/upgrade/4.3.8/content b/etc/upgrade/4.3.8/content
index ac336ff..38e3a68 100644
--- a/etc/upgrade/4.3.8/content
+++ b/etc/upgrade/4.3.8/content
@@ -2,12 +2,12 @@ use strict;
 use warnings;
 
 our @ScripConditions = (
-    {  Name        => '[SLA] Require Starts set', # loc
+    {  Name        => 'Require Starts set according to SLA', # loc
        Description => 'Detect a situation when we should set Starts date' , # loc
        ApplicableTransTypes => 'Create,Set',
        ExecModule => 'SLA_RequireStartsSet',
     },
-    {  Name        => '[SLA] Require Due set', # loc
+    {  Name        => 'Require Due set according to SLA', # loc
        Description => 'Detect a situation when we should set Due date' , # loc
        ApplicableTransTypes => 'Create,Correspond,Set,Status',
        ExecModule => 'SLA_RequireDueSet',
@@ -15,23 +15,23 @@ our @ScripConditions = (
 );
 
 our @ScripActions = (
-    {  Name        => '[SLA] Set starts date', # loc
+    {  Name        => 'Set starts date according to SLA', # loc
        Description => 'Set the starts date according to an agreement' , # loc
        ExecModule  => 'SLA_SetStarts',
     },
-    {  Name        => '[SLA] Set due date', # loc
+    {  Name        => 'Set due date according to SLA', # loc
        Description => 'Set the due date according to an agreement' , # loc
        ExecModule  => 'SLA_SetDue',
     },
 );
 
 our @Scrips = (
-    {  Description       => "[SLA] Set starts date if needed",
-       ScripCondition    => '[SLA] Require starts set',
-       ScripAction       => '[SLA] Set starts date',
+    {  Description       => "Set starts date if needed according to SLA",
+       ScripCondition    => 'Require starts set according to SLA',
+       ScripAction       => 'Set starts date according to SLA',
        Template          => 'Blank' },
-    {  Description       => "[SLA] Set due date if needed",
-       ScripCondition    => '[SLA] Require due set',
-       ScripAction       => '[SLA] Set due date',
+    {  Description       => "Set due date if needed according to SLA",
+       ScripCondition    => 'Require due set according to SLA',
+       ScripAction       => 'Set due date according to SLA',
        Template          => 'Blank' },
 );

commit e4b30a514897f871b5f07b59ac5b4a2b0431e6cf
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Aug 12 01:51:43 2015 +0800

    SLA tests
    
    most tests are migrated from SLA extension.
    I also added more tests for web and ExcludeTimeOnIgnoredStatuses option

diff --git a/t/sla/business_hours.t b/t/sla/business_hours.t
new file mode 100644
index 0000000..510faeb
--- /dev/null
+++ b/t/sla/business_hours.t
@@ -0,0 +1,64 @@
+use strict;
+use warnings;
+
+use Test::MockTime qw( :all );
+use RT::Test tests => undef;
+
+# we assume the RT's Timezone is UTC now, need a smart way to get over that.
+$ENV{'TZ'} = 'GMT';
+RT->Config->Set( Timezone => 'GMT' );
+
+diag 'check business hours' if $ENV{'TEST_VERBOSE'};
+{
+
+    no warnings 'once';
+    %RT::ServiceAgreements = (
+        Default => 'Sunday',
+        Levels  => {
+            Sunday => {
+                Resolve       => { BusinessMinutes => 60 },
+                BusinessHours => 'Sunday',
+            },
+            Monday => {
+                Resolve       => { BusinessMinutes => 60 },
+            },
+        },
+    );
+
+    %RT::ServiceBusinessHours = (
+        Sunday => {
+            0 => {
+                Name  => 'Sunday',
+                Start => '9:00',
+                End   => '17:00'
+            }
+        },
+        Default => {
+            1 => {
+                Name  => 'Monday',
+                Start => '9:00',
+                End   => '17:00'
+            },
+        },
+    );
+
+    set_absolute_time('2007-01-01T00:00:00Z');
+
+    my $ticket = RT::Ticket->new($RT::SystemUser);
+    my ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx' );
+    ok( $id, "created ticket #$id" );
+
+    is( $ticket->SLA, 'Sunday', 'default sla' );
+
+    my $start = $ticket->StartsObj->Unix;
+    my $due = $ticket->DueObj->Unix;
+    is( $start, 1168160400, 'Start date is 2007-01-07T09:00:00Z' );
+    is( $due, 1168164000, 'Due date is 2007-01-07T10:00:00Z' );
+
+    $ticket->SetSLA( 'Monday' );
+    is( $ticket->SLA, 'Monday', 'new sla' );
+    $due = $ticket->DueObj->Unix;
+    is( $due, 1167645600, 'Due date is 2007-01-01T10:00:00Z' );
+}
+
+done_testing();
diff --git a/t/sla/due.t b/t/sla/due.t
new file mode 100644
index 0000000..7fb96c9
--- /dev/null
+++ b/t/sla/due.t
@@ -0,0 +1,396 @@
+use strict;
+use warnings;
+
+use RT::Test tests => undef;
+
+diag 'check change of Due date when SLA for a ticket is changed' if $ENV{'TEST_VERBOSE'};
+{
+    %RT::ServiceAgreements = (
+        Default => '2',
+        Levels => {
+            '2' => { Resolve => { RealMinutes => 60*2 } },
+            '4' => { Resolve => { RealMinutes => 60*4 } },
+        },
+    );
+
+    my $time = time;
+
+    my $ticket = RT::Ticket->new( $RT::SystemUser );
+    my ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx' );
+    ok $id, "created ticket #$id";
+
+    is $ticket->SLA, '2', 'default sla';
+
+    my $orig_due = $ticket->DueObj->Unix;
+    ok $orig_due > 0, 'Due date is set';
+    ok $orig_due > $time, 'Due date is in the future';
+
+    $ticket->SetSLA('4');
+    is $ticket->SLA, '4', 'new sla';
+
+    my $new_due = $ticket->DueObj->Unix;
+    ok $new_due > 0, 'Due date is set';
+    ok $new_due > $time, 'Due date is in the future';
+
+    is $new_due, $orig_due+2*60*60, 'difference is two hours';
+}
+
+diag 'when not requestor creates a ticket, we dont set due date' if $ENV{'TEST_VERBOSE'};
+{
+    %RT::ServiceAgreements = (
+        Default => '2',
+        Levels => {
+            '2' => { Response => { RealMinutes => 60*2 } },
+        },
+    );
+
+    my $ticket = RT::Ticket->new( $RT::SystemUser );
+    my ($id) = $ticket->Create(
+        Queue => 'General',
+        Subject => 'xxx',
+        Requestor => 'user at example.com',
+    );
+    ok $id, "created ticket #$id";
+
+    is $ticket->SLA, '2', 'default sla';
+
+    my $due = $ticket->DueObj->Unix;
+    ok $due <= 0, 'Due date is not set';
+}
+
+diag 'check that reply to requestors unset due date' if $ENV{'TEST_VERBOSE'};
+{
+    %RT::ServiceAgreements = (
+        Default => '2',
+        Levels => {
+            '2' => { Response => { RealMinutes => 60*2 } },
+        },
+    );
+
+    my $root = RT::User->new( $RT::SystemUser );
+    $root->LoadByEmail('root at localhost');
+    ok $root->id, 'loaded root user';
+
+    # requestor creates
+    my $id;
+    {
+        my $ticket = RT::Ticket->new( $root );
+        ($id) = $ticket->Create(
+            Queue => 'General',
+            Subject => 'xxx',
+            Requestor => $root->id,
+        );
+        ok $id, "created ticket #$id";
+
+        is $ticket->SLA, '2', 'default sla';
+
+        my $due = $ticket->DueObj->Unix;
+        ok $due > 0, 'Due date is set';
+    }
+
+    # non-requestor reply
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+        $ticket->Correspond( Content => 'we are working on this.' );
+
+        $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        my $due = $ticket->DueObj->Unix;
+        ok $due <= 0, 'Due date is not set';
+    }
+
+    # non-requestor reply again
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+        $ticket->Correspond( Content => 'we are still working on this.' );
+
+        $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        my $due = $ticket->DueObj->Unix;
+        ok $due <= 0, 'Due date is not set';
+    }
+
+    # requestor reply
+    my $last_unreplied_due;
+    {
+        my $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        $ticket->Correspond( Content => 'what\'s going on with my ticket?' );
+
+        $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        my $due = $ticket->DueObj->Unix;
+        ok $due > 0, 'Due date is set again';
+
+        $last_unreplied_due = $due;
+    }
+
+    # sleep at least one second and requestor replies again
+    sleep 1;
+    {
+        my $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        $ticket->Correspond( Content => 'HEY! Were is my answer?' );
+
+        $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        my $due = $ticket->DueObj->Unix;
+        ok $due > 0, 'Due date is still set';
+        is $due, $last_unreplied_due, 'due is unchanged';
+    }
+}
+
+diag 'check that reply to requestors dont unset due date with KeepInLoop' if $ENV{'TEST_VERBOSE'};
+{
+    %RT::ServiceAgreements = (
+        Default => '2',
+        Levels => {
+            '2' => {
+                Response   => { RealMinutes => 60*2 },
+                KeepInLoop => { RealMinutes => 60*4 },
+            },
+        },
+    );
+
+    my $root = RT::User->new( $RT::SystemUser );
+    $root->LoadByEmail('root at localhost');
+    ok $root->id, 'loaded root user';
+
+    # requestor creates
+    my $id;
+    my $due;
+    {
+        my $ticket = RT::Ticket->new( $root );
+        ($id) = $ticket->Create(
+            Queue => 'General',
+            Subject => 'xxx',
+            Requestor => $root->id,
+        );
+        ok $id, "created ticket #$id";
+
+        is $ticket->SLA, '2', 'default sla';
+
+        $due = $ticket->DueObj->Unix;
+        ok $due > 0, 'Due date is set';
+    }
+
+    # non-requestor reply
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+        $ticket->Correspond( Content => 'we are working on this.' );
+
+        $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        my $tmp = $ticket->DueObj->Unix;
+        ok $tmp > 0, 'Due date is set';
+        ok $tmp > $due, "keep in loop is 4hours when response is 2hours";
+        $due = $tmp;
+    }
+
+    # non-requestor reply again
+    {
+        sleep 1;
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+        $ticket->Correspond( Content => 'we are still working on this.' );
+
+        $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        my $tmp = $ticket->DueObj->Unix;
+        ok $tmp > 0, 'Due date is set';
+        ok $tmp > $due, "keep in loop sligtly moved";
+        $due = $tmp;
+    }
+
+    # requestor reply
+    my $last_unreplied_due;
+    {
+        my $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        $ticket->Correspond( Content => 'what\'s going on with my ticket?' );
+
+        $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        my $tmp = $ticket->DueObj->Unix;
+        ok $tmp > 0, 'Due date is set';
+        ok $tmp < $due, "response deadline is 2 hours earlier";
+        $due = $tmp;
+
+        $last_unreplied_due = $due;
+    }
+
+    # sleep at least one second and requestor replies again
+    sleep 1;
+    {
+        my $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        $ticket->Correspond( Content => 'HEY! Were is my answer?' );
+
+        $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        my $tmp = $ticket->DueObj->Unix;
+        ok $tmp > 0, 'Due date is set';
+        is $tmp, $last_unreplied_due, 'due is unchanged';
+        $due = $tmp;
+    }
+}
+
+diag 'check that replies dont affect resolve deadlines' if $ENV{'TEST_VERBOSE'};
+{
+    %RT::ServiceAgreements = (
+        Default => '2',
+        Levels => {
+            '2' => { Resolve => { RealMinutes => 60*2 } },
+        },
+    );
+
+    my $root = RT::User->new( $RT::SystemUser );
+    $root->LoadByEmail('root at localhost');
+    ok $root->id, 'loaded root user';
+
+    # requestor creates
+    my ($id, $orig_due);
+    {
+        my $ticket = RT::Ticket->new( $root );
+        ($id) = $ticket->Create(
+            Queue => 'General',
+            Subject => 'xxx',
+            Requestor => $root->id,
+        );
+        ok $id, "created ticket #$id";
+
+        is $ticket->SLA, '2', 'default sla';
+
+        $orig_due = $ticket->DueObj->Unix;
+        ok $orig_due > 0, 'Due date is set';
+    }
+
+    # non-requestor reply
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+        $ticket->Correspond( Content => 'we are working on this.' );
+
+        $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        my $due = $ticket->DueObj->Unix;
+        ok $due > 0, 'Due date is set';
+        is $due, $orig_due, 'due is not changed';
+    }
+
+    # requestor reply
+    {
+        my $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        $ticket->Correspond( Content => 'what\'s going on with my ticket?' );
+
+        $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        my $due = $ticket->DueObj->Unix;
+        ok $due > 0, 'Due date is set';
+        is $due, $orig_due, 'due is not changed';
+    }
+}
+
+diag 'check that owner is not treated as requestor' if $ENV{'TEST_VERBOSE'};
+{
+    %RT::ServiceAgreements = (
+        Default => '2',
+        Levels => {
+            '2' => { Response => { RealMinutes => 60*2 } },
+        },
+    );
+
+    my $root = RT::User->new( $RT::SystemUser );
+    $root->LoadByEmail('root at localhost');
+    ok $root->id, 'loaded root user';
+
+    # requestor creates and he is owner
+    my $id;
+    {
+        my $ticket = RT::Ticket->new( $root );
+        ($id) = $ticket->Create(
+            Queue => 'General',
+            Subject => 'xxx',
+            Requestor => $root->id,
+            Owner => $root->id,
+        );
+        ok $id, "created ticket #$id";
+
+        is $ticket->SLA, '2', 'default sla';
+        is $ticket->Owner, $root->id, 'correct owner';
+
+        my $due = $ticket->DueObj->Unix;
+        ok $due <= 0, 'Due date is not set';
+    }
+}
+
+diag 'check that response deadline is left alone when there is no requestor' if $ENV{'TEST_VERBOSE'};
+{
+    %RT::ServiceAgreements = (
+        Default => '2',
+        Levels => {
+            '2' => { Response => { RealMinutes => 60*2 } },
+        },
+    );
+
+    my $root = RT::User->new( $RT::SystemUser );
+    $root->LoadByEmail('root at localhost');
+    ok $root->id, 'loaded root user';
+
+    # create a ticket without requestor
+    my $id;
+    {
+        my $ticket = RT::Ticket->new( $root );
+        ($id) = $ticket->Create(
+            Queue => 'General',
+            Subject => 'xxx',
+        );
+        ok $id, "created ticket #$id";
+
+        is $ticket->SLA, '2', 'default sla';
+
+        my $due = $ticket->DueObj->Unix;
+        ok $due <= 0, 'Due date is not set';
+    }
+}
+
+done_testing();
diff --git a/t/sla/ignore-on-statuses.t b/t/sla/ignore-on-statuses.t
new file mode 100644
index 0000000..496f530
--- /dev/null
+++ b/t/sla/ignore-on-statuses.t
@@ -0,0 +1,261 @@
+use strict;
+use warnings;
+
+use RT::Test tests => undef;
+
+diag 'check that reply to requestors dont unset due date with KeepInLoop' if $ENV{'TEST_VERBOSE'};
+{
+    %RT::ServiceAgreements = (
+        Default => '2',
+        Levels => {
+            '2' => {
+                KeepInLoop => { RealMinutes => 60*4, IgnoreOnStatuses => ['stalled'] },
+            },
+        },
+    );
+
+    my $root = RT::User->new( $RT::SystemUser );
+    $root->LoadByEmail('root at localhost');
+    ok $root->id, 'loaded root user';
+
+    # requestor creates
+    my $id;
+    my $due;
+    {
+        my $ticket = RT::Ticket->new( $root );
+        ($id) = $ticket->Create(
+            Queue => 'General',
+            Subject => 'xxx',
+            Requestor => $root->id,
+        );
+        ok $id, "created ticket #$id";
+        is $ticket->SLA, '2', 'default sla';
+        ok !$ticket->DueObj->Unix, 'no response deadline';
+        $due = 0;
+    }
+
+    # non-requestor reply
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+        $ticket->Correspond( Content => 'we are working on this.' );
+
+        $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        my $tmp = $ticket->DueObj->Unix;
+        ok $tmp > 0, 'Due date is set';
+        ok $tmp > $due, "keep in loop due set";
+        $due = $tmp;
+    }
+
+    # stalling ticket
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+        my ($status, $msg) = $ticket->SetStatus('stalled');
+        ok $status, 'stalled the ticket';
+
+        $ticket->Load( $id );
+        ok !$ticket->DueObj->Unix, 'keep in loop deadline ignored for stalled';
+    }
+
+    # non-requestor reply again
+    {
+        sleep 1;
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+        $ticket->Correspond( Content => 'we are still working on this.' );
+        $ticket->SetStatus('open');
+
+        $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        is $ticket->Status, 'open', 'ticket was opened';
+
+        my $tmp = $ticket->DueObj->Unix;
+        ok $tmp > 0, 'Due date is set';
+        ok $tmp > $due, "keep in loop sligtly moved";
+        $due = $tmp;
+    }
+}
+
+diag 'Check that failing to reply to the requestors is not ignored' if $ENV{'TEST_VERBOSE'};
+{
+    %RT::ServiceAgreements = (
+        Default => '2',
+        Levels => {
+            '2' => {
+                Response   => { RealMinutes => 60*2 },
+                KeepInLoop => { RealMinutes => 60*4, IgnoreOnStatuses => ['stalled'] },
+            },
+        },
+    );
+
+    my $root = RT::User->new( $RT::SystemUser );
+    $root->LoadByEmail('root at localhost');
+    ok $root->id, 'loaded root user';
+
+    # requestor creates
+    my $id;
+    my $due;
+    {
+        my $ticket = RT::Ticket->new( $root );
+        ($id) = $ticket->Create(
+            Queue => 'General',
+            Subject => 'xxx',
+            Requestor => $root->id,
+        );
+        ok $id, "created ticket #$id";
+        is $ticket->SLA, '2', 'default sla';
+        $due = $ticket->DueObj->Unix;
+        ok $due > 0, 'response deadline';
+    }
+
+    # stalling ticket
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+        my ($status, $msg) = $ticket->SetStatus('stalled');
+        ok $status, 'stalled the ticket';
+
+        $ticket->Load( $id );
+        my $tmp = $ticket->DueObj->Unix;
+        ok $tmp, 'response deadline not unset';
+        is $tmp, $due, 'due not changed';
+    }
+
+    # non-requestor reply
+    {
+        sleep 1;
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+        $ticket->Correspond( Content => 'we are still working on this.' );
+        $ticket->SetStatus('open');
+
+        $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        is $ticket->Status, 'open', 'ticket was opened';
+
+        my $tmp = $ticket->DueObj->Unix;
+        ok $tmp > 0, 'Due date is set';
+        ok $tmp > $due, "keep in loop is greater than response";
+        $due = $tmp;
+    }
+
+    # stalling ticket again
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+        my ($status, $msg) = $ticket->SetStatus('stalled');
+        ok $status, 'stalled the ticket';
+
+        $ticket->Load( $id );
+        ok !$ticket->DueObj->Unix, 'keep in loop deadline unset for stalled';
+    }
+}
+
+diag 'check the ExcludeTimeOnIgnoredStatuses option' if $ENV{'TEST_VERBOSE'};
+{
+    %RT::ServiceAgreements = (
+        Default => '2',
+        Levels => {
+            '2' => {
+                Response => { RealMinutes => 60*2, IgnoreOnStatuses => ['stalled'] },
+            },
+        },
+    );
+
+    my $root = RT::User->new( $RT::SystemUser );
+    $root->LoadByEmail('root at localhost');
+    ok $root->id, 'loaded root user';
+
+    my $bob =
+      RT::Test->load_or_create_user( Name => 'bob', EmailAddress => 'bob at example.com', Password => 'password' );
+    ok( $bob->Id, "Created test user bob" );
+    ok( RT::Test->add_rights( { Principal => 'Privileged', Right => [ qw(CreateTicket ShowTicket ModifyTicket SeeQueue) ] } ),
+        'Granted ticket management rights' );
+
+    # requestor creates
+    my $id;
+    my $due;
+    {
+        my $ticket = RT::Ticket->new( $bob );
+        ($id) = $ticket->Create(
+            Queue => 'General',
+            Subject => 'xxx',
+            Requestor => $bob->id,
+        );
+        ok $id, "created ticket #$id";
+        is $ticket->SLA, '2', 'default sla';
+        $due = $ticket->DueObj->Unix;
+        ok $due > 0, 'response deadline';
+    }
+
+    # stalling ticket
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+        my ($status, $msg) = $ticket->SetStatus('stalled');
+        ok $status, 'stalled the ticket';
+
+        $ticket->Load( $id );
+        ok !$ticket->DueObj->Unix, 'deadline ignored for stalled';
+    }
+
+    # requestor reply again
+    {
+        sleep 1;
+        my $ticket = RT::Ticket->new( $bob );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+        $ticket->Correspond( Content => 'please reopen this ticket, we are good to continue' );
+        $ticket->SetStatus('open');
+
+        $ticket = RT::Ticket->new( $root );
+        $ticket->Load( $id );
+        ok $ticket->id, "loaded ticket #$id";
+
+        is $ticket->Status, 'open', 'ticket was opened';
+
+        my $tmp = $ticket->DueObj->Unix;
+        ok $tmp > 0, 'Due date is set';
+        ok $tmp == $due, "deadline not changed";
+    }
+
+    %RT::ServiceAgreements = (
+        Default => '2',
+        Levels => {
+            '2' => {
+                Response => { RealMinutes => 60*2, IgnoreOnStatuses => ['stalled'], ExcludeTimeOnIgnoredStatuses => 1 },
+            },
+        },
+    );
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        $ticket->Load( $id );
+        my ($status, $msg) = $ticket->SetStatus('stalled');
+        ok $status, 'stalled the ticket';
+        ok !$ticket->DueObj->Unix, 'deadline ignored for stalled';
+        sleep 1;
+        $ticket->SetStatus('open');
+        is $ticket->Status, 'open', 'ticket was opened';
+        my $tmp = $ticket->DueObj->Unix;
+        ok $tmp > 0, 'Due date is set';
+        ok $tmp >= $due+1, "deadline slighted moved";
+        ok $tmp <= $due+5, "deadline slighted moved but not much";
+    }
+}
+
+done_testing;
diff --git a/t/sla/queue.t b/t/sla/queue.t
new file mode 100644
index 0000000..622d21f
--- /dev/null
+++ b/t/sla/queue.t
@@ -0,0 +1,52 @@
+use strict;
+use warnings;
+
+use Test::MockTime qw( :all );
+use RT::Test tests => undef;
+
+my $queue = RT::Queue->new($RT::SystemUser);
+$queue->Load('General');
+
+my $queue_sla = RT::Attribute->new($RT::SystemUser);
+
+diag 'check set of Due date with Queue default SLA' if $ENV{'TEST_VERBOSE'};
+{
+
+    # add default SLA for 'General';
+    my ($id) = $queue_sla->Create(
+        Name        => 'SLA',
+        Description => 'Default Queue SLA',
+        Content     => '4',
+        Object      => $queue
+    );
+
+    ok( $id, 'Created SLA Attribute for General' );
+
+    no warnings 'once';
+    %RT::ServiceAgreements = (
+        Default => '2',
+        Levels  => {
+            '2' => { Resolve => { RealMinutes => 60 * 2 } },
+            '4' => { StartImmediately => 1, Resolve => { RealMinutes => 60 * 4 } },
+        },
+    );
+
+
+    set_absolute_time('2007-01-01T00:00:00Z');
+    my $time = time;
+    my $ticket = RT::Ticket->new($RT::SystemUser);
+    ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx' );
+    ok( $id, "created ticket #$id" );
+
+    is $ticket->SLA, '4', 'default sla';
+
+    my $start = $ticket->StartsObj->Unix;
+    my $due = $ticket->DueObj->Unix;
+    is( $start, $time, 'Start Date is right' );
+    is( $due, $time+3600*4, 'Due date is right');
+
+    my ( $status, $message ) = $queue->DeleteAttribute('SLA');
+    ok( $status, $message );
+}
+
+done_testing;
diff --git a/t/sla/starts.t b/t/sla/starts.t
new file mode 100644
index 0000000..4769c62
--- /dev/null
+++ b/t/sla/starts.t
@@ -0,0 +1,79 @@
+use strict;
+use warnings;
+
+use Test::MockTime qw( :all );
+use RT::Test tests => undef;
+
+# we assume the RT's Timezone is UTC now, need a smart way to get over that.
+$ENV{'TZ'} = 'GMT';
+RT->Config->Set( Timezone => 'GMT' );
+
+my $bhours = RT::SLA->BusinessHours;
+
+diag 'check Starts date' if $ENV{'TEST_VERBOSE'};
+{
+    %RT::ServiceAgreements = (
+        Default => 'standard',
+        Levels  => {
+            'standard' => {
+                Response => 2 * 60,
+                Resolve  => 7 * 60 * 24,
+            },
+        },
+    );
+    %RT::ServiceBusinessHours = (
+        Default => {
+            1 => {
+                Name  => 'Monday',
+                Start => '09:00',
+                End   => '17:00'
+            },
+            2 => {
+                Name  => 'Tuesday',
+                Start => '09:00',
+                End   => '17:00'
+            },
+        }
+    );
+
+    my %time = (
+        '2007-01-01T13:15:00Z' => 1167657300,    # 2007-01-01T13:15:00Z
+        '2007-01-01T19:15:00Z' => 1167728400,    # 2007-01-02T09:00:00Z
+        '2007-01-06T13:15:00Z' => 1168246800,    # 2007-01-08T09:00:00Z
+    );
+
+    for my $time ( keys %time ) {
+        set_absolute_time($time);
+        my $ticket = RT::Ticket->new($RT::SystemUser);
+        my ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx' );
+        ok $id, "created ticket #$id";
+        is $ticket->StartsObj->Unix, $time{$time}, 'Starts date is right';
+    }
+
+    restore_time();
+}
+
+diag 'check Starts date with StartImmediately enabled' if $ENV{'TEST_VERBOSE'};
+{
+    %RT::ServiceAgreements = (
+        Default => 'start immediately',
+        Levels  => {
+            'start immediately' => {
+                StartImmediately => 1,
+                Response         => 2 * 60,
+                Resolve          => 7 * 60 * 24,
+            },
+        },
+    );
+    my $time = time;
+
+    my $ticket = RT::Ticket->new($RT::SystemUser);
+    my ($id) = $ticket->Create( Queue => 'General', Subject => 'xxx' );
+    ok $id, "created ticket #$id";
+
+    my $starts = $ticket->StartsObj->Unix;
+    ok $starts > 0, 'Starts date is set';
+    is $starts, $ticket->CreatedObj->Unix, 'Starts is correct';
+}
+
+done_testing;
diff --git a/t/sla/timezone.t b/t/sla/timezone.t
new file mode 100644
index 0000000..8edf894
--- /dev/null
+++ b/t/sla/timezone.t
@@ -0,0 +1,51 @@
+use strict;
+use warnings;
+
+use Test::MockTime qw( :all );
+use RT::Test tests => undef;
+
+my $ru_queue = RT::Test->load_or_create_queue( Name => 'RU' );
+ok $ru_queue && $ru_queue->id, 'created RU queue';
+
+my $us_queue = RT::Test->load_or_create_queue( Name => 'US' );
+ok $us_queue && $ru_queue->id, 'created US queue';
+
+no warnings 'once';
+%RT::ServiceAgreements = (
+    Default => 2,
+    QueueDefault => {
+        RU => { Timezone => 'Europe/Moscow' },
+        US => { Timezone => 'America/New_York' },
+    },
+    Levels  => {
+        '2' => { Resolve => { BusinessMinutes => 60 * 2 } },
+    },
+);
+
+set_absolute_time('2007-01-01T22:00:00Z');
+
+diag 'check dates in US queue' if $ENV{'TEST_VERBOSE'};
+{
+    my $ticket = RT::Ticket->new($RT::SystemUser);
+    my ($id) = $ticket->Create( Queue => 'US', Subject => 'xxx' );
+    ok( $id, "created ticket #$id" );
+
+    my $start = $ticket->StartsObj->ISO( Timezone => 'utc' );
+    is( $start, '2007-01-01 22:00:00', 'Start date is right' );
+    my $due = $ticket->DueObj->ISO( Timezone => 'utc' );
+    is( $due, '2007-01-02 15:00:00', 'Due date is right' );
+}
+
+diag 'check dates in RU queue' if $ENV{'TEST_VERBOSE'};
+{
+    my $ticket = RT::Ticket->new($RT::SystemUser);
+    my ($id) = $ticket->Create( Queue => 'RU', Subject => 'xxx' );
+    ok( $id, "created ticket #$id" );
+
+    my $start = $ticket->StartsObj->ISO( Timezone => 'utc' );
+    is( $start, '2007-01-02 06:00:00', 'Start date is right' );
+    my $due = $ticket->DueObj->ISO( Timezone => 'utc' );
+    is( $due, '2007-01-02 08:00:00', 'Due date is right' );
+}
+
+done_testing;
diff --git a/t/sla/web.t b/t/sla/web.t
new file mode 100644
index 0000000..d62d45b
--- /dev/null
+++ b/t/sla/web.t
@@ -0,0 +1,106 @@
+use strict;
+use warnings;
+
+my $now;
+
+BEGIN {
+    $now = time();
+    use Test::MockTime 'set_fixed_time';
+    set_fixed_time( $now );
+
+    use RT::Test tests => undef;
+}
+
+%RT::ServiceAgreements = (
+    Default => '2',
+    Levels  => {
+        '2' => {
+            StartImmediately => 1,
+            Response => { RealMinutes => 60 * 2 },
+        },
+        '4' => {
+            StartImmediately => 1,
+            Response => { RealMinutes => 60 * 4 },
+        },
+    },
+);
+
+my $queue = RT::Test->load_or_create_queue( Name => 'General' );
+
+my $user = RT::Test->load_or_create_user(
+    Name         => 'user',
+    Password     => 'password',
+    EmailAddress => 'user at example.com',
+);
+
+my ( $baseurl, $m ) = RT::Test->started_ok;
+ok(
+    RT::Test->set_rights(
+        { Principal => $user, Right => [ qw(SeeQueue CreateTicket ShowTicket ModifyTicket ShowConfigTab AdminQueue) ] },
+    ),
+    'set rights'
+);
+
+ok $m->login( 'user', 'password' ), 'logged in as user';
+
+{
+
+    $m->goto_create_ticket( $queue->id );
+    my $form = $m->form_name( 'TicketCreate' );
+    my $sla  = $form->find_input( 'SLA' );
+    is_deeply( [$sla->possible_values], [ 2, 4 ], 'possible sla' );
+    $m->submit_form( fields => { Subject => 'ticket foo with default sla' } );
+
+    my $ticket = RT::Test->last_ticket;
+    ok( $ticket->id, 'ticket is created' );
+    my $id = $ticket->id;
+    is( $ticket->SLA,             2,                  'default SLA is 2' );
+    is( $ticket->StartsObj->Unix, $now,               'Starts' );
+    is( $ticket->DueObj->Unix,    $now + 60 * 60 * 2, 'Due' );
+}
+
+{
+    $m->goto_create_ticket( $queue->id );
+    my $form = $m->form_name( 'TicketCreate' );
+    $m->submit_form( fields => { Subject => 'ticket foo with default sla', SLA => 4 } );
+
+    my $ticket = RT::Test->last_ticket;
+    ok( $ticket->id, 'ticket is created' );
+    my $id = $ticket->id;
+    is( $ticket->SLA,             4,                  'SLA is set to 4' );
+    is( $ticket->StartsObj->Unix, $now,               'Starts' );
+    is( $ticket->DueObj->Unix,    $now + 60 * 60 * 4, 'Due' );
+    $m->follow_link_ok( { text => 'Basics' }, 'Ticket -> Basics' );
+    $m->submit_form(
+        form_name => 'TicketModify',
+        fields    => { SLA => 2 },
+    );
+    $ticket->Load( $id );
+    is( $ticket->SLA, 2, 'SLA is set to 2' );
+    is( $ticket->DueObj->Unix, $now + 60 * 60 * 2, 'Due is updated accordingly' );
+}
+
+{
+    $m->get_ok( $baseurl . '/Admin/Queues/Modify.html?id=' . $queue->id );
+    my $form = $m->form_name( 'ModifyQueue' );
+    $m->untick( 'SLAEnabled', 1 );
+    $m->submit;
+    $m->text_contains( q{SLADisabled changed from (no value) to "1"} );
+}
+
+{
+
+    $m->goto_create_ticket( $queue->id );
+    my $form = $m->form_name( 'TicketCreate' );
+    ok( !$form->find_input( 'SLA' ), 'no SLA input' );
+    $m->submit_form( fields => { Subject => 'ticket foo without sla' } );
+
+    my $ticket = RT::Test->last_ticket;
+    ok( $ticket->id,               'ticket is created' );
+    ok( !$ticket->SLA,             'no SLA' );
+    ok( !$ticket->StartsObj->Unix, 'no Starts' );
+    ok( !$ticket->DueObj->Unix,    'no Due' );
+}
+
+undef $m;
+done_testing();

commit 41e59c48bbd115a0b4bec37b9c112dad2470f7da
Merge: 082c018 e4b30a5
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Fri Aug 14 13:06:50 2015 -0400

    Merge branch '4.4/sla'


-----------------------------------------------------------------------


More information about the rt-commit mailing list