[Rt-commit] rt branch, 3.9-trunk, updated. rt-3.8.8-337-ge00834b

Jesse Vincent jesse at bestpractical.com
Mon Aug 16 14:21:25 EDT 2010


The branch, 3.9-trunk has been updated
       via  e00834b0ef876b466bafab3f68af4bcb05f3526b (commit)
       via  485ce304c45004ee118ad1c4c40751dc539c91de (commit)
       via  8fb43d94fbe32ca1ecd6fa3e7a73c4616374a1b8 (commit)
       via  bf06ed64fa0a47f898e27bc3a0dd0f277e2ed2ff (commit)
       via  feb330cc28007a7e3f5f3388fe272b62b45d3780 (commit)
       via  a5f94dec9ba35c1368140a4341f75968cfd49461 (commit)
       via  cf46f8ea707f4318e6dd31f067c922a9a7e12c38 (commit)
       via  b206b50ad8ec1050fe855c20bb4f12736b774e6b (commit)
       via  c58d2fa9b7a912b8497a1bb7a8eb46f14f1ee7be (commit)
       via  3a5697ff692d22f26d6a16a903c485c4ebf8a110 (commit)
       via  a9a7f43a7314bf1faacb19faf88bbe8085bca7de (commit)
       via  ceafabc115025e830d5d720826280192a1bd5b4b (commit)
       via  cdea1e27cfa471f6226e1a9cef4be124cc791006 (commit)
       via  2ad74ba2bd8c6609ea89d7e9dacc200f8b006469 (commit)
      from  c122d2df2b2b6a5191c570e058aa757e70d3e279 (commit)

Summary of changes:
 etc/RT_Config.pm.in                                |  248 +++++++-
 lib/RT.pm.in                                       |    1 +
 lib/RT/Action/AutoOpen.pm                          |   63 ++-
 lib/RT/Action/SetStatus.pm                         |  151 +++++
 lib/RT/Condition/StatusChange.pm                   |   83 +++-
 lib/RT/Queue_Overlay.pm                            |   86 +--
 lib/RT/Rule.pm                                     |    7 +-
 lib/RT/StatusSchema.pm                             |  687 ++++++++++++++++++++
 lib/RT/StatusSchemas.pm                            |    9 +
 lib/RT/Ticket_Overlay.pm                           |  198 ++++--
 share/html/Elements/PageLayout                     |    2 +-
 .../{QueueSummary => QueueSummaryByStatus}         |   94 ++-
 .../{QueueSummary => QueueSummaryByStatusSchema}   |   95 ++-
 share/html/Elements/Quicksearch                    |   67 +--
 share/html/Elements/SelectStatus                   |   89 +--
 share/html/REST/1.0/Forms/ticket/default           |    2 +-
 share/html/Ticket/Create.html                      |    1 +
 share/html/Ticket/Elements/EditBasics              |    2 +
 share/html/Ticket/Elements/Tabs                    |  202 ++++---
 share/html/Ticket/Update.html                      |    2 +
 t/shredder/03plugin_tickets.t                      |    7 +-
 t/status-schemas/basics.t                          |  218 +++++++
 t/status-schemas/dates.t                           |  314 +++++++++
 t/status-schemas/moving.t                          |   96 +++
 t/status-schemas/utils.pl                          |   67 ++
 t/web/command_line.t                               |    2 +-
 26 files changed, 2360 insertions(+), 433 deletions(-)
 create mode 100755 lib/RT/Action/SetStatus.pm
 create mode 100644 lib/RT/StatusSchema.pm
 create mode 100644 lib/RT/StatusSchemas.pm
 copy share/html/Elements/{QueueSummary => QueueSummaryByStatus} (69%)
 copy share/html/Elements/{QueueSummary => QueueSummaryByStatusSchema} (68%)
 mode change 100755 => 100644 share/html/Elements/Quicksearch
 mode change 100755 => 100644 share/html/Elements/SelectStatus
 create mode 100644 t/status-schemas/basics.t
 create mode 100644 t/status-schemas/dates.t
 create mode 100644 t/status-schemas/moving.t
 create mode 100644 t/status-schemas/utils.pl

- Log -----------------------------------------------------------------
commit 2ad74ba2bd8c6609ea89d7e9dacc200f8b006469
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Thu Aug 12 17:41:47 2010 -0400

    Initial merge of StatusSchemas

diff --git a/lib/RT/Action/AutoOpen.pm b/lib/RT/Action/AutoOpen.pm
index 4112830..2d5c030 100755
--- a/lib/RT/Action/AutoOpen.pm
+++ b/lib/RT/Action/AutoOpen.pm
@@ -51,49 +51,72 @@ package RT::Action::AutoOpen;
 
 use strict;
 use warnings;
-
 use base qw(RT::Action);
 
 =head1 DESCRIPTION
 
-Opens a ticket unless it's allready open, but only unless transaction
-L<RT::Transaction/IsInbound is inbound>.
+AutoOpen in RT 3.8 sets status of a ticket to 'open' without considering
+custom statuses and possibility that there is no 'open' status in the system.
+This extension overrides behavior of the action.
+
+Status is not changed if there is no active statuses in the schema.
+
+Status is not changed if the current status is first active for ticket's status
+schema. For example if ticket's status is 'processing' and active statuses are
+'processing', 'on hold' and 'waiting' then status is not changed, but for ticket
+with status 'on hold' other rules are checked.
+
+Status is not changed if it's initial and creator of the current transaction
+is one of requestors.
+
+Status is not changed if message's head has field C<RT-Control> with C<no-autoopen>
+substring.
 
-Doesn't open a ticket if message's head has field C<RT-Control> with
-C<no-autoopen> substring.
+Status is set to the first possible active status. It means that if ticket's
+status is X then RT finds all possible transitions from this status and selects
+first active status in the list.
 
 =cut
 
 sub Prepare {
     my $self = shift;
 
-    # if the ticket is already open or the ticket is new and the message is more mail from the
-    # requestor, don't reopen it.
+    my $ticket = $self->TicketObj;
+    my $schema = $ticket->QueueObj->status_schema;
+    my $status = $ticket->Status;
 
-    my $status = $self->TicketObj->Status;
-    return undef if $status eq 'open';
-    return undef if $status eq 'new' && $self->TransactionObj->IsInbound;
+    my @active = $schema->active;
+    # no change if no active statuses in the schema
+    return 1 unless @active;
+
+    # no change if the ticket is already has first status from the list of active
+    return 1 if lc $status eq lc $active[0];
+
+    # no change if the ticket is in initial status and the message is a mail
+    # from a requestor
+    return 1 if $schema->is_initial($status) && $self->TransactionObj->IsInbound;
 
     if ( my $msg = $self->TransactionObj->Message->First ) {
-        return undef if ($msg->GetHeader('RT-Control') || '') =~ /\bno-autoopen\b/i;
+        return 1 if ($msg->GetHeader('RT-Control') || '') =~ /\bno-autoopen\b/i;
     }
 
+    my ($next) = grep $schema->is_active($_), $schema->transitions($status);
+
+    $self->{'set_status_to'} = $next;
+
     return 1;
 }
 
 sub Commit {
     my $self = shift;
 
-    my $oldstatus = $self->TicketObj->Status;
-    $self->TicketObj->__Set( Field => 'Status', Value => 'open' );
-    $self->TicketObj->_NewTransaction(
-        Type     => 'Status',
-        Field    => 'Status',
-        OldValue => $oldstatus,
-        NewValue => 'open',
-        Data     => 'Ticket auto-opened on incoming correspondence'
-    );
+    return 1 unless my $new_status = $self->{'set_status_to'};
 
+    my ($val, $msg) = $self->TicketObj->SetStatus( $new_status );
+    unless ( $val ) {
+        $RT::Logger->error( "Couldn't auto-open ticket: ". $msg );
+        return 0;
+    }
     return 1;
 }
 
diff --git a/lib/RT/Action/SetStatus.pm b/lib/RT/Action/SetStatus.pm
new file mode 100755
index 0000000..5db149f
--- /dev/null
+++ b/lib/RT/Action/SetStatus.pm
@@ -0,0 +1,151 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC
+#                                          <jesse 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 }}}
+package RT::Action::SetStatus;
+
+use strict;
+use warnings;
+use base qw(RT::Action);
+
+=head1 NAME
+
+RT::Action::SetStatus - RT's scrip action to set status of a ticket
+
+=head1 DESCRIPTION
+
+This action changes status to a new value according to L</ARGUMENT>.
+Status is not changed if transition is invalid or on other errors. All
+issues are logged with different level.
+
+=head1 ARGUMENT
+
+Argument can be one of the following:
+
+=over 4
+
+=item status literally
+
+Status is changed from the current value to a new defined by the argument,
+but only if it's valid status and allowed by transitions of the current schema,
+for example:
+
+    * The current status is 'stalled'
+    * Argument of this action is 'open'
+    * The only possible transition in the scheam from 'stalled' is 'open'
+    * Status is changed
+
+However, in the example above Status is not changed if argument is anything
+else as it's just not allowed by the schema.
+
+=item 'initial', 'active' or 'inactive'
+
+Status is changed from the current value to first possible 'initial',
+'active' or 'inactive' correspondingly. First possible value is figured
+according to transitions to the target set, for example:
+
+    * The current status is 'open'
+    * Argument of this action is 'inactive'
+    * Possible transitions from 'open' are 'resolved', 'rejected' or 'deleted'
+    * Status is changed to 'resolved'
+
+=back
+
+=cut
+
+sub Prepare {
+    my $self = shift;
+
+    my $ticket = $self->TicketObj;
+    my $schema = $ticket->QueueObj->status_schema;
+    my $status = $ticket->Status;
+
+    my $argument = $self->Argument;
+    unless ( $argument ) {
+        $RT::Logger->error("Argument is mandatory for SetStatus action");
+        return 0;
+    }
+
+    my $next = '';
+    if ( $argument =~ /^(initial|active|inactive)$/i ) {
+        my $method = 'is_'. lc $argument;
+        ($next) = grep $schema->$method($_), $schema->transitions($status);
+        unless ( $next ) {
+            $RT::Logger->info("No transition from '$status' to $argument set");
+            return 1;
+        }
+    }
+    elsif ( $schema->is_valid( $argument ) ) {
+        unless ( $schema->is_transition( $status => $argument ) ) {
+            $RT::Logger->warning("Transition '$status -> $argument' is not valid");
+            return 1;
+        }
+        $next = $argument;
+    }
+    else {
+        $RT::Logger->error("Argument for SetStatus action is not valid status or one of set");
+        return 0;
+    }
+
+    $self->{'set_status_to'} = $next;
+
+    return 1;
+}
+
+sub Commit {
+    my $self = shift;
+
+    return 1 unless my $new_status = $self->{'set_status_to'};
+
+    my ($val, $msg) = $self->TicketObj->SetStatus( $new_status );
+    unless ( $val ) {
+        $RT::Logger->error( "Couldn't set status: ". $msg );
+        return 0;
+    }
+    return 1;
+}
+
+1;
diff --git a/lib/RT/Condition/StatusChange.pm b/lib/RT/Condition/StatusChange.pm
index 0bdc69a..0dc1d37 100755
--- a/lib/RT/Condition/StatusChange.pm
+++ b/lib/RT/Condition/StatusChange.pm
@@ -50,23 +50,90 @@ package RT::Condition::StatusChange;
 use base 'RT::Condition';
 use strict;
 
+=head2 DESCRIPTION
 
-=head2 IsApplicable
+Condition check passes if the current transaction is a status change.
 
