TimeWorked-like custom fields, RT#11168
authormark <mark>
Thu, 17 Feb 2011 03:47:50 +0000 (03:47 +0000)
committermark <mark>
Thu, 17 Feb 2011 03:47:50 +0000 (03:47 +0000)
15 files changed:
rt/FREESIDE_MODIFIED
rt/lib/RT/Action/Accumulate.pm [new file with mode: 0644]
rt/lib/RT/Attribute_Overlay.pm
rt/lib/RT/CustomField_Overlay.pm
rt/lib/RT/Interface/Web.pm
rt/lib/RT/Record.pm
rt/lib/RT/Ticket_Overlay.pm
rt/lib/RT/Transaction_Overlay.pm
rt/share/html/Admin/CustomFields/Modify.html
rt/share/html/Admin/Elements/EditCustomFieldUILocation [new file with mode: 0644]
rt/share/html/Elements/EditCustomFieldTimeValue [new file with mode: 0644]
rt/share/html/Elements/ShowCustomFieldTimeValue [new file with mode: 0644]
rt/share/html/Ticket/Display.html
rt/share/html/Ticket/Elements/EditTransactionCustomFields
rt/share/html/Ticket/Update.html

index be645f2..60f9c0b 100644 (file)
@@ -7,13 +7,15 @@ config.layout.in
  etc/schema.Pg
  etc/schema.mysql-4.1
 
+lib/RT/Attribute_Overlay.pm #bugfix
  lib/RT/Config.pm
 lib/RT/CustomField.pm #CheckMandatoryFields
-lib/RT/CustomField_Overlay.pm #customfield date patch
-lib/RT/Interface/Web.pm #customfield date patch
+lib/RT/CustomField_Overlay.pm #customfield date patch #timeworked custom fields
+lib/RT/Interface/Web.pm #customfield date patch #fix transaction custom fields
 lib/RT/Action.pm #create ticket on custom field change
 lib/RT/Condition.pm #create ticket on custom field change
 lib/RT/Scrip_Overlay.pm #create ticket on custom field change
+lib/RT/Action/Accumulate.pm #timeworked custom fields
 lib/RT/Action/CreateTickets.pm #create ticket on custom field change
 lib/RT/Action/EscalatePriority.pm #ticket escalation
 lib/RT/Action/EscalateQueue.pm #ticket escalation
@@ -22,12 +24,12 @@ lib/RT/CustomFieldValues/Queues.pm #ticket escalation
 lib/RT/Condition/CustomFieldChange.pm #create ticket on custom field change
 lib/RT/Interface/Web_Vendor.pm
  lib/RT/Interface/Web/Handler.pm #freeside comp_root for dashboard emails
- lib/RT/Record.pm #and customfield date patch
+ lib/RT/Record.pm #and customfield date patch #fix transaction custom fields
 lib/RT/SavedSearches_Local.pm #saved searches
 lib/RT/SearchBuilder.pm #need DBIx::SearchBuilder >= 1.36 for Pg 8.1+
-lib/RT/Transaction_Overlay.pm
-lib/RT/Tickets_Overlay.pm #customfield date patch #SearchCustomerFields #this-month condition
- lib/RT/Ticket_Overlay.pm
+lib/RT/Transaction_Overlay.pm #fix transaction custom fields
+lib/RT/Tickets_Overlay.pm #customfield date patch #SearchCustomerFields #this-month condition 
+ lib/RT/Ticket_Overlay.pm #fix transaction custom fields
  lib/RT/Users_Overlay.pm
  lib/RT/Groups_Overlay.pm
 lib/RT/Date.pm #this-month condition
@@ -52,7 +54,8 @@ share/html/Ticket/Create.html # queue select dropdown on Ticket/Create
 
  share/html/autohandler #Footer getting appended where unwelcome
  share/html/index.html #option to redirect to ticket display on quick create
- share/html/Admin/CustomFields/Modify.html #CheckMandatoryFields
+ share/html/Admin/CustomFields/Modify.html #CheckMandatoryFields #timeworked custom fields
+share/html/Admin/Elements/EditCustomFieldUILocation #timeworked custom fields
  share/html/Admin/Elements/EditScrip #create ticket on custom field change
  share/html/Admin/Elements/EditScripOptions #create ticket on custom field change
  share/html/Admin/Elements/SelectScripAction #create ticket on custom field change