-If the argument passed in is equivalent to the new value of
-the Status Obj
+Argument can be used to apply additional conditions on old and new values.
+
+If argument is empty then check pass for any change of the status field.
+
+If argument is equal to new value then check is passed. This is behavior
+is close to RT 3.8 and older. For example argumen equal 'resolved' means
+'fire scrip when status changed from any to resolved'.
+
+The following extended format is supported:
+
+    old: comma separated list; new: comma separated list
+
+For example:
+
+    old: open; new: resolved
+
+You can omit old or new part, for example:
+
+    old: open
+
+    new: resolved
+
+You can specify multiple values, for example:
+
+    old: new, open; new: resolved, rejected
+
+Status sets ('initial', 'active' or 'inactive') can be used, for example:
+
+    old: active; new: inactive
+
+    old: initial, active; new: resolved
 
 =cut
 
 sub IsApplicable {
     my $self = shift;
-    if (($self->TransactionObj->Field eq 'Status') and 
-    ($self->Argument eq $self->TransactionObj->NewValue())) {
-	return(1);
-    } 
+    my $txn = $self->TransactionObj;
+    my ($type, $field) = ($txn->Type, $txn->Field);
+    return 0 unless $type eq 'Status' || ($type eq 'Set' && $field eq 'Status');
+
+    my $argument = $self->Argument;
+    return 1 unless $argument;
+
+    my $new = $txn->NewValue || '';
+    return 1 if $argument eq $new;
+
+    # let's parse argument
+    my ($old_must_be, $new_must_be) = ('', '');
+    if ( $argument =~ /^\s*old:\s*(.*);\s*new:\s*(.*)\s*$/i ) {
+        ($old_must_be, $new_must_be) = ($1, $2);
+    }
+    elsif ( $argument =~ /^\s*new:\s*(.*)\s*$/i ) {
+        $new_must_be = $1;
+    }
+    elsif ( $argument =~ /^\s*old:\s*(.*)\s*$/i ) {
+        $old_must_be = $1;
+    }
     else {
-	return(undef);
+        $RT::Logger->error("Argument '$argument' is incorrect.")
+            unless RT::StatusSchema->load('')->is_valid( $argument );
+        return 0;
+    }
+
+    my $schema = $self->TicketObj->QueueObj->status_schema;
+    if ( $new_must_be ) {
+        return 0 unless grep lc($new) eq lc($_),
+            map {m/^(initial|active|inactive)$/i? $schema->valid(lc $_): $_ }
+            grep defined && length,
+            map { s/^\s+//; s/\s+$//; $_ }
+            split /,/, $new_must_be;
+    }
+    if ( $old_must_be ) {
+        my $old = lc($txn->OldValue || '');
+        return 0 unless grep $old eq lc($_),
+            map {m/^(initial|active|inactive)$/i? $schema->valid(lc $_): $_ }
+            grep defined && length,
+            map { s/^\s+//; s/\s+$//; $_ }
+            split /,/, $old_must_be;
     }
+    return 1;
 }
 
 eval "require RT::Condition::StatusChange_Vendor";
diff --git a/lib/RT/Queue_Overlay.pm b/lib/RT/Queue_Overlay.pm
index 3eedb3c..cb97479 100755
--- a/lib/RT/Queue_Overlay.pm
+++ b/lib/RT/Queue_Overlay.pm
@@ -188,6 +188,20 @@ sub AvailableRights {
 
 # {{{ ActiveStatusArray
 
+sub status_schema {
+    my $self = shift;
+    require RT::StatusSchema;
+    return RT::StatusSchema->load('') unless ref $self && $self->id;
+
+    # If you don't have StatusSchemas set, name is default
+    my $schemas = RT->Config->Get('StatusSchemas');
+    my $name = $schemas ? ($schemas->{ $self->Name } || 'default') : 'default';
+
+    my $res = RT::StatusSchema->load( $name );
+    $RT::Logger->error("Status schema '$name' for queue '".$self->Name."' doesn't exist") unless $res;
+    return $res;
+}
+
 =head2 ActiveStatusArray
 
 Returns an array of all ActiveStatuses for this queue
@@ -196,18 +210,9 @@ Returns an array of all ActiveStatuses for this queue
 
 sub ActiveStatusArray {
     my $self = shift;
-    if (RT->Config->Get('ActiveStatus')) {
-    	return (RT->Config->Get('ActiveStatus'))
-    } else {
-        $RT::Logger->warning("RT::ActiveStatus undefined, falling back to deprecated defaults");
-        return (@DEFAULT_ACTIVE_STATUS);
-    }
+    return $self->status_schema->valid('initial', 'active');
 }
 
-# }}}
-
-# {{{ InactiveStatusArray
-
 =head2 InactiveStatusArray
 
 Returns an array of all InactiveStatuses for this queue
@@ -216,18 +221,9 @@ Returns an array of all InactiveStatuses for this queue
 
 sub InactiveStatusArray {
     my $self = shift;
-    if (RT->Config->Get('InactiveStatus')) {
-    	return (RT->Config->Get('InactiveStatus'))
-    } else {
-        $RT::Logger->warning("RT::InactiveStatus undefined, falling back to deprecated defaults");
-        return (@DEFAULT_INACTIVE_STATUS);
-    }
+    return $self->status_schema->inactive;
 }
 
-# }}}
-
-# {{{ StatusArray
-
 =head2 StatusArray
 
 Returns an array of all statuses for this queue
@@ -236,71 +232,45 @@ Returns an array of all statuses for this queue
 
 sub StatusArray {
     my $self = shift;
-    return ($self->ActiveStatusArray(), $self->InactiveStatusArray());
+    return $self->status_schema->valid( @_ );
 }
 
-# }}}
-
-# {{{ IsValidStatus
-
-=head2 IsValidStatus VALUE
-
-Returns true if VALUE is a valid status.  Otherwise, returns 0.
+=head2 IsValidStatus value
 
+Returns true if value is a valid status.  Otherwise, returns 0.
 
 =cut
 
 sub IsValidStatus {
     my $self  = shift;
-    my $value = shift;
-
-    my $retval = grep ( $_ eq $value, $self->StatusArray );
-    return ($retval);
-
+    return $self->status_schema->is_valid( shift );
 }
 
-# }}}
-
-# {{{ IsActiveStatus
-
-=head2 IsActiveStatus VALUE
-
-Returns true if VALUE is a Active status.  Otherwise, returns 0
+=head2 IsActiveStatus value
 
+Returns true if value is a Active status.  Otherwise, returns 0
 
 =cut
 
 sub IsActiveStatus {
     my $self  = shift;
-    my $value = shift;
-
-    my $retval = grep ( $_ eq $value, $self->ActiveStatusArray );
-    return ($retval);
-
+    return $self->status_schema->is_valid( shift, 'initial', 'active');
 }
 
-# }}}
 
-# {{{ IsInactiveStatus
 
-=head2 IsInactiveStatus VALUE
+=head2 IsInactiveStatus value
 
-Returns true if VALUE is a Inactive status.  Otherwise, returns 0
+Returns true if value is a Inactive status.  Otherwise, returns 0
 
 
 =cut
 
 sub IsInactiveStatus {
     my $self  = shift;
-    my $value = shift;
-
-    my $retval = grep ( $_ eq $value, $self->InactiveStatusArray );
-    return ($retval);
-
+    return $self->status_schema->is_inactive( shift );
 }
 
-# }}}
-
 
 # {{{ sub Create
 
diff --git a/lib/RT/StatusSchema.pm b/lib/RT/StatusSchema.pm
new file mode 100644
index 0000000..fa0750e
--- /dev/null
+++ b/lib/RT/StatusSchema.pm
@@ -0,0 +1,656 @@
+
+use strict;
+use warnings;
+
+package RT::StatusSchema;
+
+sub loc { return $RT::SystemUser->loc( @_ ) }
+
+our %STATUS_SCHEMAS;
+our %STATUS_SCHEMAS_CACHE;
+
+# cache structure:
+#    {
+#        '' => { # all valid statuses
+#            '' => [...],
+#            initial => [...],
+#            active => [...],
+#            inactive => [...],
+#        },
+#        schema_x => {
+#            '' => [...], # all valid in schema
+#            initial => [...],
+#            active => [...],
+#            inactive => [...],
+#            transitions => {
+#               status_x => [status_next1, status_next2,...],
+#            },
+#            rights => {
+#               'status_y -> status_y' => 'right',
+#               ....
+#            }
+#            actions => {
+#               'status_y -> status_y' => [ transition_label, transition_action ],
+#               ....
+#            }
+#        }
+#    }
+
+=head1 NAME
+
+RT::StatusSchema - class to access and manipulate status schemas
+
+=head1 DESCRIPTION
+
+A status schema is a list of statuses that a ticket can have. There are three
+groups of statuses: initial, active and inactive. A status schema also defines
+possible transitions between statuses. For example, in the 'default' schema,
+you may only change status from 'stalled' to 'open'.
+
+It is also possible to define user-interface labels and the action a user
+should perform during a transition. For example, the "open -> stalled"
+transition would have a 'Stall' label and the action would be Comment. The
+action only defines what form is showed to the user, but actually performing
+the action is not required. The user can leave the comment box empty yet still
+Stall a ticket. Finally, the user can also just use the Basics or Jumbo form to
+change the status with the usual dropdown.
+
+=head1 METHODS
+
+=head2 new
+
+Simple constructor, takes no arguments.
+
+=cut
+
+sub new {
+    my $proto = shift;
+    my $self = bless {}, ref($proto) || $proto;
+
+    $self->fill_cache unless keys %STATUS_SCHEMAS_CACHE;
+
+    return $self;
+}
+
+=head2 load
+
+Takes a name of the schema and loads it. If name is empty or undefined then
+loads the global schema with statuses from all named schemas.
+
+Can be called as class method, returns a new object, for example:
+
+    my $schema = RT::StatusSchema->load('default');
+
+=cut
+
+sub load {
+    my $self = shift;
+    my $name = shift || '';
+    return $self->new->load( $name, @_ )
+        unless ref $self;
+
+    return unless exists $STATUS_SCHEMAS_CACHE{ $name };
+
+    $self->{'name'} = $name;
+    $self->{'data'} = $STATUS_SCHEMAS_CACHE{ $name };
+
+    return $self;
+}
+
+=head2 list
+
+Returns sorted list of the schemas' names.
+
+=cut
+
+sub list {
+    my $self = shift;
+
+    $self->fill_cache unless keys %STATUS_SCHEMAS_CACHE;
+
+    return sort grep length && $_ ne '__maps__', keys %STATUS_SCHEMAS_CACHE;
+}
+
+=head2 name
+
+Returns name of the laoded schema.
+
+=cut
+
+sub name { return $_[0]->{'name'} }
+
+=head2 Getting statuses and validatiing.
+
+Methods to get statuses in different sets or validating them.
+
+=head3 valid
+
+Returns an array of all valid statuses for the current schema.
+Statuses are not sorted alphabetically, instead initial goes first,
+then active and then inactive.
+
+Takes optional list of status types, from 'initial', 'active' or
+'inactive'. For example:
+
+    $schema->valid('initial', 'active');
+
+=cut
+
+sub valid {
+    my $self = shift;
+    my @types = @_;
+    unless ( @types ) {
+        return @{ $self->{'data'}{''} || [] };
+    }
+
+    my @res;
+    push @res, @{ $self->{'data'}{ $_ } || [] } foreach @types;
+    return @res;
+}
+
+=head3 is_valid
+
+Takes a status and returns true if value is a valid status for the current
+schema. Otherwise, returns false.
+
+Takes optional list of status types after the status, so it's possible check
+validity in particular sets, for example:
+
+    # returns true if status is valid and from initial or active set
+    $schema->is_valid('some_status', 'initial', 'active');
+
+See also </valid>.
+
+=cut
+
+sub is_valid {
+    my $self  = shift;
+    my $value = lc shift;
+    return scalar grep lc($_) eq $value, $self->valid( @_ );
+}
+
+=head3 initial
+
+Returns an array of all initial statuses for the current schema.
+
+=cut
+
+sub initial {
+    my $self = shift;
+    return $self->valid('initial');
+}
+
+=head3 is_initial
+
+Takes a status and returns true if value is a valid initial status.
+Otherwise, returns false.
+
+=cut
+
+sub is_initial {
+    my $self  = shift;
+    my $value = lc shift;
+    return scalar grep lc($_) eq $value, $self->valid('initial');
+}
+
+=head3 active
+
+Returns an array of all active statuses for this schema.
+
+=cut
+
+sub active {
+    my $self = shift;
+    return $self->valid('active');
+}
+
+=head3 is_active
+
+Takes a value and returns true if value is a valid active status.
+Otherwise, returns false.
+
+=cut
+
+sub is_active {
+    my $self  = shift;
+    my $value = lc shift;
+    return scalar grep lc($_) eq $value, $self->valid('active');
+}
+
+=head3 inactive
+
+Returns an array of all inactive statuses for this schema.
+
+=cut
+
+sub inactive {
+    my $self = shift;
+    return $self->valid('inactive');
+}
+
+=head3 is_inactive
+
+Takes a value and returns true if value is a valid inactive status.
+Otherwise, returns false.
+
+=cut
+
+sub is_inactive {
+    my $self  = shift;
+    my $value = lc shift;
+    return scalar grep lc($_) eq $value, $self->valid('inactive');
+}
+
+=head2 Transitions, rights, labels and actions.
+
+=head3 transitions
+
+Takes status and returns list of statuses it can be changed to.
+
+If status is ommitted then returns a hash with all possible transitions
+in the following format:
+
+    status_x => [ next_status, next_status, ... ],
+    status_y => [ next_status, next_status, ... ],
+
+=cut
+
+sub transitions {
+    my $self = shift;
+    my $status = shift;
+    if ( $status ) {
+        return @{ $self->{'data'}{'transitions'}{ $status } || [] };
+    } else {
+        return %{ $self->{'data'}{'transitions'} || {} };
+    }
+}
+
+=head1 is_transition
+
+Takes two statuses (from -> to) and returns true if it's valid
+transition and false otherwise.
+
+=cut
+
+sub is_transition {
+    my $self = shift;
+    my $from = shift or return 0;
+    my $to   = shift or return 0;
+    return scalar grep lc($_) eq lc($to), $self->transitions($from);
+}
+
+=head3 check_right
+
+Takes two statuses (from -> to) and returns the right that should
+be checked on the ticket.
+
+=cut
+
+sub check_right {
+    my $self = shift;
+    my $from = shift;
+    my $to = shift;
+    if ( my $rights = $self->{'data'}{'rights'} ) {
+        my $check =
+            $rights->{ $from .' -> '. $to }
+            || $rights->{ '* -> '. $to }
+            || $rights->{ $from .' -> *' }
+            || $rights->{ '* -> *' };
+        return $check if $check;
+    }
+    return $to eq 'deleted' ? 'DeleteTicket' : 'ModifyTicket';
+}
+
+sub register_rights {
+    my $self = shift;
+
+    $self->fill_cache unless keys %STATUS_SCHEMAS_CACHE;
+
+    my %tmp;
+    foreach my $schema ( values %STATUS_SCHEMAS_CACHE ) {
+        next unless exists $schema->{'rights'};
+        while ( my ($transition, $right) = each %{ $schema->{'rights'} } ) {
+            push @{ $tmp{ $right } ||=[] }, $transition;
+        }
+    }
+
+    require RT::ACE;
+    require RT::Queue;
+    my $RIGHTS = $RT::Queue::RIGHTS;
+    while ( my ($right, $transitions) = each %tmp ) {
+        next if exists $RIGHTS->{ $right };
+
+        my (@from, @to);
+        foreach ( @$transitions ) {
+            ($from[@from], $to[@to]) = split / -> /, $_;
+        }
+        my $description = 'Change status'
+            . ( (grep $_ eq '*', @from)? '' : ' from '. join ', ', @from )
+            . ( (grep $_ eq '*', @to  )? '' : ' to '. join ', ', @from );
+
+        $RIGHTS->{ $right } = $description;
+        $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
+    }
+}
+
+=head3 transition_label
+
+Takes two statuses (from -> to) and returns label for the transition,
+if custom label is not defined then default equal to the second status.
+
+=cut
+
+sub transition_label {
+    my $self = shift;
+    my $from = shift;
+    my $to = shift;
+    return $self->{'data'}{'actions'}{ $from .' -> '. $to }[0] || $to;
+}
+
+=head3 transition_action
+
+Takes two statuses (from -> to) and returns action for the transition.
+
+At this moment it can be:
+
+=over 4
+
+=item '' (empty string) - no action (default)
+
+=item hide - hide this button from the Web UI
+
+=item comment - comment page is shown
+
+=item respond - reply page is shown
+
+=back
+
+=cut
+
+sub transition_action {
+    my $self = shift;
+    my $from = shift;
+    my $to = shift;
+    return $self->{'data'}{'actions'}{ $from .' -> '. $to }[1] || '';
+}
+
+=head2 Creation and manipulation
+
+=head3 create
+
+Creates a new status schema in the DB. Takes a param hash with
+'name', 'initial', 'active', 'inactive' and 'transitions' keys.
+
+All arguments except 'name' are optional and can be filled later
+with other methods.
+
+Returns (status, message) pair, status is false on error.
+
+=cut
+
+sub create {
+    my $self = shift;
+    my %args = (
+        name => undef,
+        initial => undef,
+        active => undef,
+        inactive => undef,
+        transitions => undef,
+        actions => undef,
+        @_
+    );
+    @{ $self }{qw(name data)} = (undef, undef);
+
+    my $name = delete $args{'name'};
+    return (0, loc('Invalid schema name'))
+        unless defined $name && length $name;
+    return (0, loc('Already exist'))
+        if $STATUS_SCHEMAS_CACHE{ $name };
+
+    foreach my $method (qw(_set_statuses _set_transitions _set_actions)) {
+        my ($status, $msg) = $self->$method( %args, name => $name );
+        return ($status, $msg) unless $status;
+    }
+
+    my ($status, $msg) = $self->_store_schemas( $name );
+    return ($status, $msg) unless $status;
+
+    return (1, loc('Created a new status schema'));
+}
+
+sub set_statuses {
+    my $self = shift;
+    my %args = (
+        initial  => [],
+        active   => [],
+        inactive => [],
+        @_
+    );
+
+    my $name = $self->name or return (0, loc("Status schema is not loaded"));
+
+    my ($status, $msg) = $self->_set_statuses( %args, name => $name );
+    return ($status, $msg) unless $status;
+
+    ($status, $msg) = $self->_store_schemas( $name );
+    return ($status, $msg) unless $status;
+
+    return (1, loc('Updated schema'));
+}
+
+sub set_transitions {
+    my $self = shift;
+    my %args = @_;
+
+    my $name = $self->name or return (0, loc("Status schema is not loaded"));
+
+    my ($status, $msg) = $self->_set_transitions(
+        transitions => \%args, name => $name
+    );
+    return ($status, $msg) unless $status;
+
+    ($status, $msg) = $self->_store_schemas( $name );
+    return ($status, $msg) unless $status;
+
+    return (1, loc('Updated schema with transitions data'));
+}
+
+sub set_actions {
+    my $self = shift;
+    my %args = @_;
+
+    my $name = $self->name or return (0, loc("Status schema is not loaded"));
+
+    my ($status, $msg) = $self->_set_actions(
+        actions => \%args, name => $name
+    );
+    return ($status, $msg) unless $status;
+
+    ($status, $msg) = $self->_store_schemas( $name );
+    return ($status, $msg) unless $status;
+
+    return (1, loc('Updated schema with actions data'));
+}
+
+sub fill_cache {
+    my $self = shift;
+
+    my $map = RT->Config->Get('StatusSchemaMeta') or return;
+#    my $map = $RT::System->first_attribute('StatusSchemas')
+#        or return;
+#    $map = $map->content or return;
+
+    %STATUS_SCHEMAS_CACHE = %STATUS_SCHEMAS = %$map;
+    my %all = (
+        '' => [],
+        initial => [],
+        active => [],
+        inactive => [],
+    );
+    foreach my $schema ( values %STATUS_SCHEMAS_CACHE ) {
+        my @res;
+        foreach my $type ( qw(initial active inactive) ) {
+            push @{ $all{ $type } }, @{ $schema->{ $type } || [] };
+            push @res,               @{ $schema->{ $type } || [] };
+        }
+
+        my %seen;
+        @res = grep !$seen{ lc $_ }++, @res;
+        $schema->{''} = \@res;
+    }
+    foreach my $type ( qw(initial active inactive), '' ) {
+        my %seen;
+        @{ $all{ $type } } = grep !$seen{ lc $_ }++, @{ $all{ $type } };
+        push @{ $all{''} }, @{ $all{ $type } } if $type;
+    }
+    $STATUS_SCHEMAS_CACHE{''} = \%all;
+    return;
+}
+
+sub for_localization {
+    my $self = shift;
+    $self->fill_cache unless keys %STATUS_SCHEMAS_CACHE;
+
+    my @res = ();
+
+    push @res, @{ $STATUS_SCHEMAS_CACHE{''}{''} || [] };
+    foreach my $schema ( values %STATUS_SCHEMAS ) {
+        push @res,
+            grep defined && length,
+            map $_->[0],
+            grep ref($_),
+            values %{ $schema->{'actions'} || {} };
+    }
+
+    my %seen;
+    return grep !$seen{lc $_}++, @res;
+}
+
+sub _store_schemas {
+    my $self = shift;
+    my $name = shift;
+    my ($status, $msg) = $RT::System->set_attribute(
+        name => 'StatusSchemas',
+        description => 'all system status schemas',
+        content => \%STATUS_SCHEMAS,
+    );
+    $self->fill_cache;
+    $self->load( $name );
+    return ($status, loc("Couldn't store schema")) unless $status;
+    return 1;
+}
+
+sub _set_statuses {
+    my $self = shift;
+    my %args = @_;
+
+    my @all;
+    my %tmp = (
+        initial  => [],
+        active   => [],
+        inactive => [],
+    );
+    foreach my $type ( qw(initial active inactive) ) {
+        foreach my $status ( grep defined && length, @{ $args{ $type } || [] } ) {
+            return (0, loc('Status should contain ASCII characters only. Translate via po files.'))
+                unless $status =~ /^[a-zA-Z0-9.,! ]+$/;
+            return (0, loc('Statuses must be unique in one schema'))
+                if grep lc($_) eq lc($status), @all;
+            push @all, $status;
+            push @{ $tmp{ $type } }, $status;
+        }
+    }
+
+    $STATUS_SCHEMAS{ $args{'name'} }{ $_ } = $tmp{ $_ }
+        foreach qw(initial active inactive);
+
+    return 1;
+}
+
+sub _set_transitions {
+    my $self = shift;
+    my %args = @_;
+
+    # XXX, TODO: more tests on data
+    $STATUS_SCHEMAS{ $args{'name'} }{'transitions'} = $args{'transitions'};
+    return 1;
+}
+
+sub _set_actions {
+    my $self = shift;
+    my %args = @_;
+
+    # XXX, TODO: more tests on data
+    $STATUS_SCHEMAS{ $args{'name'} }{'actions'} = $args{'actions'};
+    return 1;
+}
+
+sub from_set {
+    my $self = shift;
+    my $status = shift;
+    foreach my $set ( qw(initial active inactive) ) {
+        return $set if $self->is_valid( $status, $set );
+    }
+    return '';
+}
+
+sub map {
+    my $from = shift;
+    my $to = shift;
+    $to = RT::StatusSchema->load( $to ) unless ref $to;
+    return $STATUS_SCHEMAS{'__maps__'}{ $from->name .' -> '. $to->name } || {};
+}
+
+sub set_map {
+    my $self = shift;
+    my $to = shift;
+    $to = RT::StatusSchema->load( $to ) unless ref $to;
+    my %map = @_;
+    $map{ lc $_ } = delete $map{ $_ } foreach keys %map;
+
+    return (0, loc("Status schema is not loaded"))
+        unless $self->name;
+
+    return (0, loc("Status schema is not loaded"))
+        unless $to->name;
+
+
+    $STATUS_SCHEMAS{'__maps__'}{ $self->name .' -> '. $to->name } = \%map;
+
+    my ($status, $msg) = $self->_store_schemas( $self->name );
+    return ($status, $msg) unless $status;
+
+    return (1, loc('Updated schema with actions data'));
+}
+
+sub has_map {
+    my $self = shift;
+    my $map = $self->map( @_ );
+    return 0 unless $map && keys %$map;
+    return 0 unless grep defined && length, values %$map;
+    return 1;
+}
+
+sub no_maps {
+    my $self = shift;
+    my @list = $self->list;
+    my @res;
+    foreach my $from ( @list ) {
+        foreach my $to ( @list ) {
+            next if $from eq $to;
+            push @res, $from, $to
+                unless RT::StatusSchema->load( $from )->has_map( $to );
+        }
+    }
+    return @res;
+}
+
+sub queues {
+    my $self = shift;
+    require RT::Queues;
+    my $queues = RT::Queues->new( $RT::SystemUser );
+    $queues->limit( column => 'status_schema', value => $self->name );
+    return $queues;
+}
+
+1;
diff --git a/lib/RT/StatusSchemas.pm b/lib/RT/StatusSchemas.pm
new file mode 100644
index 0000000..ba718bc
--- /dev/null
+++ b/lib/RT/StatusSchemas.pm
@@ -0,0 +1,301 @@
+use strict;
+use warnings;
+no warnings 'redefine';
+
+package RT::StatusSchemas;
+
+our $VERSION = '0.02';
+
+=head1 NAME
+
+RT::Estension::StatusSchemas - define different set of statuses for queues in RT
+
+=head1 SYNOPSIS
+
+    # schema close to RT's default, but with some nice
+    # additions
+    Set( %StatusSchemaMeta,
+        default => {
+            initial  => ['new'],
+            active   => ['open', 'stalled'],
+            inactive => ['resolved', 'rejected', 'deleted'],
+
+            transitions => {
+                # from   => [ to list ],
+                new      => [qw(open resolved rejected deleted)],
+                open     => [qw(stalled resolved rejected deleted)],
+                stalled  => [qw(open)],
+                resolved => [qw(open)],
+                rejected => [qw(open)],
+                deleted  => [qw(open)],
+            },
+            rights  => {
+                '* -> deleted'  => 'DeleteTicket',
+                '* -> rejected' => 'RejectTicket',
+                '* -> *'        => 'ModifyTicketStatus',
+            },
+            actions => {
+                # 'from -> to'    => [action text, Respond/Comment/hide/''],
+                'new -> open'     => ['Open It', 'Respond'],
+                'new -> resolved' => ['Resolve', 'Comment'],
+                'new -> rejected' => ['Reject',  'Respond'],
+                'new -> deleted'  => ['Delete',  ''],
+
+                'open -> stalled'  => ['Stall',   'Comment'],
+                'open -> resolved' => ['Resolve', 'Comment'],
+                'open -> rejected' => ['Reject',  'Respond'],
+                'open -> deleted'  => ['Delete',  'hide'],
+
+                'stalled -> open'  => ['Open It',  ''],
+                'resolved -> open' => ['Re-open',  'Comment'],
+                'rejected -> open' => ['Re-open',  'Comment'],
+                'deleted -> open'  => ['Undelete', ''],
+            },
+        },
+    );
+
+=head1 DESCRIPTION
+
+By default RT has one set of statuses for all queues with this extension you can
+define multiple schemas with different statuses and other interesting things.
+
+=head1 CONFIGURATION
+
+=head2 Basics
+
+This extension is configured in the RT config file using several options:
+
+=over 4
+
+=item %StatusSchemas - use to define status schemas for queues, if there is no
+record for some queue then 'default' is used. For example:
+
+    Set( %StatusSchemas,
+        General => 'some_schema',
+        'Another Queue' => 'another schema',
+    );
+
+=item %StatusSchemaMeta - use to describe status schemas. Below you can read more
+about format of this option, but here is basic format:
+
+    Set( %StatusSchemaMeta,
+        default => {
+            ... description of default schema ...
+        },
+        'another schema' => {
+            ... description of another schema ...
+        },
+    );
+
+=back
+
+=head2 Statuses
+
+Each schema is a list of statues splitted into three logic sets:
+initial, active and inactive (read below). All statuses in a schema
+must be unique. Each set may have any number of statuses.
+
+For example:
+
+    default => {
+        initial  => ['new'],
+        active   => ['open', 'stalled'],
+        inactive => ['resolved', 'rejected', 'deleted'],
+        ...
+    },
+
+Note that size of status field in the DB is limitted to 10 ASCII
+characters. You can increase size in the DB, but shorter statuses
+are usually better for UI and DB performance. Also ASCII may looks
+contradictionary with multi-language interface, but you can translate
+statuses and other things using po files.
+
+=head3 Status sets
+
+Unlike RT 3.8 this extension adds 'intial' set in addition to 'active'
+and 'inactive' sets.
+
+=over 4
+
+=item intial
+
+This set is new for RT and covers one thing that has not been
+covered in RT 3.8 and earlier. First time you change a status
+from 'new' to another in 3.8 and earlier Started date is set
+to now. Status 'new' is hardcoded in RT 3.8 for this purpose.
+
+With this extension you can define multiple intial statuses and
+Started date is only set when you change from one of initial
+statuses to status from active or inactive set.
+
+This allow you to get better statistics over dates. For example
+you may have initial statuses 'new' and 'negotiation' and active
+status 'processing'. Created date is set whenever ticket was created
+and started date is set once status changed to 'processing'.
+
+As well, you may have this set empty when you're sure all tickets
+with this status schema are active when created.
+
+Started date is set to now on create if ticket is created with
+not initial status and started date is not defined.
+
+=item active
+
+Active set is something well know as active statuses from RT 3.8
+except that it's splitted into two sets: initial and active.
+
+=item inactive
+
+Inactive statuses haven't been changed much from implementation
+in RT 3.8.
+
+Resolved date is set to now when status is changed from any
+intial or active status to inactive. As well, on create if status
+of new ticket is inactive and resolved date is not defined.
+
+'deleted' is still a special status and protected by 'DeleteTicket'
+right, you have to add it to set manually or avoid if you don't
+want tickets to be deleted.
+
+=back
+
+Statuses in each set are ordered and listed in the UI in the defined
+order. It worth to mention that order of statuses may influence
+behavior a little when it's ambiguose which status to choose.
+
+Changes between statuses are constrolled by possible transitions
+described below.
+
+=head2 Allowing transitions, protecting with rights, labeling them and defining actions
+
+Transition - is a change of status from A to B. You should define
+all possible transitions in each schema using the following format:
+
+    default => {
+        ...
+        transitions => {
+            new      => [qw(open resolved rejected deleted)],
+            open     => [qw(stalled resolved rejected deleted)],
+            stalled  => [qw(open)],
+            resolved => [qw(open)],
+            rejected => [qw(open)],
+            deleted  => [qw(open)],
+        },
+        ...
+    },
+
+=head3 Protecting with rights
+
+A transation or group of transitions can be protected by a right,
+for example:
+
+    default => {
+        ...
+        rights => {
+            '* -> deleted'  => 'DeleteTicket',
+            '* -> rejected' => 'RejectTicket',
+            '* -> *'        => 'ModifyTicketStatus',
+        },
+        ...
+    },
+
+On the left hand side you can have the following variants:
+
+    '<from> -> <to>'
+    '* -> <to>'
+    '<from> -> *'
+    '* -> *'
+
+Variants are listed in order by priority, so if user want
+to change status from X to Y then schema checked for presence
+of exact match, then for presence of 'any to Y', 'X to any' and
+finally 'any to any'.
+
+If you don't define any rights or there is no match for some
+transition then DeleteTicket and ModifyTicket rights are
+checked, like RT does by default.
+
+=head3 Labeling and defining actions
+
+Each transition can be named, by default it's named as B what often
+is not that good. At this point this label is required for the UI
+only where all transitions are listed on ticket's page as possible
+actions on the ticket. Each such action may be acompanied with
+comment, correspond or hidden by default. For example you may want
+your users to write a reply when they change status from new to open.
+Or it's possible to hide open -> delete transition by default from
+ticket's main view, but still make it legal for 'edit basics' page
+or API.
+
+Additional comment or correspond is not mandatory and may be skipped
+by users. By default no action there is no need in action and one-click
+link is showed.
+
+Use the following format to define labels and actions of transitions:
+
+    default => {
+        ...
+        actions => {
+            'new -> open'     => ['Open It', 'Respond'],
+            'new -> resolved' => ['Resolve', 'Comment'],
+            'new -> rejected' => ['Reject',  'Respond'],
+            'new -> deleted'  => ['Delete',  ''],
+
+            'open -> stalled'  => ['Stall',   'Comment'],
+            'open -> resolved' => ['Resolve', 'Comment'],
+            'open -> rejected' => ['Reject',  'Respond'],
+            'open -> deleted'  => ['Delete',  'hide'],
+
+            'stalled -> open'  => ['Open It',  ''],
+            'resolved -> open' => ['Re-open',  'Comment'],
+            'rejected -> open' => ['Re-open',  'Comment'],
+            'deleted -> open'  => ['Undelete', ''],
+        },
+        ...
+    },
+
+=head2 Moving tickets between queues with different schemas
+
+Unless there is some mapping between statuses in schema A and B,
+you can not move tickets between queues with these schemas.
+
+    __maps__ => {
+        'from schema -> to schema' => {
+            'status in left schema' => 'status in right schema',
+            ...
+        },
+        ...
+    },
+
+=head2 Changes in the UI and Scrips
+
+=head3 Quicksearch portlet
+
+This portlet has been rewriten and shows initial and active
+statuses in all schemas. If status is not valid for a queue then
+users will see '-' instead of number.
+
+For setups with many different schemas or schemas that has no
+equal statuses at all it may be better to group queues by status
+schemas. You can achieve this by using QuicksearchBySchema
+portlet. Change HomepageComponents config option first.
+
+=head3 AutoOpen scrip action
+
+Auto open scrip action has been rewritten completly. You can
+read description of its behavior in F<lib/RT/Action/AutoOpen_Vendor.pm>
+using perldoc program.
+
+=head3 StatusChange scrip condition
+
+StatusChange scrip condition has been extended with new format of
+the Argument to better serve systems with multiple status schemas.
+Read description in F<lib/RT/Condition/StatusChange_Vendor.pm>.
+
+=cut
+
+
+require RT::StatusSchema;
+RT::StatusSchema->register_rights;
+
+1;
diff --git a/lib/RT/Ticket_Overlay.pm b/lib/RT/Ticket_Overlay.pm
index a9efc46..63dedf1 100755
--- a/lib/RT/Ticket_Overlay.pm
+++ b/lib/RT/Ticket_Overlay.pm
@@ -306,10 +306,14 @@ sub Create {
             $self->loc( "No permission to create tickets in the queue '[_1]'", $QueueObj->Name));
     }
 
-    unless ( $QueueObj->IsValidStatus( $args{'Status'} ) ) {
+    unless ( $QueueObj->IsValidStatus( $args{'Status'} )
+            && $QueueObj->status_schema->is_initial( $args{'Status'} )) {
         return ( 0, 0, $self->loc('Invalid value for status') );
     }
 
+
+
+
     #Since we have a queue, we can set queue defaults
 
     #Initial Priority
@@ -1723,13 +1727,24 @@ sub SetQueue {
         return ( 0, $self->loc("You may not create requests in that queue.") );
     }
 
+    my $new_status;
+    my $old_schema = $self->QueueObj->status_schema;
+    my $new_schema = $NewQueueObj->status_schema;
+    if ( $old_schema->name ne $new_schema->name ) {
+        unless ( $old_schema->has_map( $new_schema ) ) {
+            return ( 0, $self->loc("There is no mapping for statuses between these queues. Contact your system administrator.") );
+        }
+        $new_status = $old_schema->map( $new_schema )->{ $self->Status };
+        return ( 0, $self->loc("Mapping between queues' status schemas is incomplete. Contact your system administrator.") )
+            unless $new_status;
+    }
+
     unless (
         $self->OwnerObj->HasRight(
             Right    => 'OwnTicket',
             Object => $NewQueueObj
         )
-      )
-    {
+    ) {
         my $clone = RT::Ticket->new( $RT::SystemUser );
         $clone->Load( $self->Id );
         unless ( $clone->Id ) {
@@ -1739,6 +1754,49 @@ sub SetQueue {
         $RT::Logger->error("Couldn't set owner on queue change: $msg") unless $status;
     }
 
+    if ( $new_status ) {
+        my $clone = RT::Ticket->new( $RT::SystemUser );
+        $clone->Load( $self->Id );
+        unless ( $clone->Id ) {
+            return ( 0, $self->loc("Couldn't load copy of ticket #[_1].", $self->Id) );
+        }
+
+        my $now = RT::Date->new( $self->CurrentUser );
+        $now->SetToNow;
+
+        my $old_status = $clone->Status;
+
+        #If we're changing the status from initial in old to not intial in new,
+        # record that we've started
+        if ( $old_schema->is_initial($old_status) && !$new_schema->is_initial($new_status) ) {
+            #Set the Started time to "now"
+            $clone->_Set(
+                Field             => 'Started',
+                Value             => $now->ISO,
+                RecordTransaction => 0
+            );
+        }
+
+        #When we close a ticket, set the 'Resolved' attribute to now.
+        # It's misnamed, but that's just historical.
+        if ( $new_schema->is_inactive($new_status) ) {
+            $clone->_Set(
+                Field             => 'Resolved',
+                Value             => $now->ISO,
+                RecordTransaction => 0,
+            );
+        }
+
+        #Actually update the status
+        my ($val, $msg)= $clone->_Set(
+            Field             => 'Status',
+            Value             => $new_status,
+            RecordTransaction => 0,
+        );
+        $RT::Logger->error( 'Status change failed on queue change: '. $msg )
+            unless $val;
+    }
+
     my ($status, $msg) = $self->_Set( Field => 'Queue', Value => $NewQueueObj->Id() );
 
     if ( $status ) {
@@ -1749,7 +1807,7 @@ sub SetQueue {
             $RT::Logger->error('Queue change failed for reminder #' . $reminder->Id . ': ' . $msg) unless $status;
         }
     }
-    
+
     return ($status, $msg);
 }
 
@@ -2960,12 +3018,14 @@ sub ValidateStatus {
     my $status = shift;
 
     #Make sure the status passed in is valid
-    unless ( $self->QueueObj->IsValidStatus($status) ) {
-        return (undef);
-    }
+    return 1 if $self->QueueObj->IsValidStatus($status);
 
-    return (1);
+    my $i = 0;
+    while ( my $caller = (caller($i++))[3] ) {
+        return 1 if $caller eq 'RT::Ticket::SetQueue';
+    }
 
+    return 0;
 }
 
 # }}}
@@ -2983,28 +3043,43 @@ Alternatively, you can pass in a list of named parameters (Status => STATUS, For
 =cut
 
 sub SetStatus {
-    my $self   = shift;
+    my $self = shift;
     my %args;
-
     if (@_ == 1) {
-    $args{Status} = shift;
+        $args{Status} = shift;
     }
     else {
-    %args = (@_);
+        %args = (@_);
     }
 
-    #Check ACL
-    if ( $args{Status} eq 'deleted') {
-            unless ($self->CurrentUserHasRight('DeleteTicket')) {
-            return ( 0, $self->loc('Permission Denied') );
-       }
-    } else {
-            unless ($self->CurrentUserHasRight('ModifyTicket')) {
-            return ( 0, $self->loc('Permission Denied') );
-       }
+    my $schema = $self->QueueObj->status_schema;
+
+    my $new = $args{'Status'};
+    unless ( $schema->is_valid( $new ) ) {
+        return (0,
+            $self->loc("Status '[_1]' is not valid for schema '[_2]'.",
+                $self->loc($new), $self->loc($schema->name)
+            )
+        );
     }
 
-    if (!$args{Force} && ($args{'Status'} eq 'resolved') && $self->HasUnresolvedDependencies) {
+    my $old = $self->__Value('Status');
+    unless ( $schema->is_transition( $old => $new ) ) {
+        return (0,
+            $self->loc("You can't change status from '[_1]' to '[_2]'.",
+                $self->loc($old), $self->loc($new)
+            )
+        );
+    }
+
+    my $check_right = $schema->check_right( $old => $new );
+    unless ( $self->CurrentUserHasRight( $check_right ) ) {
+        return ( 0, $self->loc('Permission Denied') );
+    }
+
+    if ( !$args{Force} && $schema->is_inactive( $new )
+        && $self->HasUnresolvedDependencies
+    ) {
         return (0, $self->loc('That ticket has unresolved dependencies'));
     }
 
@@ -3012,30 +3087,34 @@ sub SetStatus {
     $now->SetToNow();
 
     #If we're changing the status from new, record that we've started
-    if ( $self->Status eq 'new' && $args{Status} ne 'new' ) {
-
+    if ( $schema->is_initial($old) && !$schema->is_initial($new) ) {
         #Set the Started time to "now"
-        $self->_Set( Field             => 'Started',
-                     Value             => $now->ISO,
-                     RecordTransaction => 0 );
+        $self->_Set(
+            Field             => 'Started',
+            Value             => $now->ISO,
+            RecordTransaction => 0
+        );
     }
 
     #When we close a ticket, set the 'Resolved' attribute to now.
     # It's misnamed, but that's just historical.
-    if ( $self->QueueObj->IsInactiveStatus($args{Status}) ) {
-        $self->_Set( Field             => 'Resolved',
-                     Value             => $now->ISO,
-                     RecordTransaction => 0 );
+    if ( $schema->is_inactive($new) ) {
+        $self->_Set(
+            Field             => 'Resolved',
+            Value             => $now->ISO,
+            RecordTransaction => 0,
+        );
     }
 
     #Actually update the status
-   my ($val, $msg)= $self->_Set( Field           => 'Status',
-                          Value           => $args{Status},
-                          TimeTaken       => 0,
-                          CheckACL      => 0,
-                          TransactionType => 'Status'  );
-
-    return($val,$msg);
+    my ($val, $msg)= $self->_Set(
+        Field           => 'Status',
+        Value           => $args{Status},
+        TimeTaken       => 0,
+        CheckACL        => 0,
+        TransactionType => 'Status',
+    );
+    return ($val, $msg);
 }
 
 # }}}
diff --git a/share/html/Elements/QueueSummaryByStatus b/share/html/Elements/QueueSummaryByStatus
new file mode 100644
index 0000000..fc2db42
--- /dev/null
+++ b/share/html/Elements/QueueSummaryByStatus
@@ -0,0 +1,128 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC
+%#                                          <jesse 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 }}}
+<table border="0" cellspacing="0" cellpadding="1" width="100%" class="queue-summary">
+
+<tr>
+    <th class="collection-as-table"><&|/l&>Queue</&></th>
+% for my $status ( @statuses ) {
+    <th class="collection-as-table"><% loc($status) %></th>
+% }
+</tr>
+
+<%PERL>
+my $i = 0;
+for my $queue (@queues) {
+    $i++;
+    my $name = $queue->{Name};
+    $name =~ s/'/\\'/g;
+    my $queue_cond = "Queue = '$name' AND ";
+    my $schema = $schema{ lc $queue->{'Schema'} };
+    my $all_q = $queue_cond . '(' . join( " OR ", map "Status = '$_'", grep $schema->is_valid($_), @statuses ) . ')';
+</%PERL>
+<tr class="<% $i%2 ? 'oddline' : 'evenline'%>" >
+
+<td><a href="<% RT->Config->Get('WebPath') %>/Search/Results.html?Query=<% $all_q |u,n %>" title="<% $queue->{Description} %>"><% $queue->{Name} %></a></td>
+
+%   for my $status (@statuses) {
+%   if ( $schema->is_valid( $status ) ) {
+%       $Tickets->FromSQL( "Queue = $queue->{id} AND Status = '$status'" );
+<td align="right"><a href="<% RT->Config->Get('WebPath') %>/Search/Results.html?Query=<% $queue_cond ."Status = '$status'" |u,n %>"><% $Tickets->Count %></a></td>
+%   } else {
+<td align="right">-</td>
+%   }
+%   }
+</tr>
+% }
+</table>
+<%INIT>
+my @queues;
+if ($cache && exists $session{$cache}) {
+    @queues = @{$session{$cache}};
+
+}
+else {
+    my $Queues = RT::Queues->new($session{'CurrentUser'});
+    $Queues->UnLimit();
+    @queues = grep $queue_filter->($_), @{$Queues->ItemsArrayRef};
+
+    $m->callback( CallbackName => 'Filter', Queues => \@queues );
+
+    @queues = map { {
+        id          => $_->Id,
+        Name        => $_->Name,
+        Description => $_->Description,
+        Schema      => $_->status_schema->name,
+    } } grep $_, @queues;
+
+    $session{$cache} = \@queues if $cache;
+}
+
+use RT::StatusSchema;
+
+my %schema;
+$schema{ lc $_->name } = $_ foreach
+    grep $_, map RT::StatusSchema->load($_),
+    map $_->{'Schema'}, @queues;
+
+
+unless ( @statuses ) {
+    my %seen;
+    foreach my $set('initial', 'active') {
+        foreach my $schema ( map $schema{$_}, sort keys %schema ) {
+            push @statuses, grep !$seen{lc $_}++, $schema->$set();
+        }
+    }
+}
+
+my $Tickets = RT::Tickets->new($session{'CurrentUser'});
+</%INIT>
+<%ARGS>
+$cache => undef
+$queue_filter => undef
+ at statuses => ()
+</%ARGS>
diff --git a/share/html/Elements/QueueSummaryByStatusSchema b/share/html/Elements/QueueSummaryByStatusSchema
new file mode 100644
index 0000000..e269b12
--- /dev/null
+++ b/share/html/Elements/QueueSummaryByStatusSchema
@@ -0,0 +1,129 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC
+%#                                          <jesse 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 }}}
+% foreach my $schema ( map $schema{$_}, sort keys %schema ) {
+% my @cur_statuses = grep $schema->is_valid($_), @statuses;
+% next unless @cur_statuses;
+<table border="0" cellspacing="0" cellpadding="1" width="100%" class="queue-summary">
+
+<tr>
+    <th class="collection-as-table"><&|/l&>Queue</&></th>
+% for my $status ( @cur_statuses ) {
+    <th class="collection-as-table"><% loc($status) %></th>
+% }
+</tr>
+
+<%PERL>
+my $i = 0;
+for my $queue (@queues) {
+    next if lc $queue->{Schema} ne lc $schema->name;
+
+    $i++;
+    my $name = $queue->{Name};
+    $name =~ s/'/\\'/g;
+    my $queue_cond = "Queue = '$name' AND ";
+    my $all_q = $queue_cond . '(' . join( " OR ", map "Status = '$_'", grep $schema->is_valid($_), @statuses ) . ')';
+</%PERL>
+<tr class="<% $i%2 ? 'oddline' : 'evenline'%>" >
+
+<td><a href="<% RT->Config->Get('WebPath') %>/Search/Results.html?Query=<% $all_q |u,n %>" title="<% $queue->{Description} %>"><% $queue->{Name} %></a></td>
+
+%   for my $status (@cur_statuses) {
+%       $Tickets->FromSQL( "Queue = $queue->{id} AND Status = '$status'" );
+<td align="right"><a href="<% RT->Config->Get('WebPath') %>/Search/Results.html?Query=<% $queue_cond ."Status = '$status'" |u,n %>"><% $Tickets->Count %></a></td>
+%   }
+</tr>
+% }
+</table>
+% }
+<%INIT>
+my @queues;
+if ($cache && exists $session{$cache}) {
+    @queues = @{$session{$cache}};
+
+}
+else {
+    my $Queues = RT::Queues->new($session{'CurrentUser'});
+    $Queues->UnLimit();
+    @queues = grep $queue_filter->($_), @{$Queues->ItemsArrayRef};
+
+    $m->callback( CallbackName => 'Filter', Queues => \@queues );
+
+    @queues = map { {
+        id          => $_->Id,
+        Name        => $_->Name,
+        Description => $_->Description,
+        Schema      => $_->status_schema->name,
+    } } grep $_, @queues;
+
+    $session{$cache} = \@queues if $cache;
+}
+
+use RT::StatusSchema;
+
+my %schema;
+$schema{ lc $_->name } = $_ foreach
+    grep $_, map RT::StatusSchema->load($_),
+    map $_->{'Schema'}, @queues;
+
+
+unless ( @statuses ) {
+    my %seen;
+    foreach my $set('initial', 'active') {
+        foreach my $schema ( map $schema{$_}, sort keys %schema ) {
+            push @statuses, grep !$seen{lc $_}++, $schema->$set();
+        }
+    }
+}
+
+my $Tickets = RT::Tickets->new($session{'CurrentUser'});
+</%INIT>
+<%ARGS>
+$cache => undef
+$queue_filter => undef
+ at statuses => ()
+</%ARGS>
diff --git a/share/html/Elements/Quicksearch b/share/html/Elements/Quicksearch
old mode 100755
new mode 100644
index 6b11f73..f02079d
--- a/share/html/Elements/Quicksearch
+++ b/share/html/Elements/Quicksearch
@@ -1,65 +1,20 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%# 
-%# COPYRIGHT:
-%# 
-%# This software is Copyright (c) 1996-2010 Best Practical Solutions, LLC
-%#                                          <jesse 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 }}}
 <div class="ticket-overview">
-<&|/Widgets/TitleBox, title => loc("Quick search"), bodyclass => "",
-   titleright => loc("Edit"), titleright_href => RT->Config->Get('WebPath').'/Prefs/Quicksearch.html' &>
-<& /Elements/QueueSummary,
+<&|/Widgets/TitleBox,
+    title => loc("Quick search"),
+    bodyclass => "",
+    titleright => loc("Edit"),
+    titleright_href => RT->Config->Get('WebPath').'/Prefs/Quicksearch.html',
+&>
+<& $comp,
    cache => 'quick_search_queues',
    queue_filter => sub { $_->CurrentUserHasRight('ShowTicket') && !exists $unwanted->{$_->Name} },
-   conditions => \@conditions,
 &>
 </&>
 </div>
 <%INIT>
 my $unwanted = $session{'CurrentUser'}->UserObj->Preferences('QuickSearch', {});
-
-my @conditions = ();
-foreach ( RT::Queue->ActiveStatusArray ) {
-    push @conditions, { cond => "Status = '$_'", name => loc($_) };
-}
+my $comp = $SplitBySchema? '/Elements/QueueSummaryByStatusSchema' : '/Elements/QueueSummaryByStatus';
 </%INIT>
+<%ARGS>
+$SplitBySchema => 0
+</%ARGS>
diff --git a/share/html/Elements/QuicksearchBySchema b/share/html/Elements/QuicksearchBySchema
new file mode 100644
index 0000000..16147a6
--- /dev/null
+++ b/share/html/Elements/QuicksearchBySchema
@@ -0,0 +1 @@
+<& /Elements/Quicksearch, SplitBySchema => 1 &>
diff --git a/share/html/Elements/SelectStatus b/share/html/Elements/SelectStatus
old mode 100755
new mode 100644
index 88409ee..64bf5d6
--- a/share/html/Elements/SelectStatus
+++ b/share/html/Elements/SelectStatus
@@ -1,73 +1,54 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%# 
-%# COPYRIGHT:
-%# 
-%# This software is Copyright (c) 1996-2010 Best Practical Solutions, LLC
-%#                                          <jesse 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 }}}
-<select name="<%$Name%>" <% $Multiple ? qq{multiple="multiple" size="$Size"} : '' |n%>>
+<select name="<%$Name%>">
 %if ($DefaultValue) {
-<option value=""<% not keys(%default) && qq[ selected="selected"] |n %>><%$DefaultLabel%></option>
+<option value=""<% !$Default && qq[ selected="selected"] |n %>><%$DefaultLabel%></option>
 %}
 %foreach my $status (@status) {
 %next if ($SkipDeleted && $status eq 'deleted');
-<option value="<%$status%>"<% $default{$status} && qq[ selected="selected"] |n %>><%loc($status)%></option>
+<option value="<%$status%>"<% (defined $Default && $status eq $Default) && qq[ selected="selected"] |n %>><%loc($status)%></o
+ption>
 % }
 </select>
-<%ONCE>
-my $queue = new RT::Queue($session{'CurrentUser'});
-my @status = $queue->StatusArray();
-</%ONCE>
-<%init>
-my %default;
-$default{$_}++ for grep $_, ref $Default ? @{$Default} : ( $Default );
-</%init>
+<%INIT>
+my $caller = $m->callers(1)->path;
+if ( $caller eq '/Ticket/Update.html' ) {
+    $Ticket = LoadTicket( $m->caller_args(1)->{'id'} );
+}
+elsif ( $caller eq '/Ticket/Create.html' ) {
+    $Queue = RT::Queue->new( $session{'CurrentUser'} );
+    $Queue->Load( $m->caller_args(1)->{'Queue'} );
+}
+elsif ( $caller eq '/Ticket/Elements/EditBasics' ) {
+    $Ticket = $m->caller_args(1)->{'TicketObj'};
+}
+### XXX: no cover for Tools/MyDay.html
+
+
+my @status;
+if ( $Ticket ) {
+    my $current = $Ticket->Status;
+    my $schema = $Ticket->QueueObj->status_schema;
+
+    my %has = ();
+    foreach my $next ( $schema->transitions( $current ) ) {
+        my $check = $schema->check_right( $current => $next );
+        $has{ $check } = $Ticket->CurrentUserHasRight( $check )
+            unless exists $has{ $check };
+        push @status, $next if $has{ $check };
+    }
+}
+elsif ( $Queue ) {
+    @status = $Queue->status_schema->valid;
+}
+else {
+    @status = RT::Queue->status_schema->valid;
+}
+</%INIT>
 <%ARGS>
 $Name => undef
+$Ticket => undef
+$Queue => undef
 $Default => ''
 $SkipDeleted => 0
 $DefaultValue => 1
 $DefaultLabel => "-"
-$Multiple => 0
-$Size => 6
 </%ARGS>
diff --git a/share/html/Ticket/Elements/Tabs b/share/html/Ticket/Elements/Tabs
index b1428b0..3dc47af 100755
--- a/share/html/Ticket/Elements/Tabs
+++ b/share/html/Ticket/Elements/Tabs
@@ -61,6 +61,12 @@ if ($Ticket) {
 
     my $id = $Ticket->id();
 
+my $current = $Ticket->Status;
+my $schema = $Ticket->QueueObj->status_schema;
+my %has;
+my $i = 1;
+
+
     if ( defined $session{'tickets'} ) {
 
         # we have to update session data if we get new ItemMap
@@ -203,58 +209,69 @@ if ($Ticket) {
     }
 
     if ( $can{'ModifyTicket'} ) {
-        if ( $Ticket->Status ne 'resolved' ) {
-            $actions->{'G'} = {
-                path =>
-                    "Ticket/Update.html?Action="
-                    . RT->Config->Get('ResolveDefaultUpdateType', $session{'CurrentUser'})
-                    . "&DefaultStatus=resolved&id="
-                    . $id,
-                title => loc('Resolve')
+        foreach my $next ( $schema->transitions($current) ) {
+            my $action = $schema->transition_action( $current => $next );
+            next if $action eq 'hide';
+
+            my $check = $schema->check_right( $current => $next );
+            $has{$check} = $Ticket->CurrentUserHasRight($check)
+                unless exists $has{$check};
+            next unless $has{$check};
+
+            my $path = 'Ticket/';
+            if ($action) {
+                $path .= "Update.html?"
+                    . $m->comp(
+                    '/Elements/QueryString',
+                    Action        => $action,
+                    DefaultStatus => $next,
+                    id            => $id
+                    );
+            } else {
+                $path .= "Display.html?"
+                    . $m->comp(
+                    '/Elements/QueryString',
+                    Status => $next,
+                    id     => $id
+                    );
+            }
+            $actions->{ 'G' . $i++ } = {
+                path  => $path,
+                title => loc( $schema->transition_label( $current => $next ) ),
             };
         }
-        if ( $Ticket->Status ne 'open' ) {
-            $actions->{'A'} = {
-                path  => "Ticket/Display.html?Status=open&id=" . $id,
-                title => loc('Open it')
-            };
+        if ( $Ticket->CurrentUserHasRight('OwnTicket') ) {
+            if ( $Ticket->OwnerObj->Id == $RT::Nobody->id ) {
+                $actions->{'B'} = {
+                    path  => "Ticket/Display.html?Action=Take&id=" . $id,
+                    title => loc('Take'),
+                    }
+                    if $can{'ModifyTicket'}
+                        or $Ticket->CurrentUserHasRight('TakeTicket');
+            } elsif ( $Ticket->OwnerObj->id != $session{CurrentUser}->id ) {
+                $actions->{'C'} = {
+                    path  => "Ticket/Display.html?Action=Steal&id=" . $id,
+                    title => loc('Steal'),
+                    }
+                    if $can{'ModifyTicket'}
+                        or $Ticket->CurrentUserHasRight('StealTicket');
+            }
         }
-    }
 
-    if ( $Ticket->CurrentUserHasRight('OwnTicket') ) {
-        if ( $Ticket->OwnerObj->Id == $RT::Nobody->id ) {
-            $actions->{'B'} = {
-                path  => "Ticket/Display.html?Action=Take&id=" . $id,
-                title => loc('Take'),
-                }
-                if $can{'ModifyTicket'}
-                    or $Ticket->CurrentUserHasRight('TakeTicket');
-        } elsif ( $Ticket->OwnerObj->id != $session{CurrentUser}->id ) {
-            $actions->{'C'} = {
-                path  => "Ticket/Display.html?Action=Steal&id=" . $id,
-                title => loc('Steal'),
-                }
-                if $can{'ModifyTicket'}
-                    or $Ticket->CurrentUserHasRight('StealTicket');
+        if (   $can{'ModifyTicket'}
+            or $Ticket->CurrentUserHasRight('CommentOnTicket') )
+        {
+            $actions->{'E'} = {
+                title => loc('Comment'),
+                path  => "Ticket/Update.html?Action=Comment&id=" . $id,
+            };
         }
-    }
 
-    if (   $can{'ModifyTicket'}
-        or $Ticket->CurrentUserHasRight('CommentOnTicket') )
-    {
-        $actions->{'E'} = {
-            title => loc('Comment'),
-            path  => "Ticket/Update.html?Action=Comment&id=" . $id,
-        };
-    }
-
-    $actions->{'_ZZ'}
-        = { html => $m->scomp( '/Ticket/Elements/Bookmark', id => $Ticket->id ),
-        };
+        $actions->{'_ZZ'} = { html => $m->scomp( '/Ticket/Elements/Bookmark', id => $Ticket->id ), };
 
-}
+    }
 
-if ( ( defined $actions->{A} || defined $actions->{B} || defined $actions->{C} )
+if ( (  defined $actions->{B} || defined $actions->{C} )
     && (   defined $actions->{E}
         || defined $actions->{F}
         || defined $actions->{G} ) )
@@ -262,9 +279,16 @@ if ( ( defined $actions->{A} || defined $actions->{B} || defined $actions->{C} )
 
     if    ( defined $actions->{C} ) { $actions->{C}->{separator} = 1 }
     elsif ( defined $actions->{B} ) { $actions->{B}->{separator} = 1 }
-    elsif ( defined $actions->{A} ) { $actions->{A}->{separator} = 1 }
 }
 
+$actions->{'G' . 1}{pre_separator} = 1;
+
+# Separator between the last status and the next set of actions
+$actions->{'G' . ($i-1)}{separator} = 1;
+
+
+
+
 my $args = '';
 my $has_query = '';
 my %query_args;
diff --git a/t/status-schemas/basics.t b/t/status-schemas/basics.t
new file mode 100644
index 0000000..e483488
--- /dev/null
+++ b/t/status-schemas/basics.t
@@ -0,0 +1,214 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Data::Dumper;
+
+require 't/utils.pl';
+use Test::More tests => 49;
+
+my $general = RT::Test->load_or_create_queue(
+    Name => 'General',
+);
+ok $general && $general->id, 'loaded or created a queue';
+
+my $tstatus = sub {
+    DBIx::SearchBuilder::Record::Cachable->FlushCache;
+    my $ticket = RT::Ticket->new( $RT::SystemUser );
+    $ticket->Load( $_[0] );
+    return $ticket->Status;
+};
+
+diag "check basic API";
+{
+    my $schema = $general->status_schema;
+    isa_ok($schema, 'RT::StatusSchema');
+    is $schema->name, 'default', "it's a default schema";
+    is join(', ', $schema->valid),
+        join(', ', qw(new open stalled resolved rejected deleted)),
+        'this is default set';
+}
+
+my ($baseurl, $m) = RT::Test->started_ok;
+ok $m->login, 'logged in';
+
+diag "check status input on create";
+{
+    $m->goto_create_ticket( $general );
+
+    my $form = $m->form_name('TicketCreate');
+    ok my $input = $form->find_input('Status'), 'found status selector';
+
+    my @form_values = $input->possible_values;
+    ok scalar @form_values, 'some options in the UI';
+
+    my $valid = 1;
+    foreach ( @form_values ) {
+        $valid = 0 unless $general->status_schema->is_valid($_);
+    }
+    ok $valid, 'all statuses in the form are valid';
+}
+
+diag "create a ticket";
+my $tid;
+{
+    my $ticket = RT::Ticket->new( $RT::SystemUser );
+    ($tid) = $ticket->Create( Queue => $general->id, Subject => 'test' );
+    ok $tid, "created a ticket #$tid";
+    is $ticket->Status, 'new', 'correct status';
+}
+
+diag "new ->(open it)-> open";
+{
+    ok $m->goto_ticket( $tid ), 'opened a ticket';
+
+    {
+        my @links = $m->followable_links;
+        ok scalar @links, 'found links';
+        my $found = 1;
+        foreach my $t ('Open It', 'Resolve', 'Reject', 'Delete') {
+            $found = 0 unless grep $_->text eq $t, @links;
+        }
+        ok $found, 'found all transitions';
+
+        $found = 0;
+        foreach my $t ('Stall', 'Re-open', 'Undelete') {
+            $found = 1 if grep $_->text eq $t, @links;
+        }
+        ok !$found, 'no unwanted transitions';
+    }
+
+    $m->follow_link_ok({text => 'Open It'});
+    $m->form_number(3);
+    $m->click('SubmitTicket');
+
+    is $tstatus->($tid), 'open', 'changed status';
+}
+
+diag "open ->(stall)-> stalled";
+{
+    is $tstatus->($tid), 'open', 'ticket is open';
+
+    ok $m->goto_ticket( $tid ), 'opened a ticket';
+
+    {
+        my @links = $m->followable_links;
+        ok scalar @links, 'found links';
+        my $found = 1;
+        foreach my $t ('Stall', 'Resolve', 'Reject') {
+            $found = 0 unless grep $_->text eq $t, @links;
+        }
+        ok $found, 'found all transitions';
+
+        $found = 0;
+        foreach my $t ('Open It', 'Delete', 'Re-open', 'Undelete') {
+            $found = 1 if grep $_->text eq $t, @links;
+        }
+        ok !$found, 'no unwanted transitions';
+    }
+
+    $m->follow_link_ok({text => 'Stall'});
+    $m->form_number(3);
+    $m->click('SubmitTicket');
+
+    is $tstatus->($tid), 'stalled', 'changed status';
+}
+
+diag "stall ->(open it)-> open";
+{
+    is $tstatus->($tid), 'stalled', 'ticket is stalled';
+
+    ok $m->goto_ticket( $tid ), 'opened a ticket';
+
+    {
+        my @links = $m->followable_links;
+        ok scalar @links, 'found links';
+        my $found = 1;
+        foreach my $t ('Open It') {
+            $found = 0 unless grep $_->text eq $t, @links;
+        }
+        ok $found, 'found all transitions';
+
+        $found = 0;
+        foreach my $t ('Delete', 'Re-open', 'Undelete', 'Stall', 'Resolve', 'Reject') {
+            $found = 1 if grep $_->text eq $t, @links;
+        }
+        ok !$found, 'no unwanted transitions';
+    }
+
+    $m->follow_link_ok({text => 'Open It'});
+
+    is $tstatus->($tid), 'open', 'changed status';
+}
+
+diag "open -> deleted, only via modify";
+{
+    is $tstatus->($tid), 'open', 'ticket is open';
+
+    $m->get_ok( '/Ticket/Modify.html?id='. $tid );
+    my $form = $m->form_number(3);
+    ok my $input = $form->find_input('Status'), 'found status selector';
+
+    my @form_values = $input->possible_values;
+    ok scalar @form_values, 'some options in the UI';
+
+    ok grep($_ eq 'deleted', @form_values), "has deleted";
+
+    $m->select( Status => 'deleted' );
+    $m->submit;
+
+    is $tstatus->($tid), 'deleted', 'deleted ticket';
+}
+
+diag "deleted -> X via modify, only open is available";
+{
+    is $tstatus->($tid), 'deleted', 'ticket is deleted';
+
+    $m->get_ok( '/Ticket/Modify.html?id='. $tid );
+    my $form = $m->form_number(3);
+    ok my $input = $form->find_input('Status'), 'found status selector';
+
+    my @form_values = $input->possible_values;
+    ok scalar @form_values, 'some options in the UI';
+
+    is join('-', @form_values), '-open', 'only open and default available';
+}
+
+diag "check illegal values and transitions";
+{
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $general->id,
+            Subject => 'test',
+            Status => 'illegal',
+        );
+        ok !$id, 'have not created a ticket';
+    }
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $general->id,
+            Subject => 'test',
+            Status => 'new',
+        );
+        ok $id, 'created a ticket';
+    }
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $general->id,
+            Subject => 'test',
+            Status => 'new',
+        );
+        ok $id, 'created a ticket';
+
+        (my $status, $msg) = $ticket->SetStatus( 'illeagal' );
+        ok !$status, "couldn't set illeagal status";
+        is $ticket->Status, 'new', 'status is steal the same';
+
+        ($status, $msg) = $ticket->SetStatus( 'stalled' );
+        ok !$status, "couldn't set status, transition is illeagal";
+        is $ticket->Status, 'new', 'status is steal the same';
+    }
+}
diff --git a/t/status-schemas/dates.t b/t/status-schemas/dates.t
new file mode 100644
index 0000000..a924d49
--- /dev/null
+++ b/t/status-schemas/dates.t
@@ -0,0 +1,299 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Data::Dumper;
+
+require 't/utils.pl';
+use Test::More tests => 82;
+
+my $general = RT::Test->load_or_create_queue(
+    Name => 'General',
+);
+ok $general && $general->id, 'loaded or created a queue';
+
+my $delivery = RT::Test->load_or_create_queue(
+    Name => 'delivery',
+);
+ok $delivery && $delivery->id, 'loaded or created a queue';
+
+my $tstatus = sub {
+    DBIx::SearchBuilder::Record::Cachable->FlushCache;
+    my $ticket = RT::Ticket->new( $RT::SystemUser );
+    $ticket->Load( $_[0] );
+    return $ticket->Status;
+};
+
+my ($baseurl, $m) = RT::Test->started_ok;
+ok $m->login, 'logged in';
+
+diag "check basic API";
+{
+    my $schema = $general->status_schema;
+    isa_ok($schema, 'RT::StatusSchema');
+    is $schema->name, 'default', "it's a default schema";
+
+    $schema = $delivery->status_schema;
+    isa_ok($schema, 'RT::StatusSchema');
+    is $schema->name, 'delivery', "it's a delivery schema";
+}
+
+diag "dates on create for default schema";
+{
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $general->id,
+            Subject => 'test',
+            Status => 'new',
+        );
+        ok $id, 'created a ticket';
+        ok $ticket->StartedObj->Unix <= 0, 'started is not set';
+        ok $ticket->ResolvedObj->Unix <= 0, 'resolved is not set';
+    }
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $general->id,
+            Subject => 'test',
+            Status => 'open',
+        );
+        ok $id, 'created a ticket';
+        ok $ticket->StartedObj->Unix > 0, 'started is set';
+        ok $ticket->ResolvedObj->Unix <= 0, 'resolved is not set';
+    }
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $general->id,
+            Subject => 'test',
+            Status => 'resolved',
+        );
+        ok $id, 'created a ticket';
+        ok $ticket->StartedObj->Unix > 0, 'started is set';
+        ok $ticket->ResolvedObj->Unix > 0, 'resolved is set';
+    }
+
+    my $test_date = '2008-11-28 12:00:00';
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $general->id,
+            Subject => 'test',
+            Status => 'new',
+            Started => $test_date,
+            Resolved => $test_date,
+        );
+        ok $id, 'created a ticket';
+        is $ticket->StartedObj->ISO, $test_date, 'started is set';
+        is $ticket->ResolvedObj->ISO, $test_date, 'resolved is set';
+    }
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $general->id,
+            Subject => 'test',
+            Status => 'open',
+            Started => $test_date,
+            Resolved => $test_date,
+        );
+        ok $id, 'created a ticket';
+        is $ticket->StartedObj->ISO, $test_date, 'started is set';
+        is $ticket->ResolvedObj->ISO, $test_date, 'resolved is set';
+    }
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $general->id,
+            Subject => 'test',
+            Status => 'resolved',
+            Started => $test_date,
+            Resolved => $test_date,
+        );
+        ok $id, 'created a ticket';
+        is $ticket->StartedObj->ISO, $test_date, 'started is set';
+        is $ticket->ResolvedObj->ISO, $test_date, 'resolved is set';
+    }
+}
+
+diag "dates on create for delivery schema";
+{
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $delivery->id,
+            Subject => 'test',
+            Status => 'ordered',
+        );
+        ok $id, 'created a ticket';
+        ok $ticket->StartedObj->Unix <= 0, 'started is not set';
+        ok $ticket->ResolvedObj->Unix <= 0, 'resolved is not set';
+    }
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $delivery->id,
+            Subject => 'test',
+            Status => 'on way',
+        );
+        ok $id, 'created a ticket';
+        ok $ticket->StartedObj->Unix > 0, 'started is set';
+        ok $ticket->ResolvedObj->Unix <= 0, 'resolved is not set';
+    }
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $delivery->id,
+            Subject => 'test',
+            Status => 'delivered',
+        );
+        ok $id, 'created a ticket';
+        ok $ticket->StartedObj->Unix > 0, 'started is set';
+        ok $ticket->ResolvedObj->Unix > 0, 'resolved is set';
+    }
+
+    my $test_date = '2008-11-28 12:00:00';
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $delivery->id,
+            Subject => 'test',
+            Status => 'ordered',
+            Started => $test_date,
+            Resolved => $test_date,
+        );
+        ok $id, 'created a ticket';
+        is $ticket->StartedObj->ISO, $test_date, 'started is set';
+        is $ticket->ResolvedObj->ISO, $test_date, 'resolved is set';
+    }
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $delivery->id,
+            Subject => 'test',
+            Status => 'on way',
+            Started => $test_date,
+            Resolved => $test_date,
+        );
+        ok $id, 'created a ticket';
+        is $ticket->StartedObj->ISO, $test_date, 'started is set';
+        is $ticket->ResolvedObj->ISO, $test_date, 'resolved is set';
+    }
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $delivery->id,
+            Subject => 'test',
+            Status => 'delivered',
+            Started => $test_date,
+            Resolved => $test_date,
+        );
+        ok $id, 'created a ticket';
+        is $ticket->StartedObj->ISO, $test_date, 'started is set';
+        is $ticket->ResolvedObj->ISO, $test_date, 'resolved is set';
+    }
+}
+
+diag "dates on status change for default schema";
+{
+    my $ticket = RT::Ticket->new( $RT::SystemUser );
+    my ($id, $msg) = $ticket->Create(
+        Queue => $general->id,
+        Subject => 'test',
+        Status => 'new',
+    );
+    ok $id, 'created a ticket';
+    ok $ticket->StartedObj->Unix <= 0, 'started is not set';
+    ok $ticket->ResolvedObj->Unix <= 0, 'resolved is not set';
+
+    (my $status, $msg) = $ticket->SetStatus('open');
+    ok $status, 'changed status' or diag "error: $msg";
+    ok $ticket->StartedObj->Unix > 0, 'started is set';
+    ok $ticket->ResolvedObj->Unix <= 0, 'resolved is not set';
+
+    my $started = $ticket->StartedObj->Unix;
+
+    ($status, $msg) = $ticket->SetStatus('stalled');
+    ok $status, 'changed status' or diag "error: $msg";
+    is $ticket->StartedObj->Unix, $started, 'started is set and the same';
+    ok $ticket->ResolvedObj->Unix <= 0, 'resolved is not set';
+
+    ($status, $msg) = $ticket->SetStatus('open');
+    ok $status, 'changed status' or diag "error: $msg";
+    is $ticket->StartedObj->Unix, $started, 'started is set and the same';
+    ok $ticket->ResolvedObj->Unix <= 0, 'resolved is not set';
+
+    ($status, $msg) = $ticket->SetStatus('resolved');
+    ok $status, 'changed status' or diag "error: $msg";
+    is $ticket->StartedObj->Unix, $started, 'started is set and the same';
+    ok $ticket->ResolvedObj->Unix > 0, 'resolved is set';
+}
+
+diag "dates on status change for delivery schema";
+{
+    my $ticket = RT::Ticket->new( $RT::SystemUser );
+    my ($id, $msg) = $ticket->Create(
+        Queue => $delivery->id,
+        Subject => 'test',
+        Status => 'ordered',
+    );
+    ok $id, 'created a ticket';
+    ok $ticket->StartedObj->Unix <= 0, 'started is not set';
+    ok $ticket->ResolvedObj->Unix <= 0, 'resolved is not set';
+
+    (my $status, $msg) = $ticket->SetStatus('delayed');
+    ok $status, 'changed status' or diag "error: $msg";
+    ok $ticket->StartedObj->Unix > 0, 'started is set';
+    ok $ticket->ResolvedObj->Unix <= 0, 'resolved is not set';
+
+    my $started = $ticket->StartedObj->Unix;
+
+    ($status, $msg) = $ticket->SetStatus('on way');
+    ok $status, 'changed status' or diag "error: $msg";
+    is $ticket->StartedObj->Unix, $started, 'started is set and the same';
+    ok $ticket->ResolvedObj->Unix <= 0, 'resolved is not set';
+
+    ($status, $msg) = $ticket->SetStatus('delivered');
+    ok $status, 'changed status' or diag "error: $msg";
+    is $ticket->StartedObj->Unix, $started, 'started is set and the same';
+    ok $ticket->ResolvedObj->Unix > 0, 'resolved is set';
+}
+
+diag "add partial map between general->delivery";
+{
+    my $schemas = RT->Config->Get('StatusSchemaMeta');
+    $schemas->{'__maps__'} = {
+        'default -> delivery' => {
+            new => 'on way',
+        },
+        'delivery -> default' => {
+            'on way' => 'resolved',
+        },
+    };
+    RT::StatusSchema->fill_cache;
+}
+
+diag "check date changes on moving a ticket";
+{
+    my $ticket = RT::Ticket->new( $RT::SystemUser );
+    my ($id, $msg) = $ticket->Create(
+        Queue => $general->id,
+        Subject => 'test',
+        Status => 'new',
+    );
+    ok $id, 'created a ticket';
+    ok $ticket->StartedObj->Unix <= 0, 'started is not set';
+    ok $ticket->ResolvedObj->Unix <= 0, 'resolved is not set';
+
+    (my $status, $msg) = $ticket->SetQueue( $delivery->id );
+    ok $status, "moved ticket between queues with different schemas";
+    is $ticket->Status, 'on way', 'status has been changed';
+    ok $ticket->StartedObj->Unix > 0, 'started is set';
+    ok $ticket->ResolvedObj->Unix <= 0, 'resolved is not set';
+
+    ($status, $msg) = $ticket->SetQueue( $general->id );
+    ok $status, "moved ticket between queues with different schemas";
+    is $ticket->Status, 'resolved', 'status has been changed';
+    ok $ticket->StartedObj->Unix > 0, 'started is set';
+    ok $ticket->ResolvedObj->Unix > 0, 'resolved is set';
+}
diff --git a/t/status-schemas/moving.t b/t/status-schemas/moving.t
new file mode 100644
index 0000000..fc8aa7a
--- /dev/null
+++ b/t/status-schemas/moving.t
@@ -0,0 +1,97 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Data::Dumper;
+
+require 't/utils.pl';
+use Test::More tests => 18;
+
+my $general = RT::Test->load_or_create_queue(
+    Name => 'General',
+);
+ok $general && $general->id, 'loaded or created a queue';
+
+my $delivery = RT::Test->load_or_create_queue(
+    Name => 'delivery',
+);
+ok $delivery && $delivery->id, 'loaded or created a queue';
+
+my $tstatus = sub {
+    DBIx::SearchBuilder::Record::Cachable->FlushCache;
+    my $ticket = RT::Ticket->new( $RT::SystemUser );
+    $ticket->Load( $_[0] );
+    return $ticket->Status;
+};
+
+diag "check moving without a map";
+{
+    my $ticket = RT::Ticket->new( $RT::SystemUser );
+    my ($id, $msg) = $ticket->Create(
+        Queue => $general->id,
+        Subject => 'test',
+        Status => 'new',
+    );
+    ok $id, 'created a ticket';
+    (my $status, $msg) = $ticket->SetQueue( $delivery->id );
+    ok !$status, "couldn't change queue when there is no maps between schemas";
+    is $ticket->Queue, $general->id, 'queue is steal the same';
+    is $ticket->Status, 'new', 'status is steal the same';
+}
+
+diag "add partial map";
+{
+    my $schemas = RT->Config->Get('StatusSchemaMeta');
+    $schemas->{'__maps__'} = {
+        'default -> delivery' => {
+            new => 'ordered',
+        },
+    };
+    RT::StatusSchema->fill_cache;
+}
+
+diag "check moving with a partial map";
+{
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $general->id,
+            Subject => 'test',
+            Status => 'new',
+        );
+        ok $id, 'created a ticket';
+        (my $status, $msg) = $ticket->SetQueue( $delivery->id );
+        ok $status, "moved ticket between queues with different schemas";
+        is $ticket->Queue, $delivery->id, 'queue has been changed'
+            or diag "error: $msg";
+        is $ticket->Status, 'ordered', 'status has been changed';
+    }
+    {
+        my $ticket = RT::Ticket->new( $RT::SystemUser );
+        my ($id, $msg) = $ticket->Create(
+            Queue => $general->id,
+            Subject => 'test',
+            Status => 'open',
+        );
+        ok $id, 'created a ticket';
+        (my $status, $msg) = $ticket->SetQueue( $delivery->id );
+        ok !$status, "couldn't change queue when map is not complete";
+        is $ticket->Queue, $general->id, 'queue is steal the same';
+        is $ticket->Status, 'open', 'status is steal the same';
+    }
+}
+
+diag "one way map doesn't work backwards";
+{
+    my $ticket = RT::Ticket->new( $RT::SystemUser );
+    my ($id, $msg) = $ticket->Create(
+        Queue => $delivery->id,
+        Subject => 'test',
+        Status => 'ordered',
+    );
+    ok $id, 'created a ticket';
+    (my $status, $msg) = $ticket->SetQueue( $general->id );
+    ok !$status, "couldn't change queue when there is no maps between schemas";
+    is $ticket->Queue, $delivery->id, 'queue is steal the same';
+    is $ticket->Status, 'ordered', 'status is steal the same';
+}
diff --git a/t/status-schemas/utils.pl b/t/status-schemas/utils.pl
new file mode 100644
index 0000000..5e07144
--- /dev/null
+++ b/t/status-schemas/utils.pl
@@ -0,0 +1,69 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+### after: use lib qw(@RT_LIB_PATH@);
+use lib qw(/opt/rt3/local/lib /opt/rt3/lib);
+
+my $config;
+BEGIN {
+$config = <<END;
+Set(\@Plugins, 'RT::Extension::StatusSchemas');
+
+Set(\%StatusSchemaMeta,
+    default => {
+        initial  => ['new'],
+        active   => [qw(open stalled)],
+        inactive => [qw(resolved rejected deleted)],
+        transitions => {
+            new      => [qw(open resolved rejected deleted)],
+            open     => [qw(stalled resolved rejected deleted)],
+            stalled  => [qw(open)],
+            resolved => [qw(open)],
+            rejected => [qw(open)],
+            deleted  => [qw(open)],
+        },
+        actions => {
+            'new -> open'     => ['Open It', 'Respond'],
+            'new -> resolved' => ['Resolve', 'Comment'],
+            'new -> rejected' => ['Reject',  'Respond'],
+            'new -> deleted'  => ['Delete',  ''],
+
+            'open -> stalled'  => ['Stall',   'Comment'],
+            'open -> resolved' => ['Resolve', 'Comment'],
+            'open -> rejected' => ['Reject',  'Respond'],
+            'open -> deleted'  => ['Delete',  'hide'],
+
+            'stalled -> open'  => ['Open It',  ''],
+            'resolved -> open' => ['Re-open',  'Comment'],
+            'rejected -> open' => ['Re-open',  'Comment'],
+            'deleted -> open'  => ['Undelete', ''],
+        },
+    },
+    delivery => {
+        initial  => ['ordered'],
+        active   => ['on way', 'delayed'],
+        inactive => ['delivered'],
+        transitions => {
+            ordered   => ['on way', 'delayed'],
+            'on way'  => ['delivered'],
+            delayed   => ['on way'],
+            delivered => [],
+        },
+        actions => {
+            'ordered -> on way'   => ['Put On Way', 'Respond'],
+            'ordered -> delayed'  => ['Delay',      'Respond'],
+
+            'on way -> delivered' => ['Done',       'Respond'],
+            'delayed -> on way'   => ['Put On Way', 'Respond'],
+        },
+    },
+);
+Set(\%StatusSchemas, delivery => 'delivery');
+END
+}
+
+use RT::Test config => $config;
+
+1;

commit cdea1e27cfa471f6226e1a9cef4be124cc791006
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Thu Aug 12 18:00:07 2010 -0400

    merge fixups

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index d67fd4c..2fa07c7 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1752,19 +1752,51 @@ Set($ApprovalRejectionNotes, 1);
 
 =head1 Miscellaneous Configuration
 
-=over 4
+=cut
+
+    Set( %StatusSchemaMeta,
+        default => {
+            initial  => ['new'],
+            active   => ['open', 'stalled'],
+            inactive => ['resolved', 'rejected', 'deleted'],
+
+            transitions => {
+                # from   => [ to list ],
+                new      => [qw(open resolved rejected deleted)],
+                open     => [qw(stalled resolved rejected deleted)],
+                stalled  => [qw(open)],
+                resolved => [qw(open)],
+                rejected => [qw(open)],
+                deleted  => [qw(open)],
+            },
+            rights  => {
+                '* -> deleted'  => 'DeleteTicket',
+                '* -> rejected' => 'RejectTicket',
+                '* -> *'        => 'ModifyTicketStatus',
+            },
+            actions => {
+                # 'from -> to'    => [action text, Respond/Comment/hide/''],
+                'new -> open'     => ['Open It', 'Respond'],
+                'new -> resolved' => ['Resolve', 'Comment'],
+                'new -> rejected' => ['Reject',  'Respond'],
+                'new -> deleted'  => ['Delete',  ''],
+
+                'open -> stalled'  => ['Stall',   'Comment'],
+                'open -> resolved' => ['Resolve', 'Comment'],
+                'open -> rejected' => ['Reject',  'Respond'],
+                'open -> deleted'  => ['Delete',  'hide'],
+
+                'stalled -> open'  => ['Open It',  ''],
+                'resolved -> open' => ['Re-open',  'Comment'],
+                'rejected -> open' => ['Re-open',  'Comment'],
+                'deleted -> open'  => ['Undelete', ''],
+            },
+        },
+    );
 