@@ -60,6 +63,7 @@ share/html/Ticket/Create.html # queue select dropdown on Ticket/Create
  share/html/Admin/Users/Modify.html
  share/html/Elements/CollectionList
 share/html/Elements/EditCustomFieldDate #customfield date patch (NEW)
+share/html/Elements/EditCustomFieldTimeValue #timeworked custom fields
  share/html/Elements/Header
  share/html/Elements/PageLayout
  #html/Elements/QuickCreate
@@ -67,6 +71,7 @@ share/html/Elements/EditCustomFieldDate #customfield date patch (NEW)
 share/html/Elements/RT__SavedSearch/ColumnMap #saved searches
 share/html/Elements/SavedSearches #saved searches
  share/html/Elements/ShowCustomFieldDate #customfield date patch (NEW)
+share/html/Elements/ShowCustomFieldTimeValue #timeworked custom fields
  share/html/Elements/SelectDate
 share/html/Elements/ShowLink_Checklist
  share/html/Elements/ShowUserVerbose
@@ -80,16 +85,18 @@ share/html/Search/Results.tsv #content-type bug fix
  share/html/Search/Elements/BuildFormatString
  share/html/Search/Elements/PickCFs #customfield date patch
 share/html/Ticket/Checklist.html
- share/html/Ticket/Display.html
+ share/html/Ticket/Display.html #timeworked custom fields
 share/html/Ticket/Elements/AddCustomers
  share/html/Ticket/Elements/CheckMandatoryFields
 share/html/Ticket/Elements/EditCustomers
+share/html/Ticket/Elements/EditTransactionCustomFields #timeworked custom fields
 share/html/Ticket/Elements/ShowCustomers
 share/html/Ticket/Elements/ShowMembers_Checklist
  share/html/Ticket/Elements/BulkLinks
  share/html/Ticket/Elements/ShowSummary
  share/html/Ticket/Elements/ShowTransactionAttachments
  share/html/Ticket/Elements/Tabs #saved searches
+share/html/Ticket/Update.html #timeworked custom fields
 share/html/Ticket/ModifyCustomers.html
  html/NoAuth/css/3.5-default/main.css
  html/NoAuth/css/3.5-default/misc.css
diff --git a/rt/lib/RT/Action/Accumulate.pm b/rt/lib/RT/Action/Accumulate.pm
new file mode 100644 (file)
index 0000000..c4ca667
--- /dev/null
@@ -0,0 +1,44 @@
+package RT::Action::Accumulate;
+use base 'RT::Action';
+
+use strict;
+
+=head1 NAME 
+
+RT::Action::Accumulate - Accumulate a running total in a ticket custom field.
+
+This action requires a transaction and ticket custom field with the same name.
+When a transaction is submitted with a numeric value in that field, the field 
+value for the ticket will be incremented by that amount.  Use this to create 
+custom fields that behave like the "TimeWorked" field.
+
+Best used with an "On Update" condition that triggers on any transaction.  The 
+ticket custom field update itself does not a create a transaction.
+
+The argument to this action is the name of the custom field.  They must have 
+the same name, and should be single-valued fields.
+
+=cut
+
+sub Prepare {
+    my $self = shift;
+    my $cfname = $self->Argument or return 0;
+    $self->{'inc_by'} = $self->TransactionObj->FirstCustomFieldValue($cfname);
+    return ( $self->{'inc_by'} =~ /^(\d+)$/ );
+}
+
+sub Commit {
+    my $self = shift;
+    my $cfname = $self->Argument;
+    my $newval = $self->{'inc_by'} + 
+      ($self->TicketObj->FirstCustomFieldValue($cfname) || 0);
+    my ($val) = $self->TicketObj->AddCustomFieldValue(
+      Field => 'Support time',
+      Value => $newval,
+      RecordTransaction => 0,
+    );
+    return $val;
+}
+
+1;
+
index 58b5eb8..1f69d46 100644 (file)
@@ -309,12 +309,9 @@ Deletes the subvalue with the key NAME
 sub DeleteSubValue {
     my $self = shift;
     my $key = shift;
-    my %values = $self->Content();
-    delete $values{$key};
-    $self->SetContent(%values);
-
-    
-
+    my $values = $self->Content();
+    delete $values->{$key};
+    $self->SetContent($values);
 }
 
 