-=item C<@ActiveStatus>, C<@InactiveStatus>
 
-You can define new statuses and even reorder existing statuses here.
-WARNING. DO NOT DELETE ANY OF THE DEFAULT STATUSES. If you do, RT
-will break horribly. The statuses you add must be no longer than
-10 characters.
 
-=cut
 
-Set(@ActiveStatus, qw(new open stalled));
-Set(@InactiveStatus, qw(resolved rejected deleted));
 
 =item C<$LinkTransactionsRun1Scrip>
 
diff --git a/lib/RT.pm.in b/lib/RT.pm.in
index 3d2fa7e..1858af2 100755
--- a/lib/RT.pm.in
+++ b/lib/RT.pm.in
@@ -454,6 +454,7 @@ sub InitClasses {
     require RT::Attributes;
     require RT::Dashboard;
     require RT::Approval;
+    require RT::StatusSchemas;
 
     # on a cold server (just after restart) people could have an object
     # in the session, as we deserialize it so we never call constructor
diff --git a/lib/RT/Queue_Overlay.pm b/lib/RT/Queue_Overlay.pm
index cb97479..4680dfd 100755
--- a/lib/RT/Queue_Overlay.pm
+++ b/lib/RT/Queue_Overlay.pm
@@ -109,11 +109,13 @@ our $RIGHTS = {
     CommentOnTicket => 'Comment on tickets',                          # loc_pair
     OwnTicket       => 'Own tickets',                                 # loc_pair
     ModifyTicket    => 'Modify tickets',                              # loc_pair
+    ModifyTicketStatus    => 'Modify ticket status',                              # loc_pair
     DeleteTicket    => 'Delete tickets',                              # loc_pair
+    RejectTicket    => 'Reject tickets',                              # loc_pair
     TakeTicket      => 'Take tickets',                                # loc_pair
     StealTicket     => 'Steal tickets',                               # loc_pair
 
-    ForwardMessage  => 'Forward messages to third person(s)',         # loc_pair
+    ForwardMessage  => 'Forward messages outside of RT',         # loc_pair
 
 };
 
diff --git a/lib/RT/StatusSchemas.pm b/lib/RT/StatusSchemas.pm
index ba718bc..76af64f 100644
--- a/lib/RT/StatusSchemas.pm
+++ b/lib/RT/StatusSchemas.pm
@@ -1,6 +1,5 @@
 use strict;
 use warnings;
-no warnings 'redefine';
 
 package RT::StatusSchemas;
 
diff --git a/share/html/Ticket/Elements/Tabs b/share/html/Ticket/Elements/Tabs
index 3dc47af..8a672c5 100755
--- a/share/html/Ticket/Elements/Tabs
+++ b/share/html/Ticket/Elements/Tabs
@@ -287,7 +287,7 @@ $actions->{'G' . 1}{pre_separator} = 1;
 $actions->{'G' . ($i-1)}{separator} = 1;
 
 
-
+}
 
 my $args = '';
 my $has_query = '';
diff --git a/t/status-schemas/basics.t b/t/status-schemas/basics.t
index e483488..598a58d 100644
--- a/t/status-schemas/basics.t
+++ b/t/status-schemas/basics.t
@@ -4,7 +4,7 @@ use strict;
 use warnings;
 use Data::Dumper;
 
-require 't/utils.pl';
+require 't/status-schemas/utils.pl';
 use Test::More tests => 49;
 
 my $general = RT::Test->load_or_create_queue(
diff --git a/t/status-schemas/utils.pl b/t/status-schemas/utils.pl
index 5e07144..ec57d1b 100644
--- a/t/status-schemas/utils.pl
+++ b/t/status-schemas/utils.pl
@@ -3,14 +3,10 @@
 use strict;
 use warnings;
 
-### after: use lib qw(@RT_LIB_PATH@);
-use lib qw(/opt/rt3/local/lib /opt/rt3/lib);
 
 my $config;
 BEGIN {
 $config = <<END;
-Set(\@Plugins, 'RT::Extension::StatusSchemas');
-
 Set(\%StatusSchemaMeta,
     default => {
         initial  => ['new'],

commit ceafabc115025e830d5d720826280192a1bd5b4b
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Thu Aug 12 18:18:42 2010 -0400

    updates to SelectStatus to remove a newline in the middle of /option

diff --git a/share/html/Elements/SelectStatus b/share/html/Elements/SelectStatus
index 64bf5d6..f4f1dab 100644
--- a/share/html/Elements/SelectStatus
+++ b/share/html/Elements/SelectStatus
@@ -4,8 +4,7 @@
 %}
 %foreach my $status (@status) {
 %next if ($SkipDeleted && $status eq 'deleted');
-<option value="<%$status%>"<% (defined $Default && $status eq $Default) && qq[ selected="selected"] |n %>><%loc($status)%></o
-ption>
+<option value="<%$status%>"<% (defined $Default && $status eq $Default) && qq[ selected="selected"] |n %>><%loc($status)%></option>
 % }
 </select>
 <%INIT>

commit a9a7f43a7314bf1faacb19faf88bbe8085bca7de
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Thu Aug 12 18:19:03 2010 -0400

    now actually RUN all status-schema tests - not all pass

diff --git a/t/status-schemas/basics.t b/t/status-schemas/basics.t
index 598a58d..b8839c2 100644
--- a/t/status-schemas/basics.t
+++ b/t/status-schemas/basics.t
@@ -4,8 +4,7 @@ use strict;
 use warnings;
 use Data::Dumper;
 
-require 't/status-schemas/utils.pl';
-use Test::More tests => 49;
+BEGIN {require  't/status-schemas/utils.pl'};
 
 my $general = RT::Test->load_or_create_queue(
     Name => 'General',
@@ -36,6 +35,7 @@ diag "check status input on create";
 {
     $m->goto_create_ticket( $general );
 
+    diag $m->uri;
     my $form = $m->form_name('TicketCreate');
     ok my $input = $form->find_input('Status'), 'found status selector';
 
@@ -44,7 +44,9 @@ diag "check status input on create";
 
     my $valid = 1;
     foreach ( @form_values ) {
-        $valid = 0 unless $general->status_schema->is_valid($_);
+        next if $general->status_schema->is_valid($_);
+        $valid = 0;
+        diag("$_ doesn't appear to be a valid status, but it was in the form");
     }
     ok $valid, 'all statuses in the form are valid';
 }
@@ -67,13 +69,13 @@ diag "new ->(open it)-> open";
         ok scalar @links, 'found links';
         my $found = 1;
         foreach my $t ('Open It', 'Resolve', 'Reject', 'Delete') {
-            $found = 0 unless grep $_->text eq $t, @links;
+            $found = 0 unless grep {($_->text ||'') eq $t} @links;
         }
         ok $found, 'found all transitions';
 
         $found = 0;
         foreach my $t ('Stall', 'Re-open', 'Undelete') {
-            $found = 1 if grep $_->text eq $t, @links;
+            $found = 1 if grep {($_->text||'') eq $t} @links;
         }
         ok !$found, 'no unwanted transitions';
     }
@@ -96,13 +98,13 @@ diag "open ->(stall)-> stalled";
         ok scalar @links, 'found links';
         my $found = 1;
         foreach my $t ('Stall', 'Resolve', 'Reject') {
-            $found = 0 unless grep $_->text eq $t, @links;
+            $found = 0 unless grep { ($_->text ||'') eq $t} @links;
         }
         ok $found, 'found all transitions';
 
         $found = 0;
         foreach my $t ('Open It', 'Delete', 'Re-open', 'Undelete') {
-            $found = 1 if grep $_->text eq $t, @links;
+            $found = 1 if grep { ($_->text ||'') eq $t} @links;
         }
         ok !$found, 'no unwanted transitions';
     }
@@ -125,13 +127,13 @@ diag "stall ->(open it)-> open";
         ok scalar @links, 'found links';
         my $found = 1;
         foreach my $t ('Open It') {
-            $found = 0 unless grep $_->text eq $t, @links;
+            $found = 0 unless grep {($_->text ||'')eq $t} @links;
         }
         ok $found, 'found all transitions';
 
         $found = 0;
         foreach my $t ('Delete', 'Re-open', 'Undelete', 'Stall', 'Resolve', 'Reject') {
-            $found = 1 if grep $_->text eq $t, @links;
+            $found = 1 if grep { ($_->text ||'') eq $t} @links;
         }
         ok !$found, 'no unwanted transitions';
     }
diff --git a/t/status-schemas/dates.t b/t/status-schemas/dates.t
index a924d49..146d287 100644
--- a/t/status-schemas/dates.t
+++ b/t/status-schemas/dates.t
@@ -4,8 +4,7 @@ use strict;
 use warnings;
 use Data::Dumper;
 
-require 't/utils.pl';
-use Test::More tests => 82;
+BEGIN {require 't/status-schemas/utils.pl'};
 
 my $general = RT::Test->load_or_create_queue(
     Name => 'General',
diff --git a/t/status-schemas/moving.t b/t/status-schemas/moving.t
index fc8aa7a..9c864df 100644
--- a/t/status-schemas/moving.t
+++ b/t/status-schemas/moving.t
@@ -4,8 +4,7 @@ use strict;
 use warnings;
 use Data::Dumper;
 
-require 't/utils.pl';
-use Test::More tests => 18;
+BEGIN {require 't/status-schemas/utils.pl'};
 
 my $general = RT::Test->load_or_create_queue(
     Name => 'General',

commit 3a5697ff692d22f26d6a16a903c485c4ebf8a110
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Fri Aug 13 17:41:21 2010 -0400

    Update the "Default" status scheme to better match RT's 3.8's standard.

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 2fa07c7..db46e0b 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1756,23 +1756,23 @@ Set($ApprovalRejectionNotes, 1);
 
     Set( %StatusSchemaMeta,
         default => {
-            initial  => ['new'],
+            initial  => ['new', 'open'],
             active   => ['open', 'stalled'],
             inactive => ['resolved', 'rejected', 'deleted'],
 
             transitions => {
                 # from   => [ to list ],
-                new      => [qw(open resolved rejected deleted)],
-                open     => [qw(stalled resolved rejected deleted)],
-                stalled  => [qw(open)],
-                resolved => [qw(open)],
-                rejected => [qw(open)],
-                deleted  => [qw(open)],
+                new      => [qw(open stalled resolved rejected deleted)],
+                open     => [qw(new stalled resolved rejected deleted)],
+                stalled  => [qw(new open rejected resolved deleted)],
+                resolved => [qw(new open stalled rejected deleted)],
+                rejected => [qw(new open stalled resolved deleted)],
+                deleted  => [qw(new open stalled rejected resolved)],
             },
             rights  => {
                 '* -> deleted'  => 'DeleteTicket',
-                '* -> rejected' => 'RejectTicket',
-                '* -> *'        => 'ModifyTicketStatus',
+                '* -> rejected' => 'ModifyTicket',
+                '* -> *'        => 'ModifyTicket',
             },
             actions => {
                 # 'from -> to'    => [action text, Respond/Comment/hide/''],
@@ -1790,6 +1790,7 @@ Set($ApprovalRejectionNotes, 1);
                 'resolved -> open' => ['Re-open',  'Comment'],
                 'rejected -> open' => ['Re-open',  'Comment'],
                 'deleted -> open'  => ['Undelete', ''],
+                'open -> new'      => ['New', 'hide'],
             },
         },
     );

commit c58d2fa9b7a912b8497a1bb7a8eb46f14f1ee7be
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Fri Aug 13 17:45:41 2010 -0400

    Further work to integrate status schemas

diff --git a/lib/RT/Ticket_Overlay.pm b/lib/RT/Ticket_Overlay.pm
index 63dedf1..c5af5df 100755
--- a/lib/RT/Ticket_Overlay.pm
+++ b/lib/RT/Ticket_Overlay.pm
@@ -354,7 +354,17 @@ sub Create {
     if ( defined $args{'Started'} ) {
         $Started->Set( Format => 'ISO', Value => $args{'Started'} );
     }
-    elsif ( $args{'Status'} ne 'new' ) {
+    # We sort of want to ask "is this not an initial state
+    # But some schemes require active statuses to be duplicated
+    # in the initial state list so that tickets can be created
+    # in those states already open
+    #
+    # Instead, we check to make sure that it's either an "active" or "inactive" status
+    elsif (
+        $QueueObj->status_schema->is_active($args{'Status'}) ||
+        $QueueObj->status_schema->is_inactive($args{'Status'})
+
+    ){
         $Started->SetToNow;
     }
 
@@ -364,7 +374,7 @@ sub Create {
     }
 
     #If the status is an inactive status, set the resolved date
-    elsif ( $QueueObj->IsInactiveStatus( $args{'Status'} ) )
+    elsif ( $QueueObj->status_schema->is_inactive( $args{'Status'} ) )
     {
         $RT::Logger->debug( "Got a ". $args{'Status'}
             ."(inactive) ticket with undefined resolved date. Setting to now."
@@ -3057,10 +3067,9 @@ sub SetStatus {
     my $new = $args{'Status'};
     unless ( $schema->is_valid( $new ) ) {
         return (0,
-            $self->loc("Status '[_1]' is not valid for schema '[_2]'.",
-                $self->loc($new), $self->loc($schema->name)
-            )
-        );
+            $self->loc("Status '[_1]' isn't a valid status for tickets in this queue.",
+                $self->loc($new)
+        ));
     }
 
     my $old = $self->__Value('Status');
@@ -3087,7 +3096,7 @@ sub SetStatus {
     $now->SetToNow();
 
     #If we're changing the status from new, record that we've started
-    if ( $schema->is_initial($old) && !$schema->is_initial($new) ) {
+    if ( $schema->is_initial($old) && $schema->is_active($new) ) {
         #Set the Started time to "now"
         $self->_Set(
             Field             => 'Started',
diff --git a/share/html/Elements/SelectStatus b/share/html/Elements/SelectStatus
index f4f1dab..097882d 100644
--- a/share/html/Elements/SelectStatus
+++ b/share/html/Elements/SelectStatus
@@ -8,35 +8,24 @@
 % }
 </select>
 <%INIT>
-my $caller = $m->callers(1)->path;
-if ( $caller eq '/Ticket/Update.html' ) {
-    $Ticket = LoadTicket( $m->caller_args(1)->{'id'} );
-}
-elsif ( $caller eq '/Ticket/Create.html' ) {
-    $Queue = RT::Queue->new( $session{'CurrentUser'} );
-    $Queue->Load( $m->caller_args(1)->{'Queue'} );
-}
-elsif ( $caller eq '/Ticket/Elements/EditBasics' ) {
-    $Ticket = $m->caller_args(1)->{'TicketObj'};
-}
 ### XXX: no cover for Tools/MyDay.html
 
 
 my @status;
-if ( $Ticket ) {
-    my $current = $Ticket->Status;
-    my $schema = $Ticket->QueueObj->status_schema;
+if ( $TicketObj ) {
+    my $current = $TicketObj->Status;
+    my $schema = $TicketObj->QueueObj->status_schema;
 
     my %has = ();
     foreach my $next ( $schema->transitions( $current ) ) {
         my $check = $schema->check_right( $current => $next );
-        $has{ $check } = $Ticket->CurrentUserHasRight( $check )
+        $has{ $check } = $TicketObj->CurrentUserHasRight( $check )
             unless exists $has{ $check };
         push @status, $next if $has{ $check };
     }
 }
-elsif ( $Queue ) {
-    @status = $Queue->status_schema->valid;
+elsif ( $QueueObj ) {
+    @status = $QueueObj->status_schema->valid;
 }
 else {
     @status = RT::Queue->status_schema->valid;
@@ -44,8 +33,8 @@ else {
 </%INIT>
 <%ARGS>
 $Name => undef
-$Ticket => undef
-$Queue => undef
+$TicketObj => undef
+$QueueObj => undef
 $Default => ''
 $SkipDeleted => 0
 $DefaultValue => 1
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index 5a01466..8537714 100755
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -87,6 +87,7 @@
                     Default         => $ARGS{Status} || 'new',
                     DefaultValue    => 0,
                     SkipDeleted     => 1,
+                    QueueObj        => $QueueObj
                 },
             },
             {   name => 'Owner',
diff --git a/share/html/Ticket/Elements/EditBasics b/share/html/Ticket/Elements/EditBasics
index 59345ea..5d33858 100755
--- a/share/html/Ticket/Elements/EditBasics
+++ b/share/html/Ticket/Elements/EditBasics
@@ -71,6 +71,8 @@ unless ( @fields ) {
                 Name => 'Queue',
                 Default => $defaults{'Queue'} || $TicketObj->QueueObj->Id,
                 ShowNullOption => 0,
+                TicketObj => $TicketObj,
+                QueueObj => $TicketObj->QueueObj
             }
         },
         {   name => 'Owner',
diff --git a/share/html/Ticket/Elements/Tabs b/share/html/Ticket/Elements/Tabs
index 8a672c5..98f3788 100755
--- a/share/html/Ticket/Elements/Tabs
+++ b/share/html/Ticket/Elements/Tabs
@@ -45,15 +45,8 @@
 %# those contributions and any derivatives thereof.
 %# 
 %# END BPS TAGGED BLOCK }}}
-% $m->callback( Ticket => $Ticket, actions=> $actions, tabs => $tabs, %ARGS, args => \%ARGS );
-<& /Elements/Tabs, 
-    tabs => $tabs, 
-    actions => $actions, 
-    current_tab => $current_tab, 
-    current_toptab => $ARGS{current_toptab} || 'Search/Build.html',
-    Title => $Title &> 
 <%INIT>
-my $tabs = {};
+my $tabs       = {};
 my $searchtabs = {};
 my $actions;
 
@@ -61,11 +54,10 @@ if ($Ticket) {
 
     my $id = $Ticket->id();
 
-my $current = $Ticket->Status;
-my $schema = $Ticket->QueueObj->status_schema;
-my %has;
-my $i = 1;
-
+    my $current = $Ticket->Status;
+    my $schema  = $Ticket->QueueObj->status_schema;
+    my %has;
+    my $i = 1;
 
     if ( defined $session{'tickets'} ) {
 
@@ -90,8 +82,7 @@ my $i = 1;
             }
             $searchtabs->{"_b"} = {
                 class => "nav",
-                path  => "Ticket/Display.html?id="
-                    . $item_map->{ $Ticket->Id }->{prev},
+                path  => "Ticket/Display.html?id=" . $item_map->{ $Ticket->Id }->{prev},
                 title => '< ' . loc('Prev')
             };
         }
@@ -100,8 +91,7 @@ my $i = 1;
         if ( $item_map->{ $Ticket->Id }->{next} ) {
             $searchtabs->{'d'} = {
                 class => "nav",
-                path  => "Ticket/Display.html?id="
-                    . $item_map->{ $Ticket->Id }->{next},
+                path  => "Ticket/Display.html?id=" . $item_map->{ $Ticket->Id }->{next},
                 title => loc('Next') . ' >'
             };
             if ( $item_map->{last} ) {
@@ -122,14 +112,20 @@ my $i = 1;
     };
 
     my %can = (
-        ModifyTicket => $Ticket->CurrentUserHasRight('ModifyTicket'),
+        ModifyTicket      => $Ticket->CurrentUserHasRight('ModifyTicket'),
+        OwnTicket         => $Ticket->CurrentUserHasRight('OwnTicket'),
+        StealTicket         => $Ticket->CurrentUserHasRight('StealTicket'),
+        TakeTicket         => $Ticket->CurrentUserHasRight('TakeTicket'),
+        ReplyToTicket     => $Ticket->CurrentUserHasRight('ReplyToTicket'),
+        CommentOnTicket     => $Ticket->CurrentUserHasRight('CommentOnTicket'),
         ModifyCustomField => $Ticket->CurrentUserHasRight('ModifyCustomField'),
+        _ModifyOwner      => (
+                   $Ticket->CurrentUserHasRight('OwnTicket')
+                || $Ticket->CurrentUserHasRight('TakeTicket')
+                || $Ticket->CurrentUserHasRight('StealTicket')
+        )
     );
 
-    $can{_ModifyOwner} = $Ticket->CurrentUserHasRight('OwnTicket')
-                      || $Ticket->CurrentUserHasRight('TakeTicket')
-                      || $Ticket->CurrentUserHasRight('StealTicket');
-
     my $ticket_page_tabs = {
         _A => {
             title => loc('Display'),
@@ -142,21 +138,21 @@ my $i = 1;
         },
     };
 
-    if ($can{ModifyTicket} || $can{ModifyCustomField}) {
+    if ( $can{ModifyTicket} || $can{ModifyCustomField} ) {
         $ticket_page_tabs->{_B} = {
             title => loc('Basics'),
             path  => "Ticket/Modify.html?id=" . $id,
         };
     }
 
-    if ($can{ModifyTicket} || $can{_ModifyOwner}) {
+    if ( $can{ModifyTicket} || $can{_ModifyOwner} ) {
         $ticket_page_tabs->{_D} = {
             title => loc('People'),
             path  => "Ticket/ModifyPeople.html?id=" . $id,
         };
     }
 
-    if ($can{ModifyTicket}) {
+    if ( $can{ModifyTicket} ) {
         $ticket_page_tabs->{_C} = {
             title => loc('Dates'),
             path  => "Ticket/ModifyDates.html?id=" . $id,
@@ -167,7 +163,7 @@ my $i = 1;
         };
     }
 
-    if ($can{ModifyTicket} || $can{ModifyCustomField} || $can{_ModifyOwner}) {
+    if ( $can{ModifyTicket} || $can{ModifyCustomField} || $can{_ModifyOwner} ) {
         $ticket_page_tabs->{_X} = {
             title => loc('Jumbo'),
             path  => "Ticket/ModifyAll.html?id=" . $id,
@@ -185,23 +181,20 @@ my $i = 1;
     foreach my $tab ( sort keys %{$ticket_page_tabs} ) {
         if ( $ticket_page_tabs->{$tab}->{'path'} eq $current_tab ) {
             $ticket_page_tabs->{$tab}->{"subtabs"} = $subtabs;
-            $tabs->{'this'}->{"current_subtab"}
-                = $ticket_page_tabs->{$tab}->{"path"};
+            $tabs->{'this'}->{"current_subtab"} = $ticket_page_tabs->{$tab}->{"path"};
         }
     }
     $tabs->{'this'}->{"subtabs"} = $ticket_page_tabs;
     $current_tab = "Ticket/Display.html?id=" . $id;
 
-    if ( $can{'ModifyTicket'} or $Ticket->CurrentUserHasRight('ReplyToTicket') )
-    {
+    if ( $can{'ModifyTicket'} or $can{'ReplyToTicket'} ) {
         $actions->{'F'} = {
             title => loc('Reply'),
-            path  => "Ticket/Update.html?Action=Respond&id=" . $id,
+            path  => "Ticket/Update.html?Action=Respond;id=" . $id,
         };
     }
 
-    if ( $Ticket->CurrentUserHasRight('ForwardMessage') )
-    {
+    if ( $Ticket->CurrentUserHasRight('ForwardMessage') ) {
         $actions->{'FA'} = {
             title => loc('Forward'),
             path  => "Ticket/Forward.html?id=" . $id,
@@ -240,84 +233,87 @@ my $i = 1;
                 title => loc( $schema->transition_label( $current => $next ) ),
             };
         }
-        if ( $Ticket->CurrentUserHasRight('OwnTicket') ) {
-            if ( $Ticket->OwnerObj->Id == $RT::Nobody->id ) {
-                $actions->{'B'} = {
-                    path  => "Ticket/Display.html?Action=Take&id=" . $id,
-                    title => loc('Take'),
-                    }
-                    if $can{'ModifyTicket'}
-                        or $Ticket->CurrentUserHasRight('TakeTicket');
-            } elsif ( $Ticket->OwnerObj->id != $session{CurrentUser}->id ) {
-                $actions->{'C'} = {
-                    path  => "Ticket/Display.html?Action=Steal&id=" . $id,
-                    title => loc('Steal'),
-                    }
-                    if $can{'ModifyTicket'}
-                        or $Ticket->CurrentUserHasRight('StealTicket');
-            }
-        }
-
-        if (   $can{'ModifyTicket'}
-            or $Ticket->CurrentUserHasRight('CommentOnTicket') )
+    }
+    if ( $can{OwnTicket} ) {
+        if ($Ticket->OwnerObj->Id == $RT::Nobody->id
+            && (   $can{'ModifyTicket'} or $can{'TakeTicket'})
+            )
         {
-            $actions->{'E'} = {
-                title => loc('Comment'),
-                path  => "Ticket/Update.html?Action=Comment&id=" . $id,
+            $actions->{'B'} = {
+                path  => "Ticket/Display.html?Action=Take;id=" . $id,
+                title => loc('Take'),
+                }
+
+        } elsif ( $Ticket->OwnerObj->id != $RT::Nobody->id
+                    && $Ticket->OwnerObj->id != $session{CurrentUser}->id
+                    && ( $can{'ModifyTicket'} or $can{'StealTicket'})
+                )
+        {
+
+            $actions->{'C'} = {
+                path  => "Ticket/Display.html?Action=Steal;id=" . $id,
+                title => loc('Steal'),
             };
         }
+    }
 
-        $actions->{'_ZZ'} = { html => $m->scomp( '/Ticket/Elements/Bookmark', id => $Ticket->id ), };
-
+    if (   $can{'ModifyTicket'} || $can{'CommentOnTicket'}) {
+        $actions->{'E'} = {
+            title => loc('Comment'),
+            path  => "Ticket/Update.html?Action=Comment;id=" . $id,
+        };
     }
 
-if ( (  defined $actions->{B} || defined $actions->{C} )
+    $actions->{'_ZZ'} = { html => $m->scomp( '/Ticket/Elements/Bookmark', id => $Ticket->id ), };
+
+
+if (( defined $actions->{B} || defined $actions->{C} )
     && (   defined $actions->{E}
         || defined $actions->{F}
-        || defined $actions->{G} ) )
+        || defined $actions->{G} )
+    )
 {
 
     if    ( defined $actions->{C} ) { $actions->{C}->{separator} = 1 }
     elsif ( defined $actions->{B} ) { $actions->{B}->{separator} = 1 }
 }
 
-$actions->{'G' . 1}{pre_separator} = 1;
+$actions->{ 'G' . 1 }{pre_separator} = 1;
 
 # Separator between the last status and the next set of actions
-$actions->{'G' . ($i-1)}{separator} = 1;
-
-
+$actions->{ 'G' . ( $i - 1 ) }{separator} = 1;
 }
 
-my $args = '';
+my $args      = '';
 my $has_query = '';
 my %query_args;
-my $search_id = $ARGS{'SavedSearchId'}
-            || $session{'CurrentSearchHash'}->{'SearchId'} || '';
+my $search_id
+    = $ARGS{'SavedSearchId'}
+    || $session{'CurrentSearchHash'}->{'SearchId'}
+    || '';
 my $chart_search_id = $ARGS{'SavedChartSearchId'} || '';
 
 $has_query = 1 if ( $ARGS{'Query'} or $session{'CurrentSearchHash'}->{'Query'} );
-  
-%query_args = (
-        SavedSearchId => ($search_id eq 'new') ? undef : $search_id,
-        SavedChartSearchId => $chart_search_id,
-        Query  => $ARGS{'Query'}  || $session{'CurrentSearchHash'}->{'Query'},
-        Format => $ARGS{'Format'} || $session{'CurrentSearchHash'}->{'Format'},
-        OrderBy => $ARGS{'OrderBy'}
-            || $session{'CurrentSearchHash'}->{'OrderBy'},
-        Order => $ARGS{'Order'} || $session{'CurrentSearchHash'}->{'Order'},
-        Page  => $ARGS{'Page'}  || $session{'CurrentSearchHash'}->{'Page'},
-        RowsPerPage  => $ARGS{'RowsPerPage'}  || $session{'CurrentSearchHash'}->{'RowsPerPage'},
-    );
 
-    $args = "?" . $m->comp( '/Elements/QueryString', %query_args );
+%query_args = (
+    SavedSearchId => ( $search_id eq 'new' ) ? undef : $search_id,
+    SavedChartSearchId => $chart_search_id,
+    Query              => $ARGS{'Query'} || $session{'CurrentSearchHash'}->{'Query'},
+    Format             => $ARGS{'Format'} || $session{'CurrentSearchHash'}->{'Format'},
+    OrderBy            => $ARGS{'OrderBy'} || $session{'CurrentSearchHash'}->{'OrderBy'},
+    Order              => $ARGS{'Order'} || $session{'CurrentSearchHash'}->{'Order'},
+    Page               => $ARGS{'Page'} || $session{'CurrentSearchHash'}->{'Page'},
+    RowsPerPage        => $ARGS{'RowsPerPage'} || $session{'CurrentSearchHash'}->{'RowsPerPage'},
+);
+
+$args = "?" . $m->comp( '/Elements/QueryString', %query_args );
 
 $tabs->{"f"} = {
     path  => "Search/Build.html?NewQuery=1",
     title => loc('New Search')
 };
 $tabs->{"g"} = {
-    path  => "Search/Build.html" . (($has_query) ? $args : ''),
+    path => "Search/Build.html" . ( ($has_query) ? $args : '' ),
     title => loc('Edit Search')
 };
 $tabs->{"h"} = {
@@ -325,14 +321,13 @@ $tabs->{"h"} = {
     title     => loc('Advanced'),
     separator => 1
 };
+
 if ($has_query) {
 
     if ( $current_tab =~ m{Search/Results.html} ) {
         $current_tab = "Search/Results.html$args";
 
-        if ( $session{'CurrentUser'}
-            ->HasRight( Right => 'SuperUser', Object => $RT::System ) )
-        {
+        if ( $session{'CurrentUser'}->HasRight( Right => 'SuperUser', Object => $RT::System ) ) {
             my $shred_args = $m->comp(
                 '/Elements/QueryString',
                 Search          => 1,
@@ -371,10 +366,23 @@ if ($has_query) {
 
 foreach my $searchtab ( keys %{$searchtabs} ) {
     ( $searchtab =~ /^_/ )
-        ? $tabs->{ "s" . $searchtab } = $searchtabs->{$searchtab}
+        ? $tabs->{ "s" . $searchtab }
+        = $searchtabs->{$searchtab}
         : $tabs->{ "z_" . $searchtab } = $searchtabs->{$searchtab};
 }
 
+$m->callback( Ticket => $Ticket, actions => $actions, tabs => $tabs, %ARGS, args => \%ARGS );
+$m->comp(
+    "/Elements/Tabs",
+    tabs           => $tabs,
+    actions        => $actions,
+    current_tab    => $current_tab,
+    current_toptab => $ARGS{current_toptab} || 'Search/Build.html',
+    Title          => $Title
+);
+
+return;
+
 </%INIT>
 
   
diff --git a/share/html/Ticket/Update.html b/share/html/Ticket/Update.html
index 2e7b8ee..38e9f48 100755
--- a/share/html/Ticket/Update.html
+++ b/share/html/Ticket/Update.html
@@ -94,6 +94,8 @@
                 Name => 'Status',
                 DefaultLabel => loc("[_1] (Unchanged)", loc($TicketObj->Status)),
                 Default => $ARGS{'Status'} || ($TicketObj->Status eq $DefaultStatus ? undef : $DefaultStatus),
+                TicketObj => $TicketObj,
+                QueueObj => $TicketObj->QueueObj
             },
         },
         {   name => 'Owner',
diff --git a/t/shredder/03plugin_tickets.t b/t/shredder/03plugin_tickets.t
index 3d742ff..2ee8f2f 100644
--- a/t/shredder/03plugin_tickets.t
+++ b/t/shredder/03plugin_tickets.t
@@ -31,7 +31,6 @@ use_ok('RT::Tickets');
     my $parent = RT::Ticket->new( $RT::SystemUser );
     my ($pid) = $parent->Create( Subject => 'parent', Queue => 1 );
     ok( $pid, "created new ticket" );
-
     my $child = RT::Ticket->new( $RT::SystemUser );
     my ($cid) = $child->Create( Subject => 'child', Queue => 1, MemberOf => $pid );
     ok( $cid, "created new ticket" );
@@ -110,15 +109,17 @@ cmp_deeply( dump_current_and_savepoint('clean'), "current DB equal to savepoint"
 
 { # create parent and child and check functionality of 'apply_query_to_linked' arg
     my $parent = RT::Ticket->new( $RT::SystemUser );
-    my ($pid) = $parent->Create( Subject => 'parent', Queue => 1, Status => 'resolved' );
+    my ($pid) = $parent->Create( Subject => 'parent', Queue => 1 );
     ok( $pid, "created new ticket" );
+    $parent->SetStatus('resolved');
 
     my $child1 = RT::Ticket->new( $RT::SystemUser );
     my ($cid1) = $child1->Create( Subject => 'child', Queue => 1, MemberOf => $pid );
     ok( $cid1, "created new ticket" );
     my $child2 = RT::Ticket->new( $RT::SystemUser );
-    my ($cid2) = $child2->Create( Subject => 'child', Queue => 1, MemberOf => $pid, Status => 'resolved' );
+    my ($cid2) = $child2->Create( Subject => 'child', Queue => 1, MemberOf => $pid);
     ok( $cid2, "created new ticket" );
+    $child2->SetStatus('resolved');
 
     my $plugin = new RT::Shredder::Plugin::Tickets;
     isa_ok($plugin, 'RT::Shredder::Plugin::Tickets');
diff --git a/t/status-schemas/basics.t b/t/status-schemas/basics.t
index b8839c2..62bd26a 100644
--- a/t/status-schemas/basics.t
+++ b/t/status-schemas/basics.t
@@ -23,9 +23,9 @@ diag "check basic API";
     my $schema = $general->status_schema;
     isa_ok($schema, 'RT::StatusSchema');
     is $schema->name, 'default', "it's a default schema";
-    is join(', ', $schema->valid),
-        join(', ', qw(new open stalled resolved rejected deleted)),
-        'this is default set';
+    is join(', ', sort $schema->valid),
+        join(', ', sort qw(new open stalled resolved rejected deleted)),
+        'this is the default set from our config file';
 }
 
 my ($baseurl, $m) = RT::Test->started_ok;
@@ -48,6 +48,8 @@ diag "check status input on create";
         $valid = 0;
         diag("$_ doesn't appear to be a valid status, but it was in the form");
     }
+
+
     ok $valid, 'all statuses in the form are valid';
 }
 
diff --git a/t/status-schemas/dates.t b/t/status-schemas/dates.t
index 146d287..c42a73d 100644
--- a/t/status-schemas/dates.t
+++ b/t/status-schemas/dates.t
@@ -125,8 +125,9 @@ diag "dates on create for delivery schema";
             Status => 'ordered',
         );
         ok $id, 'created a ticket';
-        ok $ticket->StartedObj->Unix <= 0, 'started is not set';
-        ok $ticket->ResolvedObj->Unix <= 0, 'resolved is not set';
+        is $ticket->StartedObj->Unix , 0, 'started is not set';
+        is $ticket->ResolvedObj->Unix, 0, 'resolved is not set';
+
     }
     {
         my $ticket = RT::Ticket->new( $RT::SystemUser );
diff --git a/t/status-schemas/utils.pl b/t/status-schemas/utils.pl
index ec57d1b..7335f04 100644
--- a/t/status-schemas/utils.pl
+++ b/t/status-schemas/utils.pl
@@ -9,7 +9,7 @@ BEGIN {
 $config = <<END;
 Set(\%StatusSchemaMeta,
     default => {
-        initial  => ['new'],
+        initial  => [qw(new open resolved )],
         active   => [qw(open stalled)],
         inactive => [qw(resolved rejected deleted)],
         transitions => {
diff --git a/t/web/command_line.t b/t/web/command_line.t
index 3fc279b..88cb9df 100644
--- a/t/web/command_line.t
+++ b/t/web/command_line.t
@@ -269,7 +269,7 @@ expect_send("show ticket/$ticket_id -f status", 'Verifying change...');
 expect_like(qr/Status: resolved/, 'Verified change');
 # try to set status to an illegal value
 expect_send("edit ticket/$ticket_id set status=quux", 'Changing status to an illegal value...');
-expect_like(qr/illegal value/i, 'Errored out');
+expect_like(qr/isn't a valid status/i, 'Errored out');
 expect_send("show ticket/$ticket_id -f status", 'Verifying lack of change...');
 expect_like(qr/Status: resolved/, 'Verified change');
 

commit b206b50ad8ec1050fe855c20bb4f12736b774e6b
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Fri Aug 13 19:14:37 2010 -0400

    Add a new "initial default status" to status schemas, since not every
    schema will have "new"
    
    Fix status schema tests to take into account that RT now prevents you
    from creating a ticket with a status that's not a valid initial status.
    
    All tests pass here. Pushing

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index db46e0b..6059be4 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1756,6 +1756,7 @@ Set($ApprovalRejectionNotes, 1);
 
     Set( %StatusSchemaMeta,
         default => {
+            default_initial => 'new',
             initial  => ['new', 'open'],
             active   => ['open', 'stalled'],
             inactive => ['resolved', 'rejected', 'deleted'],
diff --git a/lib/RT/StatusSchema.pm b/lib/RT/StatusSchema.pm
index fa0750e..986a341 100644
--- a/lib/RT/StatusSchema.pm
+++ b/lib/RT/StatusSchema.pm
@@ -193,6 +193,19 @@ sub is_initial {
     return scalar grep lc($_) eq $value, $self->valid('initial');
 }
 
+
+=head3 default_initial
+
+Returns the "default" initial status for this schema
+
+=cut
+
+sub default_initial {
+    my $self = shift;
+    return $self->{data}->{default_initial};
+}
+
+
 =head3 active
 
 Returns an array of all active statuses for this schema.
@@ -407,7 +420,7 @@ sub create {
     return (0, loc('Already exist'))
         if $STATUS_SCHEMAS_CACHE{ $name };
 
-    foreach my $method (qw(_set_statuses _set_transitions _set_actions)) {
+    foreach my $method (qw(_set_defaults _set_statuses _set_transitions _set_actions)) {
         my ($status, $msg) = $self->$method( %args, name => $name );
         return ($status, $msg) unless $status;
     }
@@ -438,6 +451,9 @@ sub set_statuses {
     return (1, loc('Updated schema'));
 }
 
+
+
+
 sub set_transitions {
     my $self = shift;
     my %args = @_;
@@ -567,6 +583,21 @@ sub _set_statuses {
     return 1;
 }
 
+
+sub _set_defaults {
+    my $self = shift;
+    my %args = @_;
+
+    $STATUS_SCHEMAS{ $args{'name'} }{$_ } = $args{ $_ }
+        foreach qw(default_initial);
+
+    return 1;
+}
+
+
+
+
+
 sub _set_transitions {
     my $self = shift;
     my %args = @_;
diff --git a/lib/RT/Ticket_Overlay.pm b/lib/RT/Ticket_Overlay.pm
index c5af5df..5ccaa50 100755
--- a/lib/RT/Ticket_Overlay.pm
+++ b/lib/RT/Ticket_Overlay.pm
@@ -259,7 +259,7 @@ sub Create {
         InitialPriority    => undef,
         FinalPriority      => undef,
         Priority           => undef,
-        Status             => 'new',
+        Status             => undef,
         TimeWorked         => "0",
         TimeLeft           => 0,
         TimeEstimated      => 0,
@@ -306,9 +306,15 @@ sub Create {
             $self->loc( "No permission to create tickets in the queue '[_1]'", $QueueObj->Name));
     }
 
+    if ( ! defined $args{'Status'}) {
+        $args{'Status'} = $QueueObj->status_schema->default_initial();
+    }
+
     unless ( $QueueObj->IsValidStatus( $args{'Status'} )
             && $QueueObj->status_schema->is_initial( $args{'Status'} )) {
-        return ( 0, 0, $self->loc('Invalid value for status') );
+        return ( 0, 0,
+            $self->loc("Status '[_1]' isn't a valid status for tickets in this queue.",
+                $self->loc($args{'Status'})));
     }
 
 
@@ -1727,13 +1733,7 @@ sub SetQueue {
     if ( $NewQueueObj->Id == $self->QueueObj->Id ) {
         return ( 0, $self->loc('That is the same value') );
     }
-    unless (
-        $self->CurrentUser->HasRight(
-            Right    => 'CreateTicket',
-            Object => $NewQueueObj
-        )
-      )
-    {
+    unless ( $self->CurrentUser->HasRight( Right    => 'CreateTicket', Object => $NewQueueObj)) {
         return ( 0, $self->loc("You may not create requests in that queue.") );
     }
 
@@ -1749,12 +1749,7 @@ sub SetQueue {
             unless $new_status;
     }
 
-    unless (
-        $self->OwnerObj->HasRight(
-            Right    => 'OwnTicket',
-            Object => $NewQueueObj
-        )
-    ) {
+    unless ( $self->OwnerObj->HasRight( Right    => 'OwnTicket', Object => $NewQueueObj)) {
         my $clone = RT::Ticket->new( $RT::SystemUser );
         $clone->Load( $self->Id );
         unless ( $clone->Id ) {
@@ -1778,7 +1773,7 @@ sub SetQueue {
 
         #If we're changing the status from initial in old to not intial in new,
         # record that we've started
-        if ( $old_schema->is_initial($old_status) && !$new_schema->is_initial($new_status) ) {
+        if ( $old_schema->is_initial($old_status) && !$new_schema->is_initial($new_status)  && $clone->StartedObj->Unix == 0 ) {
             #Set the Started time to "now"
             $clone->_Set(
                 Field             => 'Started',
@@ -3066,19 +3061,12 @@ sub SetStatus {
 
     my $new = $args{'Status'};
     unless ( $schema->is_valid( $new ) ) {
-        return (0,
-            $self->loc("Status '[_1]' isn't a valid status for tickets in this queue.",
-                $self->loc($new)
-        ));
+        return (0, $self->loc("Status '[_1]' isn't a valid status for tickets in this queue.", $self->loc($new)));
     }
 
     my $old = $self->__Value('Status');
     unless ( $schema->is_transition( $old => $new ) ) {
-        return (0,
-            $self->loc("You can't change status from '[_1]' to '[_2]'.",
-                $self->loc($old), $self->loc($new)
-            )
-        );
+        return (0, $self->loc("You can't change status from '[_1]' to '[_2]'.", $self->loc($old), $self->loc($new)));
     }
 
     my $check_right = $schema->check_right( $old => $new );
@@ -3086,17 +3074,18 @@ sub SetStatus {
         return ( 0, $self->loc('Permission Denied') );
     }
 
-    if ( !$args{Force} && $schema->is_inactive( $new )
-        && $self->HasUnresolvedDependencies
-    ) {
+    if ( !$args{Force} && $schema->is_inactive( $new ) && $self->HasUnresolvedDependencies) {
         return (0, $self->loc('That ticket has unresolved dependencies'));
     }
 
     my $now = RT::Date->new( $self->CurrentUser );
     $now->SetToNow();
 
+    my $raw_started = RT::Date->new($RT::SystemUser);
+    $raw_started->Set(Format => 'ISO', Value => $self->__Value('Started'));
+
     #If we're changing the status from new, record that we've started
-    if ( $schema->is_initial($old) && $schema->is_active($new) ) {
+    if ( $schema->is_initial($old) && !$schema->is_initial($new) && !$raw_started->Unix) {
         #Set the Started time to "now"
         $self->_Set(
             Field             => 'Started',
diff --git a/share/html/Ticket/Elements/EditBasics b/share/html/Ticket/Elements/EditBasics
index 5d33858..edbb065 100755
--- a/share/html/Ticket/Elements/EditBasics
+++ b/share/html/Ticket/Elements/EditBasics
@@ -63,6 +63,8 @@ unless ( @fields ) {
                 Name => 'Status',
                 DefaultLabel => loc("[_1] (Unchanged)",loc($TicketObj->Status)),
                 Default => $defaults{'Status'} || undef,
+                TicketObj => $TicketObj,
+                QueueObj => $TicketObj->QueueObj
             },
         },
         {   name => 'Queue',
@@ -71,8 +73,6 @@ unless ( @fields ) {
                 Name => 'Queue',
                 Default => $defaults{'Queue'} || $TicketObj->QueueObj->Id,
                 ShowNullOption => 0,
-                TicketObj => $TicketObj,
-                QueueObj => $TicketObj->QueueObj
             }
         },
         {   name => 'Owner',
diff --git a/t/status-schemas/dates.t b/t/status-schemas/dates.t
index c42a73d..54d9389 100644
--- a/t/status-schemas/dates.t
+++ b/t/status-schemas/dates.t
@@ -131,23 +131,33 @@ diag "dates on create for delivery schema";
     }
     {
         my $ticket = RT::Ticket->new( $RT::SystemUser );
-        my ($id, $msg) = $ticket->Create(
+        my ($id, $txn, $msg) = $ticket->Create(
             Queue => $delivery->id,
             Subject => 'test',
-            Status => 'on way',
         );
         ok $id, 'created a ticket';
-        ok $ticket->StartedObj->Unix > 0, 'started is set';
-        ok $ticket->ResolvedObj->Unix <= 0, 'resolved is not set';
+        diag($msg);
+        is $ticket->Status, 'ordered', "Status is ordered";
+        my ($statusval,$statusmsg) = $ticket->SetStatus('on way');
+        ok($statusval,$statusmsg);
+        ok $ticket->StartedObj->Unix > 0, 'started is set to ' .$ticket->StartedObj->AsString ;
+        is $ticket->ResolvedObj->Unix, 0, 'resolved is not set';
     }
+    exit;
     {
         my $ticket = RT::Ticket->new( $RT::SystemUser );
         my ($id, $msg) = $ticket->Create(
             Queue => $delivery->id,
             Subject => 'test',
-            Status => 'delivered',
         );
         ok $id, 'created a ticket';
+
+        my ($statusval,$statusmsg) = $ticket->SetStatus('on way');
+        ok($statusval,$statusmsg);
+
+        ($statusval,$statusmsg) = $ticket->SetStatus('delivered');
+        ok($statusval,$statusmsg);
+
         ok $ticket->StartedObj->Unix > 0, 'started is set';
         ok $ticket->ResolvedObj->Unix > 0, 'resolved is set';
     }
@@ -155,7 +165,7 @@ diag "dates on create for delivery schema";
     my $test_date = '2008-11-28 12:00:00';
     {
         my $ticket = RT::Ticket->new( $RT::SystemUser );
-        my ($id, $msg) = $ticket->Create(
+        my ($id, $statusmsg) = $ticket->Create(
             Queue => $delivery->id,
             Subject => 'test',
             Status => 'ordered',
@@ -168,27 +178,32 @@ diag "dates on create for delivery schema";
     }
     {
         my $ticket = RT::Ticket->new( $RT::SystemUser );
-        my ($id, $msg) = $ticket->Create(
+        my ($id, $statusmsg) = $ticket->Create(
             Queue => $delivery->id,
             Subject => 'test',
-            Status => 'on way',
+            Status => 'ordered',
             Started => $test_date,
             Resolved => $test_date,
         );
         ok $id, 'created a ticket';
+        my ($statusval,$statusmsg) = $ticket->SetStatus('on way');
+        ok($statusval,$statusmsg);
         is $ticket->StartedObj->ISO, $test_date, 'started is set';
         is $ticket->ResolvedObj->ISO, $test_date, 'resolved is set';
     }
     {
         my $ticket = RT::Ticket->new( $RT::SystemUser );
-        my ($id, $msg) = $ticket->Create(
+        my ($id, $statusmsg) = $ticket->Create(
             Queue => $delivery->id,
             Subject => 'test',
-            Status => 'delivered',
             Started => $test_date,
             Resolved => $test_date,
         );
         ok $id, 'created a ticket';
+        my ($statusval,$statusmsg) = $ticket->SetStatus('on way');
+        ok($statusval,$statusmsg);
+        ($statusval,$statusmsg) = $ticket->SetStatus('delievered');
+        ok($statusval,$statusmsg);
         is $ticket->StartedObj->ISO, $test_date, 'started is set';
         is $ticket->ResolvedObj->ISO, $test_date, 'resolved is set';
     }
diff --git a/t/status-schemas/utils.pl b/t/status-schemas/utils.pl
index 7335f04..c6616b3 100644
--- a/t/status-schemas/utils.pl
+++ b/t/status-schemas/utils.pl
@@ -9,6 +9,7 @@ BEGIN {
 $config = <<END;
 Set(\%StatusSchemaMeta,
     default => {
+        default_initial => 'new',
         initial  => [qw(new open resolved )],
         active   => [qw(open stalled)],
         inactive => [qw(resolved rejected deleted)],
@@ -38,6 +39,7 @@ Set(\%StatusSchemaMeta,
         },
     },
     delivery => {
+        default_initial => 'ordered',
         initial  => ['ordered'],
         active   => ['on way', 'delayed'],
         inactive => ['delivered'],

commit cf46f8ea707f4318e6dd31f067c922a9a7e12c38
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Mon Aug 16 13:31:49 2010 -0400

    finish porting over Quicksearch schema seperation

diff --git a/share/html/Elements/Quicksearch b/share/html/Elements/Quicksearch
index f02079d..559248f 100644
--- a/share/html/Elements/Quicksearch
+++ b/share/html/Elements/Quicksearch
@@ -16,5 +16,5 @@ my $unwanted = $session{'CurrentUser'}->UserObj->Preferences('QuickSearch', {});
 my $comp = $SplitBySchema? '/Elements/QueueSummaryByStatusSchema' : '/Elements/QueueSummaryByStatus';
 </%INIT>
 <%ARGS>
-$SplitBySchema => 0
+$SplitBySchema => 1
 </%ARGS>
diff --git a/share/html/Elements/QuicksearchBySchema b/share/html/Elements/QuicksearchBySchema
deleted file mode 100644
index 16147a6..0000000
--- a/share/html/Elements/QuicksearchBySchema
+++ /dev/null
@@ -1 +0,0 @@
-<& /Elements/Quicksearch, SplitBySchema => 1 &>

commit a5f94dec9ba35c1368140a4341f75968cfd49461
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Mon Aug 16 13:32:08 2010 -0400

    Rewrite status schemas docs and move them to the config file

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 6059be4..8041ed1 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1752,53 +1752,251 @@ Set($ApprovalRejectionNotes, 1);
 
 =head1 Miscellaneous Configuration
 
+=head1 Statuses
+
+=head2
+
+Each schema is a list of possible statuses split into three logic sets:
+B<initial>, B<active> and B<inactive>. Each status in a schema must be
+unique. (Statuses may not be repeated across sets.)  Each set may have
+any number of statuses.
+
+For example:
+
+    default => {
+        default_initial => 'new',
+        initial  => ['new'],
+        active   => ['open', 'stalled'],
+        inactive => ['resolved', 'rejected', 'deleted'],
+        ...
+    },
+
+Because of database constraints, status names can be from 1
+to 10 characters.  Statuses are localized using RT's standard
+internationalization and localization system.
+
+=head3 Status sets
+
+=over 4
+
+=item default_initial
+
+As you'll read below, you can specify a set of statuses which
+RT will treat as valid "initial" statuses for tickets.
+
+If you (or yoour code) doesn't specify a status when creating a
+ticket, RT will use the L<"default_initial"> status.
+
+=item initial
+
+You can define multiple B<initial> statuses for tickets in a
+given status schema. Newly created tickets can only have
+a status that's listed as initial.
+
+A ticket's B<Created> date is set immediately upon ticket creation.
+RT will automatically set its B<Started> date when you change
+a ticket's status from an B<initial> state to an B<active> or B<inactive>
+status.
+
+=item active
+
+B<Active> tickets are "currently in play" - they're things that are
+being worked on and not yet complete.
+
+=item inactive
+
+B<Inactive> tickets are typically in their "final resting state".
+
+While you're free to implement a workflow that ignores that description,
+typically once a ticket enters an inactive state, it will never again
+enter an active state.
+
+RT will automatically set the B<Resolved> state when a ticket's status is
+changed from an B<Initial> or B<Active> status to an B<Inactive> status.
+
+B<deleted> is still a special status and protected by the B<DeleteTicket>
+right. If you don't want to allow ticket deletion at any time simply
+don't include it in your schema.
+
+=back
+
+Statuses in each set are ordered and listed in the UI in the defined
+order.
+
+Changes between statuses are constrained by transition rules,
+as described below.
+
+=head2 Transitions between statuses and UI actions
+
+A B<Transition> - is a change of status from A to B. You should define
+all possible transitions in each schema using the following format:
+
+    default => {
+        ...
+        transitions => {
+            new      => [qw(open resolved rejected deleted)],
+            open     => [qw(stalled resolved rejected deleted)],
+            stalled  => [qw(open)],
+            resolved => [qw(open)],
+            rejected => [qw(open)],
+            deleted  => [qw(open)],
+        },
+        ...
+    },
+
+=head3 Protecting status changes with with rights
+
+A transation or group of transitions can be protected by a specific right.
+
+For example:
+
+    default => {
+        ...
+        rights => {
+            '* -> deleted'  => 'DeleteTicket',
+            '* -> rejected' => 'RejectTicket',
+            '* -> *'        => 'ModifyTicketStatus',
+        },
+        ...
+    },
+
+On the left hand side you can have the following variants:
+
+    '<from> -> <to>'
+    '* -> <to>'
+    '<from> -> *'
+    '* -> *'
+
+Valid transitions are listed in order of priority. If a user attempts
+to to change status a ticket's status from B<new> to B<open> then the
+schema checked for presence of an exact match, then for 'any to B<open>',
+'B<new> to any' and finally 'any to any'.
+
+If you don't define any rights or there is no match for a
+transition RT will use B<DeleteTicket> or B<ModifyTicket> as
+apropriate.
+
+=head3 Labeling and defining actions
+
+Each transition can be named, by default it's named as the end state.
+
+Each action may be annotated with B<Comment>, B<Respond> or B<Hidden>.
+
+For example, you may want your staff to write a reply to the end user
+when they change status from B<new> to B<open>.
+
+Using B<Hide>, it's possible to hide the B<open> -> B<delete> from the ticket's main view, while still allowing it from the basics page and API.
+
+Neither B<Comment> nor B<Respond> are mandatory.
+
+Use the following format to define labels and actions of transitions:
+
+    default => {
+        ...
+        actions => {
+            'new -> open'     => ['Open It', 'Respond'],
+            'new -> resolved' => ['Resolve', 'Comment'],
+            'new -> rejected' => ['Reject',  'Respond'],
+            'new -> deleted'  => ['Delete',  ''],
+
+            'open -> stalled'  => ['Stall',   'Comment'],
+            'open -> resolved' => ['Resolve', 'Comment'],
+            'open -> rejected' => ['Reject',  'Respond'],
+            'open -> deleted'  => ['Delete',  'hide'],
+
+            'stalled -> open'  => ['Open It',  ''],
+            'resolved -> open' => ['Re-open',  'Comment'],
+            'rejected -> open' => ['Re-open',  'Comment'],
+            'deleted -> open'  => ['Undelete', ''],
+        },
+        ...
+    },
+
+=head2 Moving tickets between queues with different schemas
+
+Unless there is a mapping between statuses in two schemas,
+you can not move tickets between queues with these schemas.
+
+    __maps__ => {
+        'from schema -> to schema' => {
+            'status in left schema' => 'status in right schema',
+            ...
+        },
+        ...
+    },
+
+=back
+
 =cut
 
-    Set( %StatusSchemaMeta,
-        default => {
-            default_initial => 'new',
-            initial  => ['new', 'open'],
-            active   => ['open', 'stalled'],
-            inactive => ['resolved', 'rejected', 'deleted'],
-
-            transitions => {
-                # from   => [ to list ],
-                new      => [qw(open stalled resolved rejected deleted)],
-                open     => [qw(new stalled resolved rejected deleted)],
-                stalled  => [qw(new open rejected resolved deleted)],
-                resolved => [qw(new open stalled rejected deleted)],
-                rejected => [qw(new open stalled resolved deleted)],
-                deleted  => [qw(new open stalled rejected resolved)],
-            },
-            rights  => {
-                '* -> deleted'  => 'DeleteTicket',
-                '* -> rejected' => 'ModifyTicket',
-                '* -> *'        => 'ModifyTicket',
-            },
-            actions => {
-                # 'from -> to'    => [action text, Respond/Comment/hide/''],
-                'new -> open'     => ['Open It', 'Respond'],
-                'new -> resolved' => ['Resolve', 'Comment'],
-                'new -> rejected' => ['Reject',  'Respond'],
-                'new -> deleted'  => ['Delete',  ''],
-
-                'open -> stalled'  => ['Stall',   'Comment'],
-                'open -> resolved' => ['Resolve', 'Comment'],
-                'open -> rejected' => ['Reject',  'Respond'],
-                'open -> deleted'  => ['Delete',  'hide'],
-
-                'stalled -> open'  => ['Open It',  ''],
-                'resolved -> open' => ['Re-open',  'Comment'],
-                'rejected -> open' => ['Re-open',  'Comment'],
-                'deleted -> open'  => ['Undelete', ''],
-                'open -> new'      => ['New', 'hide'],
-            },
+Set(%StatusSchemaMeta,
+    default => {
+        default_initial => 'new',
+        initial         => [ 'new', 'open' ],
+        active          => [ 'open', 'stalled' ],
+        inactive        => [ 'resolved', 'rejected', 'deleted' ],
+
+        transitions => {
+
+            # from   => [ to list ],
+            new      => [qw(open stalled resolved rejected deleted)],
+            open     => [qw(new stalled resolved rejected deleted)],
+            stalled  => [qw(new open rejected resolved deleted)],
+            resolved => [qw(new open stalled rejected deleted)],
+            rejected => [qw(new open stalled resolved deleted)],
+            deleted  => [qw(new open stalled rejected resolved)],
+        },
+        rights => {
+            '* -> deleted'  => 'DeleteTicket',
+            '* -> rejected' => 'ModifyTicket',
+            '* -> *'        => 'ModifyTicket',
         },
+        actions => {
+
+            # 'from -> to'    => [action text, Respond/Comment/hide/''],
+            'new -> open'     => [ 'Open It', 'Respond' ],
+            'new -> resolved' => [ 'Resolve', 'Comment' ],
+            'new -> rejected' => [ 'Reject',  'Respond' ],
+            'new -> deleted'  => [ 'Delete',  '' ],
+
+            'open -> stalled'  => [ 'Stall',   'Comment' ],
+            'open -> resolved' => [ 'Resolve', 'Comment' ],
+            'open -> rejected' => [ 'Reject',  'Respond' ],
+            'open -> deleted'  => [ 'Delete',  'hide' ],
+
+            'stalled -> open'  => [ 'Open It',  '' ],
+            'resolved -> open' => [ 'Re-open',  'Comment' ],
+            'rejected -> open' => [ 'Re-open',  'Comment' ],
+            'deleted -> open'  => [ 'Undelete', '' ],
+            'open -> new'      => [ 'New',      'hide' ],
+        },
+    },
+);
+
+
+=item %StatusSchemas
+
+StatusSchemas map schemas to individual queues.
+
+If there is no record for a given queue then RT uses the
+B<default> schema.
+
+For example:
+
+    Set( %StatusSchemas,
+        General => 'some_schema',
+        'Another Queue' => 'another schema',
     );
 
+=cut
+
 
+Set( %StatusSchemas,
+     General => 'default',
+);
 
 
+=head1 Ticket Relationships
 
 =item C<$LinkTransactionsRun1Scrip>
 
@@ -1820,6 +2018,8 @@ them.
 
 Set($StrictLinkACL, 1);
 
+=head1 Sending messages
+
 =item C<$PreviewScripMessages>
 
 Set C<$PreviewScripMessages> to 1 if the scrips preview on the ticket
diff --git a/lib/RT/StatusSchemas.pm b/lib/RT/StatusSchemas.pm
index 76af64f..400d6d9 100644
--- a/lib/RT/StatusSchemas.pm
+++ b/lib/RT/StatusSchemas.pm
@@ -3,297 +3,6 @@ use warnings;
 
 package RT::StatusSchemas;
 
-our $VERSION = '0.02';
-
-=head1 NAME
-
-RT::Estension::StatusSchemas - define different set of statuses for queues in RT
-
-=head1 SYNOPSIS
-
-    # schema close to RT's default, but with some nice
-    # additions
-    Set( %StatusSchemaMeta,
-        default => {
-            initial  => ['new'],
-            active   => ['open', 'stalled'],
-            inactive => ['resolved', 'rejected', 'deleted'],
-
-            transitions => {
-                # from   => [ to list ],
-                new      => [qw(open resolved rejected deleted)],
-                open     => [qw(stalled resolved rejected deleted)],
-                stalled  => [qw(open)],
-                resolved => [qw(open)],
-                rejected => [qw(open)],
-                deleted  => [qw(open)],
-            },
-            rights  => {
-                '* -> deleted'  => 'DeleteTicket',
-                '* -> rejected' => 'RejectTicket',
-                '* -> *'        => 'ModifyTicketStatus',
-            },
-            actions => {
-                # 'from -> to'    => [action text, Respond/Comment/hide/''],
-                'new -> open'     => ['Open It', 'Respond'],
-                'new -> resolved' => ['Resolve', 'Comment'],
-                'new -> rejected' => ['Reject',  'Respond'],
-                'new -> deleted'  => ['Delete',  ''],
-
-                'open -> stalled'  => ['Stall',   'Comment'],
-                'open -> resolved' => ['Resolve', 'Comment'],
-                'open -> rejected' => ['Reject',  'Respond'],
-                'open -> deleted'  => ['Delete',  'hide'],
-
-                'stalled -> open'  => ['Open It',  ''],
-                'resolved -> open' => ['Re-open',  'Comment'],
-                'rejected -> open' => ['Re-open',  'Comment'],
-                'deleted -> open'  => ['Undelete', ''],
-            },
-        },
-    );
-
-=head1 DESCRIPTION
-
-By default RT has one set of statuses for all queues with this extension you can
-define multiple schemas with different statuses and other interesting things.
-
-=head1 CONFIGURATION
-
-=head2 Basics
-
-This extension is configured in the RT config file using several options:
-
-=over 4
-
-=item %StatusSchemas - use to define status schemas for queues, if there is no
-record for some queue then 'default' is used. For example:
-
-    Set( %StatusSchemas,
-        General => 'some_schema',
-        'Another Queue' => 'another schema',
-    );
-
-=item %StatusSchemaMeta - use to describe status schemas. Below you can read more
-about format of this option, but here is basic format:
-
-    Set( %StatusSchemaMeta,
-        default => {
-            ... description of default schema ...
-        },
-        'another schema' => {
-            ... description of another schema ...
-        },
-    );
-
-=back
-
-=head2 Statuses
-
-Each schema is a list of statues splitted into three logic sets:
-initial, active and inactive (read below). All statuses in a schema
-must be unique. Each set may have any number of statuses.
-
-For example:
-
-    default => {
-        initial  => ['new'],
-        active   => ['open', 'stalled'],
-        inactive => ['resolved', 'rejected', 'deleted'],
-        ...
-    },
-
-Note that size of status field in the DB is limitted to 10 ASCII
-characters. You can increase size in the DB, but shorter statuses
-are usually better for UI and DB performance. Also ASCII may looks
-contradictionary with multi-language interface, but you can translate
-statuses and other things using po files.
-
-=head3 Status sets
-
-Unlike RT 3.8 this extension adds 'intial' set in addition to 'active'
-and 'inactive' sets.
-
-=over 4
-
-=item intial
-
-This set is new for RT and covers one thing that has not been
-covered in RT 3.8 and earlier. First time you change a status
-from 'new' to another in 3.8 and earlier Started date is set
-to now. Status 'new' is hardcoded in RT 3.8 for this purpose.
-
-With this extension you can define multiple intial statuses and
-Started date is only set when you change from one of initial
-statuses to status from active or inactive set.
-
-This allow you to get better statistics over dates. For example
-you may have initial statuses 'new' and 'negotiation' and active
-status 'processing'. Created date is set whenever ticket was created
-and started date is set once status changed to 'processing'.
-
-As well, you may have this set empty when you're sure all tickets
-with this status schema are active when created.
-
-Started date is set to now on create if ticket is created with
-not initial status and started date is not defined.
-
-=item active
-
-Active set is something well know as active statuses from RT 3.8
-except that it's splitted into two sets: initial and active.
-
-=item inactive
-
-Inactive statuses haven't been changed much from implementation
-in RT 3.8.
-
-Resolved date is set to now when status is changed from any
-intial or active status to inactive. As well, on create if status
-of new ticket is inactive and resolved date is not defined.
-
-'deleted' is still a special status and protected by 'DeleteTicket'
-right, you have to add it to set manually or avoid if you don't
-want tickets to be deleted.
-
-=back
-
-Statuses in each set are ordered and listed in the UI in the defined
-order. It worth to mention that order of statuses may influence
-behavior a little when it's ambiguose which status to choose.
-
-Changes between statuses are constrolled by possible transitions
-described below.
-
-=head2 Allowing transitions, protecting with rights, labeling them and defining actions
-
-Transition - is a change of status from A to B. You should define
-all possible transitions in each schema using the following format:
-
-    default => {
-        ...
-        transitions => {
-            new      => [qw(open resolved rejected deleted)],
-            open     => [qw(stalled resolved rejected deleted)],
-            stalled  => [qw(open)],
-            resolved => [qw(open)],
-            rejected => [qw(open)],
-            deleted  => [qw(open)],
-        },
-        ...
-    },
-
-=head3 Protecting with rights
-
-A transation or group of transitions can be protected by a right,
-for example:
-
-    default => {
-        ...
-        rights => {
-            '* -> deleted'  => 'DeleteTicket',
-            '* -> rejected' => 'RejectTicket',
-            '* -> *'        => 'ModifyTicketStatus',
-        },
-        ...
-    },
-
-On the left hand side you can have the following variants:
-
-    '<from> -> <to>'
-    '* -> <to>'
-    '<from> -> *'
-    '* -> *'
-
-Variants are listed in order by priority, so if user want
-to change status from X to Y then schema checked for presence
-of exact match, then for presence of 'any to Y', 'X to any' and
-finally 'any to any'.
-
-If you don't define any rights or there is no match for some
-transition then DeleteTicket and ModifyTicket rights are
-checked, like RT does by default.
-
-=head3 Labeling and defining actions
-
-Each transition can be named, by default it's named as B what often
-is not that good. At this point this label is required for the UI
-only where all transitions are listed on ticket's page as possible
-actions on the ticket. Each such action may be acompanied with
-comment, correspond or hidden by default. For example you may want
-your users to write a reply when they change status from new to open.
-Or it's possible to hide open -> delete transition by default from
-ticket's main view, but still make it legal for 'edit basics' page
-or API.
-
-Additional comment or correspond is not mandatory and may be skipped
-by users. By default no action there is no need in action and one-click
-link is showed.
-
-Use the following format to define labels and actions of transitions:
-
-    default => {
-        ...
-        actions => {
-            'new -> open'     => ['Open It', 'Respond'],
-            'new -> resolved' => ['Resolve', 'Comment'],
-            'new -> rejected' => ['Reject',  'Respond'],
-            'new -> deleted'  => ['Delete',  ''],
-
-            'open -> stalled'  => ['Stall',   'Comment'],
-            'open -> resolved' => ['Resolve', 'Comment'],
-            'open -> rejected' => ['Reject',  'Respond'],
-            'open -> deleted'  => ['Delete',  'hide'],
-
-            'stalled -> open'  => ['Open It',  ''],
-            'resolved -> open' => ['Re-open',  'Comment'],
-            'rejected -> open' => ['Re-open',  'Comment'],
-            'deleted -> open'  => ['Undelete', ''],
-        },
-        ...
-    },
-
-=head2 Moving tickets between queues with different schemas
-
-Unless there is some mapping between statuses in schema A and B,
-you can not move tickets between queues with these schemas.
-
-    __maps__ => {
-        'from schema -> to schema' => {
-            'status in left schema' => 'status in right schema',
-            ...
-        },
-        ...
-    },
-
-=head2 Changes in the UI and Scrips
-
-=head3 Quicksearch portlet
-
-This portlet has been rewriten and shows initial and active
-statuses in all schemas. If status is not valid for a queue then
-users will see '-' instead of number.
-
-For setups with many different schemas or schemas that has no
-equal statuses at all it may be better to group queues by status
-schemas. You can achieve this by using QuicksearchBySchema
-portlet. Change HomepageComponents config option first.
-
-=head3 AutoOpen scrip action
-
-Auto open scrip action has been rewritten completly. You can
-read description of its behavior in F<lib/RT/Action/AutoOpen_Vendor.pm>
-using perldoc program.
-
-=head3 StatusChange scrip condition
-
-StatusChange scrip condition has been extended with new format of
-the Argument to better serve systems with multiple status schemas.
-Read description in F<lib/RT/Condition/StatusChange_Vendor.pm>.
-
-=cut
-
-
 require RT::StatusSchema;
 RT::StatusSchema->register_rights;
 

commit feb330cc28007a7e3f5f3388fe272b62b45d3780
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Fri Aug 13 17:46:29 2010 -0400

    grammar cleanup

diff --git a/lib/RT/Ticket_Overlay.pm b/lib/RT/Ticket_Overlay.pm
index 5ccaa50..2a78ea8 100755
--- a/lib/RT/Ticket_Overlay.pm
+++ b/lib/RT/Ticket_Overlay.pm
@@ -405,8 +405,10 @@ sub Create {
         if ( $args{'Owner'}->id ) {
             $Owner = $args{'Owner'};
         } else {
-            $RT::Logger->error('passed not loaded owner object');
-            push @non_fatal_errors, $self->loc("Invalid owner object");
+            $RT::Logger->error('Passed an empty RT::User for owner');
+            push @non_fatal_errors,
+                $self->loc("Owner could not be set.") . " ".
+            $self->loc("Invalid value for [_1]",loc('owner'));
             $Owner = undef;
         }
     }

commit bf06ed64fa0a47f898e27bc3a0dd0f277e2ed2ff
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Fri Aug 13 17:42:34 2010 -0400

    warnings avoidance / cleanup

diff --git a/lib/RT/Rule.pm b/lib/RT/Rule.pm
index 43bf402..14fc9d6 100644
--- a/lib/RT/Rule.pm
+++ b/lib/RT/Rule.pm
@@ -56,7 +56,12 @@ use constant _Queue => undef;
 
 sub Prepare {
     my $self = shift;
-    return (0) if $self->_Queue && $self->TicketObj->QueueObj->Name ne $self->_Queue;
+    if (   $self->_Queue
+        && $self->TicketObj->QueueObj->Name
+        && ( $self->TicketObj->QueueObj->Name ne $self->_Queue ) )
+    {
+        return (0);
+    }
     return 1;
 }
 
diff --git a/lib/RT/Ticket_Overlay.pm b/lib/RT/Ticket_Overlay.pm
index 2a78ea8..560d4ba 100755
--- a/lib/RT/Ticket_Overlay.pm
+++ b/lib/RT/Ticket_Overlay.pm
@@ -417,8 +417,9 @@ sub Create {
     elsif ( $args{'Owner'} ) {
         $Owner = RT::User->new( $self->CurrentUser );
         $Owner->Load( $args{'Owner'} );
-        $Owner->LoadByEmail( $args{'Owner'} )
-            unless $Owner->Id;
+        if (!$Owner->id) {
+            $Owner->LoadByEmail( $args{'Owner'} )
+        }
         unless ( $Owner->Id ) {
             push @non_fatal_errors,
                 $self->loc("Owner could not be set.") . " "
@@ -3315,7 +3316,7 @@ sub _ApplyTransactionBatch {
     my $batch = $self->TransactionBatch;
 
     my %seen;
-    my $types = join ',', grep !$seen{$_}++, grep defined, map $_->Type, grep defined, @{$batch};
+    my $types = join ',', grep !$seen{$_}++, grep defined, map $_->__Value('Type'), grep defined, @{$batch};
 
     require RT::Scrips;
     RT::Scrips->new($RT::SystemUser)->Apply(

commit 8fb43d94fbe32ca1ecd6fa3e7a73c4616374a1b8
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Fri Aug 13 17:42:50 2010 -0400

    Warnings avoidance

diff --git a/share/html/REST/1.0/Forms/ticket/default b/share/html/REST/1.0/Forms/ticket/default
index 0d403b2..4ceb463 100755
--- a/share/html/REST/1.0/Forms/ticket/default
+++ b/share/html/REST/1.0/Forms/ticket/default
@@ -280,7 +280,7 @@ else {
             $key = $simple{$key};
             $set = "Set$key";
 
-            next if (($val eq $ticket->$key)|| ($ticket->$key =~ /^\d+$/ && $val == $ticket->$key));
+            next if (($val eq $ticket->$key)|| ($ticket->$key =~ /^\d+$/ && $val =~ /^\d+$/ && $val == $ticket->$key));
             ($n, $s) = $ticket->$set("$val");
         }
         elsif (exists $dates{$key}) {

commit 485ce304c45004ee118ad1c4c40751dc539c91de
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Fri Aug 13 17:46:47 2010 -0400

    Warning avoidance

diff --git a/share/html/Elements/PageLayout b/share/html/Elements/PageLayout
index e31e27f..a3e5c03 100755
--- a/share/html/Elements/PageLayout
+++ b/share/html/Elements/PageLayout
@@ -155,7 +155,7 @@
     <li<% $class->{li} ? qq[ class="$class->{li}"] : ''|n %>><% $count > 1 && !$postsep && qq[<span class="bullet">&#183; </span>]|n%>
 %         if ($type->{"$action"}->{'html'}) {
       <% $type->{"$action"}->{'html'} | n %>
-%         } else {
+%         } elsif ($type->{$action}->{path})  {
       <a href="<%RT->Config->Get('WebPath')%>/<%$type->{$action}->{'path'}%>"<% $type->{$action}->{class} && ' class="'.$type->{$action}->{class}.'"' |n %><% $type->{$action}->{id} && ' id="'.$type->{$action}->{id}.'"' |n %>><%$type->{$action}->{'title'}%></a>
 %         }
     </li>

commit e00834b0ef876b466bafab3f68af4bcb05f3526b
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Mon Aug 16 14:12:33 2010 -0400

    Doc updates for transitions

diff --git a/lib/RT/Action/AutoOpen.pm b/lib/RT/Action/AutoOpen.pm
index 2d5c030..440b6e2 100755
--- a/lib/RT/Action/AutoOpen.pm
+++ b/lib/RT/Action/AutoOpen.pm
@@ -55,9 +55,7 @@ use base qw(RT::Action);
 
 =head1 DESCRIPTION
 
-AutoOpen in RT 3.8 sets status of a ticket to 'open' without considering
-custom statuses and possibility that there is no 'open' status in the system.
-This extension overrides behavior of the action.
+This action automatically moves a ticket to an active status.
 
 Status is not changed if there is no active statuses in the schema.
 
@@ -66,15 +64,17 @@ schema. For example if ticket's status is 'processing' and active statuses are
 'processing', 'on hold' and 'waiting' then status is not changed, but for ticket
 with status 'on hold' other rules are checked.
 
-Status is not changed if it's initial and creator of the current transaction
-is one of requestors.
+Status is not changed if it's in the list of C<initial> statuses for the
+current scheme and creator of the current transaction
+is one of the ticket's requestors.
 
 Status is not changed if message's head has field C<RT-Control> with C<no-autoopen>
 substring.
 
-Status is set to the first possible active status. It means that if ticket's
-status is X then RT finds all possible transitions from this status and selects
-first active status in the list.
+Status is set to the first possible active status. If the ticket's
+status is C<Stalled> then RT finds all possible transitions from C<Stalled>
+status and selects first one that results in the ticket having an
+active status.
 
 =cut
 

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


More information about the Rt-commit mailing list