index e2342e9..5e868d1 100644 (file)
@@ -102,6 +102,11 @@ our %FieldTypes = (
         'Select date',                 # loc
         'Select up to [_1] dates',     # loc
     ],
+    TimeValue => [
+        'Enter multiple time values (UNSUPPORTED)',
+        'Enter a time value',
+        'Enter [_1] time values (UNSUPPORTED)',
+    ],
 );
 
 
@@ -261,6 +266,10 @@ sub Create {
         $self->SetBasedOn( $args{'BasedOn'} );
     }
 
+    if ( exists $args{'UILocation'} ) {
+        $self->SetUILocation( $args{'UILocation'} );
+    }
+
     return ($rv, $msg) unless exists $args{'Queue'};
 
     # Compat code -- create a new ObjectCustomField mapping
@@ -835,7 +844,7 @@ Returns an array of all possible composite values for custom fields.
 
 sub TypeComposites {
     my $self = shift;
-    return grep !/(?:[Tt]ext|Combobox|Date)-0/, map { ("$_-1", "$_-0") } $self->Types;
+    return grep !/(?:[Tt]ext|Combobox|Date|TimeValue)-0/, map { ("$_-1", "$_-0") } $self->Types;
 }
 
 =head2 SetLookupType
@@ -1447,4 +1456,21 @@ sub BasedOnObj {
     return $obj;
 }
 
+sub UILocation {
+    my $self = shift;
+    my $tag = $self->FirstAttribute( 'UILocation' );
+    return $tag ? $tag->Content : '';
+}
+
+sub SetUILocation {
+    my $self = shift;
+    my $tag = shift;
+    if ( $tag ) {
+        return $self->SetAttribute( Name => 'UILocation', Content => $tag );
+    }
+    else {
+        return $self->DeleteAttribute('UILocation');
+    }
+}
+
 1;
index 2990f3e..4e4611b 100644 (file)
@@ -1334,13 +1334,22 @@ sub ProcessUpdateMessage {
     my $bcc = $args{ARGSRef}->{'UpdateBcc'};
     my $cc  = $args{ARGSRef}->{'UpdateCc'};
 
+    my %txn_customfields;
+
+    foreach my $key ( keys %{ $args{ARGSRef} } ) {
+      if ( $key =~ /^(?:Object-RT::Transaction--)?CustomField-(\d+)/ ) {
+        $txn_customfields{$key} = $args{ARGSRef}->{$key};
+      }
+    }
+
     my %message_args = (
         CcMessageTo  => $cc,
         BccMessageTo => $bcc,
         Sign         => $args{ARGSRef}->{'Sign'},
         Encrypt      => $args{ARGSRef}->{'Encrypt'},
         MIMEObj      => $Message,
-        TimeTaken    => $args{ARGSRef}->{'UpdateTimeWorked'}
+        TimeTaken    => $args{ARGSRef}->{'UpdateTimeWorked'},
+        CustomFields => \%txn_customfields,
     );
 
     my @temp_squelch;
@@ -1376,14 +1385,17 @@ sub ProcessUpdateMessage {
     }
 
     my @results;
+    # Do the update via the appropriate Ticket method
     if ( $args{ARGSRef}->{'UpdateType'} =~ /^(private|public)$/ ) {
-        my ( $Transaction, $Description, $Object ) = $args{TicketObj}->Comment(%message_args);
+        my ( $Transaction, $Description, $Object ) = 
+            $args{TicketObj}->Comment(%message_args);
         push( @results, $Description );
-        $Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
+        #$Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
     } elsif ( $args{ARGSRef}->{'UpdateType'} eq 'response' ) {
-        my ( $Transaction, $Description, $Object ) = $args{TicketObj}->Correspond(%message_args);
+        my ( $Transaction, $Description, $Object ) = 
+            $args{TicketObj}->Correspond(%message_args);
         push( @results, $Description );
-        $Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
+        #$Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
     } else {
         push( @results,
             loc("Update type was neither correspondence nor comment.") . " " . loc("Update not recorded.") );
@@ -1716,6 +1728,8 @@ sub ProcessTicketCustomFieldUpdates {
             $ARGSRef->{"Object-RT::Ticket-$1"} = delete $ARGSRef->{$arg};
         } elsif ( $arg =~ /^CustomField-(\d+-.*)/ ) {
             $ARGSRef->{"Object-RT::Ticket--$1"} = delete $ARGSRef->{$arg};
+        } elsif ( $arg =~ /^Object-RT::Transaction-(\d*)-CustomField/ ) {
+            delete $ARGSRef->{$arg}; # don't try to update transaction fields
         }
     }
 
index c87626a..ce46a90 100755 (executable)
@@ -1467,6 +1467,7 @@ sub _NewTransaction {
         MIMEObj   => undef,
         ActivateScrips => 1,
         CommitScrips => 1,
+        CustomFields => {},
         @_
     );
 
@@ -1500,6 +1501,7 @@ sub _NewTransaction {
         MIMEObj   => $args{'MIMEObj'},
         ActivateScrips => $args{'ActivateScrips'},
         CommitScrips => $args{'CommitScrips'},
+        CustomFields => $args{'CustomFields'},
     );
 
     # Rationalize the object since we may have done things to it during the caching.
index e4db79a..e1809a3 100644 (file)
@@ -734,7 +734,8 @@ sub Create {
 
         if ( $self->Id && $Trans ) {
 
-            $TransObj->UpdateCustomFields(ARGSRef => \%args);
+          #$TransObj->UpdateCustomFields(ARGSRef => \%args);
+            $TransObj->UpdateCustomFields(%args);
 
             $RT::Logger->info( "Ticket " . $self->Id . " created in queue '" . $QueueObj->Name . "' by " . $self->CurrentUser->Name );
             $ErrStr = $self->loc( "Ticket [_1] created in queue '[_2]'", $self->Id, $QueueObj->Name );
@@ -2222,6 +2223,7 @@ sub _RecordNote {
         NoteType     => 'Correspond',
         TimeTaken    => 0,
         CommitScrips => 1,
+        CustomFields => {},
         @_
     );
 
@@ -2278,6 +2280,7 @@ sub _RecordNote {
              TimeTaken => $args{'TimeTaken'},
              MIMEObj   => $args{'MIMEObj'}, 
              CommitScrips => $args{'CommitScrips'},
+             CustomFields => $args{'CustomFields'},
     );
 
     unless ($Trans) {
index c9e13dd..d4b1ab2 100644 (file)
@@ -110,12 +110,13 @@ sub Create {
         NewValue       => undef,
         MIMEObj        => undef,
         ActivateScrips => 1,
-        CommitScrips => 1,
-       ObjectType => 'RT::Ticket',
-       ObjectId => 0,
-       ReferenceType => undef,
-        OldReference       => undef,
-        NewReference       => undef,
+        CommitScrips   => 1,
+       ObjectType     => 'RT::Ticket',
+       ObjectId       => 0,
+       ReferenceType  => undef,
+        OldReference   => undef,
+        NewReference   => undef,
+        CustomFields   => {},
         @_
     );
 
@@ -130,17 +131,17 @@ sub Create {
 
     #lets create our transaction
     my %params = (
-        Type      => $args{'Type'},
-        Data      => $args{'Data'},
-        Field     => $args{'Field'},
-        OldValue  => $args{'OldValue'},
-        NewValue  => $args{'NewValue'},
-        Created   => $args{'Created'},
-       ObjectType => $args{'ObjectType'},
-       ObjectId => $args{'ObjectId'},
+        Type          => $args{'Type'},
+        Data          => $args{'Data'},
+        Field         => $args{'Field'},
+        OldValue      => $args{'OldValue'},
+        NewValue      => $args{'NewValue'},
+        Created       => $args{'Created'},
+       ObjectType    => $args{'ObjectType'},
+       ObjectId      => $args{'ObjectId'},
        ReferenceType => $args{'ReferenceType'},
-       OldReference => $args{'OldReference'},
-       NewReference => $args{'NewReference'},
+       OldReference  => $args{'OldReference'},
+       NewReference  => $args{'NewReference'},
     );
 
     # Parameters passed in during an import that we probably don't want to touch, otherwise
@@ -158,6 +159,10 @@ sub Create {
         }
     }
 
+    # Set up any custom fields passed at creation.  Has to happen 
+    # before scrips.
+    
+    $self->UpdateCustomFields(%{ $args{'CustomFields'} });
 
     #Provide a way to turn off scrips if we need to
         $RT::Logger->debug('About to think about scrips for transaction #' .$self->Id);
index f53e4d7..5ef3212 100644 (file)
         Default => $CustomFieldObj->LookupType, &>
 </td></tr>
 
+% if ( $CustomFieldObj->Id 
+%     and $CustomFieldObj->LookupType =~ /RT::Transaction/ ) {
+<tr><td class="label"><&|/l&>Display&nbsp;with</&></td>
+<td><& /Admin/Elements/EditCustomFieldUILocation,
+        CustomField => $CustomFieldObj &>
+</td></tr>
+% }
+
 <tr><td class="label"><&|/l&>Validation</&></td>
 <td><& /Widgets/ComboBox,
     Name    => 'Pattern',
@@ -191,6 +199,8 @@ if ( $ARGS{'Update'} && $id ne 'new' ) {
 
     $CustomFieldObj->SetBasedOn( $BasedOn );
 
+    $CustomFieldObj->SetUILocation( $UILocation );
+
     my $paramtag = "CustomField-". $CustomFieldObj->Id ."-Value";
     # Delete any fields that want to be deleted
     foreach my $key ( keys %ARGS ) {
@@ -267,4 +277,5 @@ $ValuesClass => 'RT::CustomFieldValues'
 $LinkValueTo => undef
 $IncludeContentForValue => undef
 $BasedOn => undef
+$UILocation => undef
 </%ARGS>
diff --git a/rt/share/html/Admin/Elements/EditCustomFieldUILocation b/rt/share/html/Admin/Elements/EditCustomFieldUILocation
new file mode 100644 (file)
index 0000000..9cffafa
--- /dev/null
@@ -0,0 +1,66 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%# 
+%# COPYRIGHT:
+%# 
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%#                                          <jesse@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 id="ui-location-class-block">
+<select name="UILocation">
+% foreach my $tag (@tags) {
+<option value="<% $tag %>" <% $tag eq $CustomField->UILocation && 'selected="selected"' %>><% $description{$tag} %></option>
+% }
+</select>
+</div>
+
+<%INIT>
+my @sources;
+my @tags = ( '', 'TimeWorked' );
+my %description = (
+  '' => 'Custom fields',
+  'TimeWorked' => 'Time Worked',
+);
+</%INIT>
+<%ARGS>
+$CustomField => undef
+</%ARGS>
diff --git a/rt/share/html/Elements/EditCustomFieldTimeValue b/rt/share/html/Elements/EditCustomFieldTimeValue
new file mode 100644 (file)
index 0000000..0645545
--- /dev/null
@@ -0,0 +1,16 @@
+% my $name = $NamePrefix . $CustomField->Id . '-Value';
+% if ($Multiple) {
+%   $RT::Logger->error("TimeValue Multiple custom field not supported");
+%   return;
+% }
+<& /Elements/EditTimeValue,
+    Name    => $name,
+    Default => $Default,
+    InUnits => $ARGS{"$name-TimeUnits"} || 'minutes',
+&>
+<%ARGS>
+$CustomField => undef
+$NamePrefix => undef
+$Default => undef
+$Multiple => undef
+</%ARGS>
diff --git a/rt/share/html/Elements/ShowCustomFieldTimeValue b/rt/share/html/Elements/ShowCustomFieldTimeValue
new file mode 100644 (file)
index 0000000..16d26df
--- /dev/null
@@ -0,0 +1,4 @@
+<%$Object->Content|n%> min
+<%ARGS>
+$Object
+</%ARGS>
index bc52941..6fd8b85 100755 (executable)
@@ -155,7 +155,7 @@ if ($ARGS{'id'} eq 'new') {
     push @Actions, ProcessTicketBasics(  ARGSRef => \%ARGS, TicketObj => $TicketObj );
     push @Actions, ProcessTicketLinks(   ARGSRef => \%ARGS, TicketObj => $TicketObj );
     push @Actions, ProcessTicketDates(   ARGSRef => \%ARGS, TicketObj => $TicketObj );
-    push @Actions, ProcessObjectCustomFieldUpdates(ARGSRef => \%ARGS, TicketObj => $TicketObj );
+    push @Actions, ProcessTicketCustomFieldUpdates(ARGSRef => \%ARGS, TicketObj => $TicketObj );
 
     # XXX: we shouldn't block actions here if user has no right to see the ticket,
     # but we should allow him to see actions he has done
index e2f42b2..630f678 100644 (file)
 % if ($CustomFields->Count) {
 % while (my $CF = $CustomFields->Next()) {
 % next unless $CF->CurrentUserHasRight('ModifyCustomField');
+% next unless $CF->UILocation eq $UILocation;
 <tr>
-<td class="label"><% loc($CF->Name) %>:</td>
+<td class="label">
+<% loc($CF->Name) %>:
+</td>
 <td>
 <& /Elements/EditCustomField,
     CustomField => $CF,
     NamePrefix => $NamePrefix
 &>
+% if ( $CF->Type ne 'TimeValue' ) {
 <em><% $CF->FriendlyType %></em>
-%  if (my $msg = $m->notes('InvalidField-' . $CF->Id)) {
+% }
+% if (my $msg = $m->notes('InvalidField-' . $CF->Id)) {
         <br />
         <span class="cfinvalidfield"><% $msg %></span>
-%  }
+% }
 </td>
 </td></tr>
 % }
@@ -83,5 +88,6 @@ $m->callback( CallbackName => 'MassageTransactionCustomFields', CustomFields =>
 $NamePrefix => "Object-RT::Transaction--CustomField-"
 $TicketObj => undef
 $QueueObj => undef
+$UILocation => ''
 </%ARGS>
 
index 50c6f93..62db0d1 100755 (executable)
@@ -65,8 +65,8 @@
 <table width="100%" border="0">
 % $m->callback(CallbackName => 'AfterTableOpens', ARGSRef => \%ARGS, Ticket => $TicketObj);
 
-<tr><td class="label"><&|/l&>Status</&>:</td>
-<td>
+<tr><td valign="baseline" class="label"><&|/l&>Status</&>:</td>
+<td valign="baseline">
 <& /Elements/SelectStatus, Name=>"Status", DefaultLabel => loc("[_1] (Unchanged)", loc($TicketObj->Status)), Default => $ARGS{'Status'} || ($TicketObj->Status eq $DefaultStatus ? undef : $DefaultStatus)&>
 <span class="label"><&|/l&>Owner</&>:</span>
 <& /Elements/SelectOwner,
     DefaultLabel => loc("[_1] (Unchanged)", $m->scomp('/Elements/ShowUser', User => $TicketObj->OwnerObj)),
     Default      => $ARGS{'Owner'}
 &>
-<span class="label"><&|/l&>Worked</&>:</span>
-<& /Elements/EditTimeValue,
+</td>
+<td rowspan=4 valign="top">
+<table style="float:right;">
+<tr>
+<td class="label"><&|/l&>Worked</&>:</td>
+<td><& /Elements/EditTimeValue,
     Name => 'UpdateTimeWorked',
     Default => $ARGS{UpdateTimeWorked}||'',
     InUnits => $ARGS{'UpdateTimeWorked-TimeUnits'}||'minutes',
 &>
 </td></tr>
+<& /Ticket/Elements/EditTransactionCustomFields, 
+    %ARGS,
+    TicketObj   => $TicketObj,
+    UILocation  => 'TimeWorked',
+&>
+</table></td></tr>
 % my $skip;
 % $m->callback( %ARGS, CallbackName => 'BeforeUpdateType', skip => \$skip );
 % if (!$skip) {
 % }
 % $m->callback( %ARGS, CallbackName => 'AfterGnuPG' );
 
-<tr><td class="label" valign="top"><&|/l&>Message</&>:</td><td>
+<tr><td class="label" valign="top"><&|/l&>Message</&>:</td><td colspan=2>
 % $m->callback( %ARGS, CallbackName => 'BeforeMessageBox' );
 % if (exists $ARGS{UpdateContent}) {
 % # preserve QuoteTransaction so we can use it to set up sane references/in/reply to