self-service ticket priority and edit subject, #13199
authormark <mark>
Mon, 27 Jun 2011 07:11:01 +0000 (07:11 +0000)
committermark <mark>
Mon, 27 Jun 2011 07:11:01 +0000 (07:11 +0000)
13 files changed:
FS/FS/ClientAPI/MyAccount.pm
FS/FS/Conf.pm
FS/FS/Schema.pm
FS/FS/TicketSystem/RT_External.pm
FS/FS/TicketSystem/RT_Internal.pm
FS/FS/cust_main.pm
fs_selfservice/FS-SelfService/SelfService.pm
fs_selfservice/FS-SelfService/cgi/selfservice.cgi
fs_selfservice/FS-SelfService/cgi/ticket_summary.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/tktview.html
httemplate/config/config-view.cgi
httemplate/edit/cust_main/top_misc.html
httemplate/view/cust_main/tickets.html

index fa0bbb8..a788a5c 100644 (file)
@@ -74,7 +74,7 @@ sub skin_info {
       or die "no agentnum for custnum $custnum";
 
   #} elsif ( $context eq 'agent' ) {
-  } elsif ( $p->{'agentnum'} =~ /^(\d+)$/ ) {
+  } elsif ( defined($p->{'agentnum'}) and $p->{'agentnum'} =~ /^(\d+)$/ ) {
     $agentnum = $1;
   }
 
@@ -97,7 +97,7 @@ sub skin_info {
     $skin_info_cache_agent = {
       'agentnum' => $agentnum,
       ( map { $_ => scalar( $conf->config($_, $agentnum) ) }
-        qw( company_name ) ),
+        qw( company_name date_format ) ),
       ( map { $_ => scalar( $conf->config("selfservice-$_", $agentnum ) ) }
         qw( body_bgcolor box_bgcolor
             text_color link_color vlink_color hlink_color alink_color
@@ -295,6 +295,10 @@ sub access_info {
   $info->{'self_suspend_reason'} = 
       $conf->config('selfservice-self_suspend_reason', $cust_main->agentnum);
 
+  $info->{'edit_ticket_subject'} =
+      $conf->exists('ticket_system-selfservice_edit_subject') && 
+      $cust_main->edit_subject;
+
   return { %$info,
            'custnum'       => $custnum,
            'access_pkgnum' => $session->{'pkgnum'},
@@ -316,7 +320,12 @@ sub customer_info {
   }else{
     $return{'require_address2'} = '';
   }
-  
+
+  if ( $conf->exists('ticket_system') ) {
+    warn "$me customer_info: initializing ticket system\n" if $DEBUG;
+    FS::TicketSystem->init();
+  }
   if ( $custnum ) { #customer record
 
     my $search = { 'custnum' => $custnum };
@@ -1909,13 +1918,13 @@ sub create_ticket {
   );
 
   if ( ref($err_or_ticket) ) {
-    warn "$me create_ticket: sucessful: ". $err_or_ticket->id. "\n"
+    warn "$me create_ticket: successful: ". $err_or_ticket->id. "\n"
       if $DEBUG;
     return { 'error'     => '',
              'ticket_id' => $err_or_ticket->id,
            };
   } else {
-    warn "$me create_ticket: unsucessful: $err_or_ticket\n"
+    warn "$me create_ticket: unsuccessful: $err_or_ticket\n"
       if $DEBUG;
     return { 'error' => $err_or_ticket };
   }
@@ -2009,62 +2018,135 @@ sub get_ticket {
 
   warn "$me get_ticket: initializing ticket system\n" if $DEBUG;
   FS::TicketSystem->init();
+  return { 'error' => 'get_ticket configuration error' }
+    if $FS::TicketSystem::system ne 'RT_Internal';
+
+  # check existence and ownership as part of this
+  warn "$me get_ticket: fetching ticket\n" if $DEBUG;
+  my $rt_session = FS::TicketSystem->session('');
+  my $Ticket = FS::TicketSystem->get_ticket_object(
+    $rt_session, 
+    ticket_id => $p->{'ticket_id'},
+    custnum => $custnum
+  );
+  return { 'error' => 'ticket not found' } if !$Ticket;
+
+  if ( length( $p->{'subject'} || '' ) ) {
+    # subject change
+    if ( $p->{'subject'} ne $Ticket->Subject ) {
+      my ($val, $msg) = $Ticket->SetSubject($p->{'subject'});
+      return { 'error' => "unable to set subject: $msg" } if !$val;
+    }
+  }
 
   if(length($p->{'reply'})) {
-# currently this allows anyone to correspond on any ticket as fs_selfservice
-# probably bad...
-      my @err_or_res = FS::TicketSystem->correspond_ticket(
-       '', #create RT session based on FS CurrentUser (fs_selfservice)
-       'ticket_id' => $p->{'ticket_id'},
-       'content' => $p->{'reply'},
-      );
-    
+    my @err_or_res = FS::TicketSystem->correspond_ticket(
+      $rt_session,
+      'ticket_id' => $p->{'ticket_id'},
+      'content' => $p->{'reply'},
+    );
+
     return { 'error' => 'unable to reply to ticket' } 
-       unless ( $err_or_res[0] != 0 && defined $err_or_res[2] );
+    unless ( $err_or_res[0] != 0 && defined $err_or_res[2] );
   }
 
-  warn "$me get_ticket: getting ticket\n" if $DEBUG;
+  warn "$me get_ticket: getting ticket history\n" if $DEBUG;
   my $err_or_ticket = FS::TicketSystem->get_ticket(
-    '', #create RT session based on FS CurrentUser (fs_selfservice)
+    $rt_session,
     'ticket_id' => $p->{'ticket_id'},
   );
 
-  if ( ref($err_or_ticket) ) {
+  if ( !ref($err_or_ticket) ) { # there is no way this should ever happen
+    warn "$me get_ticket: unsuccessful: $err_or_ticket\n"
+      if $DEBUG;
+    return { 'error' => $err_or_ticket };
+  }
 
-# since we're bypassing the RT security/permissions model by always using
-# fs_selfservice as the RT user (as opposed to a requestor, which we
-# can't do since we want all tickets linked to a cust), we check below whether
-# the requested ticket was actually linked to this customer
-    my @custs = @{$err_or_ticket->{'custs'}};
-    my @txns = @{$err_or_ticket->{'txns'}};
-    my @filtered_txns;
+  my @custs = @{$err_or_ticket->{'custs'}};
+  my @txns = @{$err_or_ticket->{'txns'}};
+  my @filtered_txns;
 
-    return { 'error' => 'no customer' } unless ( $custnum && scalar(@custs) );
+  # superseded by check in get_ticket_object
+  #return { 'error' => 'invalid ticket requested' } 
+  #unless grep($_ eq $custnum, @custs);
 
-    return { 'error' => 'invalid ticket requested' } 
-       unless grep($_ eq $custnum, @custs);
+  foreach my $txn ( @txns ) {
+    push @filtered_txns, $txn 
+    if ($txn->{'type'} eq 'EmailRecord' 
+      || $txn->{'type'} eq 'Correspond'
+      || $txn->{'type'} eq 'Create');
+  }
 
-    foreach my $txn ( @txns ) {
-       push @filtered_txns, $txn 
-           if ($txn->{'type'} eq 'EmailRecord' 
-               || $txn->{'type'} eq 'Correspond'
-               || $txn->{'type'} eq 'Create');
+  warn "$me get_ticket: successful: \n"
+  if $DEBUG;
+  return { 'error'     => '',
+    'transactions' => \@filtered_txns,
+    'ticket_fields' => $err_or_ticket->{'fields'},
+    'ticket_id' => $p->{'ticket_id'},
+  };
+}
+
+sub adjust_ticket_priority {
+  my $p = shift;
+  my($context, $session, $custnum) = _custoragent_session_custnum($p);
+  return { 'error' => $session } if $context eq 'error';
+
+  warn "$me adjust_ticket_priority: initializing ticket system\n" if $DEBUG;
+  FS::TicketSystem->init;
+  my $ss_priority = FS::TicketSystem->selfservice_priority;
+
+  return { 'error' => 'adjust_ticket_priority configuration error' }
+    if $FS::TicketSystem::system ne 'RT_Internal'
+      or !$ss_priority;
+
+  my $values = $p->{'values'}; #hashref, id => priority value
+  my %ticket_error;
+
+  foreach my $id (keys %$values) {
+    warn "$me adjust_ticket_priority: fetching ticket $id\n" if $DEBUG;
+    my $Ticket = FS::TicketSystem->get_ticket_object('',
+      'ticket_id' => $id,
+      'custnum'   => $custnum,
+    );
+    if ( !$Ticket ) {
+      $ticket_error{$id} = 'ticket not found';
+      next;
+    }
+    
+  # RT API stuff--would we gain anything by wrapping this in FS::TicketSystem?
+  # We're not going to implement it for RT_External.
+    my $old_value = $Ticket->FirstCustomFieldValue($ss_priority);
+    my $new_value = $values->{$id};
+    next if $old_value eq $new_value;
+
+    warn "$me adjust_ticket_priority: updating ticket $id\n" if $DEBUG;
+
+    # AddCustomFieldValue works fine (replacing any existing value) if it's 
+    # a single-valued custom field, which it should be.  If it's not, you're 
+    # doing something wrong.
+    my ($val, $msg);
+    if ( length($new_value) ) {
+      ($val, $msg) = $Ticket->AddCustomFieldValue( 
+        Field => $ss_priority,
+        Value => $new_value,
+      );
+    }
+    else {
+      ($val, $msg) = $Ticket->DeleteCustomFieldValue(
+        Field => $ss_priority,
+        Value => $old_value,
+      );
     }
 
-    warn "$me get_ticket: sucessful: \n"
-      if $DEBUG;
-    return { 'error'     => '',
-             'transactions' => \@filtered_txns,
-            'ticket_id' => $p->{'ticket_id'},
-           };
-  } else {
-    warn "$me create_ticket: unsucessful: $err_or_ticket\n"
-      if $DEBUG;
-    return { 'error' => $err_or_ticket };
+    $ticket_error{$id} = $msg if !$val;
+    warn "$me adjust_ticket_priority: $id: $msg\n" if $DEBUG and !$val;
   }
+  return { 'error' => '',
+           'ticket_error' => \%ticket_error,
+           %{ customer_info($p) } # send updated customer info back
+         }
 }
 
-
 #--
 
 sub _custoragent_session_custnum {
index 7d9d6c7..170d884 100644 (file)
@@ -2540,7 +2540,7 @@ and customer address. Include units.',
 
   {
     'key'         => 'ticket_system',
-    'section'     => '',
+    'section'     => 'ticketing',
     'description' => 'Ticketing system integration.  <b>RT_Internal</b> uses the built-in RT ticketing system (see the <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:2.1:Documentation:RT_Installation">integrated ticketing installation instructions</a>).   <b>RT_External</b> accesses an external RT installation in a separate database (local or remote).',
     'type'        => 'select',
     #'select_enum' => [ '', qw(RT_Internal RT_Libs RT_External) ],
@@ -2558,7 +2558,7 @@ and customer address. Include units.',
 
   {
     'key'         => 'ticket_system-default_queueid',
-    'section'     => '',
+    'section'     => 'ticketing',
     'description' => 'Default queue used when creating new customer tickets.',
     'type'        => 'select-sub',
     'options_sub' => sub {
@@ -2584,13 +2584,13 @@ and customer address. Include units.',
   },
   {
     'key'         => 'ticket_system-force_default_queueid',
-    'section'     => '',
+    'section'     => 'ticketing',
     'description' => 'Disallow queue selection when creating new tickets from customer view.',
     'type'        => 'checkbox',
   },
   {
     'key'         => 'ticket_system-selfservice_queueid',
-    'section'     => '',
+    'section'     => 'ticketing',
     'description' => 'Queue used when creating new customer tickets from self-service.  Defautls to ticket_system-default_queueid if not specified.',
     #false laziness w/above
     'type'        => 'select-sub',
@@ -2618,49 +2618,63 @@ and customer address. Include units.',
 
   {
     'key'         => 'ticket_system-requestor',
-    'section'     => '',
+    'section'     => 'ticketing',
     'description' => 'Email address to use as the requestor for new tickets.  If blank, the customer\'s invoicing address(es) will be used.',
     'type'        => 'text',
   },
 
   {
     'key'         => 'ticket_system-priority_reverse',
-    'section'     => '',
+    'section'     => 'ticketing',
     'description' => 'Enable this to consider lower numbered priorities more important.  A bad habit we picked up somewhere.  You probably want to avoid it and use the default.',
     'type'        => 'checkbox',
   },
 
   {
     'key'         => 'ticket_system-custom_priority_field',
-    'section'     => '',
+    'section'     => 'ticketing',
     'description' => 'Custom field from the ticketing system to use as a custom priority classification.',
     'type'        => 'text',
   },
 
   {
     'key'         => 'ticket_system-custom_priority_field-values',
-    'section'     => '',
+    'section'     => 'ticketing',
     'description' => 'Values for the custom field from the ticketing system to break down and sort customer ticket lists.',
     'type'        => 'textarea',
   },
 
   {
     'key'         => 'ticket_system-custom_priority_field_queue',
-    'section'     => '',
+    'section'     => 'ticketing',
     'description' => 'Ticketing system queue in which the custom field specified in ticket_system-custom_priority_field is located.',
     'type'        => 'text',
   },
 
   {
+    'key'         => 'ticket_system-selfservice_priority_field',
+    'section'     => 'ticketing',
+    'description' => 'Custom field from the ticket system to use as a customer-managed priority field.',
+    'type'        => 'text',
+  },
+
+  {
+    'key'         => 'ticket_system-selfservice_edit_subject',
+    'section'     => 'ticketing',
+    'description' => 'Allow customers to edit ticket subjects through selfservice.',
+    'type'        => 'checkbox',
+  },
+
+  {
     'key'         => 'ticket_system-escalation',
-    'section'     => '',
+    'section'     => 'ticketing',
     'description' => 'Enable priority escalation of tickets as part of daily batch processing.',
     'type'        => 'checkbox',
   },
 
   {
     'key'         => 'ticket_system-rt_external_datasrc',
-    'section'     => '',
+    'section'     => 'ticketing',
     'description' => 'With external RT integration, the DBI data source for the external RT installation, for example, <code>DBI:Pg:user=rt_user;password=rt_word;host=rt.example.com;dbname=rt</code>',
     'type'        => 'text',
 
@@ -2668,7 +2682,7 @@ and customer address. Include units.',
 
   {
     'key'         => 'ticket_system-rt_external_url',
-    'section'     => '',
+    'section'     => 'ticketing',
     'description' => 'With external RT integration, the URL for the external RT installation, for example, <code>https://rt.example.com/rt</code>',
     'type'        => 'text',
   },
index bf84f0b..c786176 100644 (file)
@@ -881,6 +881,7 @@ sub tables_hashref {
         'email_csv_cdr', 'char', 'NULL', 1, '', '',
         'accountcode_cdr', 'char', 'NULL', 1, '', '',
         'billday',   'int', 'NULL', '', '', '',
+        'edit_subject', 'char', 'NULL', 1, '', '',
       ],
       'primary_key' => 'custnum',
       'unique' => [ [ 'agentnum', 'agent_custid' ] ],
index 8a8c3ff..f976ac0 100644 (file)
@@ -403,5 +403,9 @@ sub create_ticket {
   return 'create_ticket unimplemented w/external RT (write something w/RT::Client::REST?)';
 }
 
+sub init { } #unimplemented
+
+sub selfservice_priority { '' } #unimplemented
+
 1;
 
index 6ae8881..220b4a0 100644 (file)
@@ -35,7 +35,6 @@ sub baseurl {
 sub access_right {
   my( $self, $session, $right ) = @_;
 
-  #return '' unless $conf->config('ticket_system');
   return '' unless FS::Conf->new->config('ticket_system');
 
   $session = $self->session($session);
@@ -63,41 +62,34 @@ sub session {
   $session;
 }
 
+my $firsttime = 1;
+
 sub init {
   my $self = shift;
+  if ( $firsttime ) {
+
+    # this part only needs to be done once
+    warn "$me init: loading RT libraries\n" if $DEBUG;
+    eval '
+      use lib ( "/opt/rt3/local/lib", "/opt/rt3/lib" );
+      use RT;
+
+      #for web external auth...
+      use RT::Interface::Web;
+    ';
+    die $@ if $@;
+
+    warn "$me init: loading RT config\n" if $DEBUG;
+    {
+      local $SIG{__DIE__};
+      eval 'RT::LoadConfig();';
+    }
+    die $@ if $@;
 
-  warn "$me init: loading RT libraries\n" if $DEBUG;
-  eval '
-    use lib ( "/opt/rt3/local/lib", "/opt/rt3/lib" );
-    use RT;
-    #it looks like the rest are taken care of these days in RT::InitClasses
-    #use RT::Ticket;
-    #use RT::Transactions;
-    #use RT::Users;
-    #use RT::CurrentUser;
-    #use RT::Templates;
-    #use RT::Queues;
-    #use RT::ScripActions;
-    #use RT::ScripConditions;
-    #use RT::Scrips;
-    #use RT::Groups;
-    #use RT::GroupMembers;
-    #use RT::CustomFields;
-    #use RT::CustomFieldValues;
-    #use RT::ObjectCustomFieldValues;
-
-    #for web external auth...
-    use RT::Interface::Web;
-  ';
-  die $@ if $@;
-
-  warn "$me init: loading RT config\n" if $DEBUG;
-  {
-    local $SIG{__DIE__};
-    eval 'RT::LoadConfig();';
+    $firsttime = 0;
   }
-  die $@ if $@;
 
+  # this needs to be done on each fork
   warn "$me init: initializing RT\n" if $DEBUG;
   {
     local $SIG{__DIE__};
@@ -108,6 +100,106 @@ sub init {
   warn "$me init: complete" if $DEBUG;
 }
 
+=item customer_tickets CUSTNUM [ LIMIT ] [ PRIORITYVALUE ]
+
+Replacement for the one in RT_External so that we can access custom fields 
+properly.
+
+=cut
+
+sub _customer_tickets_search {
+  my ( $self, $custnum, $limit, $priority ) = @_;
+
+  $custnum =~ /^\d+$/ or die "invalid custnum: $custnum";
+  $limit =~ /^\d+$/ or die "invalid limit: $limit";
+
+  my $session = $self->session();
+  my $CurrentUser = $session->{CurrentUser} 
+    or die "unable to create an RT session";
+
+  my $Tickets = RT::Tickets->new($CurrentUser);
+
+  my $rtql = "MemberOf = 'freeside://freeside/cust_main/$custnum'";
+
+  if ( defined( $priority ) ) {
+    my $custom_priority = FS::Conf->new->config('ticket_system-custom_priority_field');
+    $rtql .= " AND CF.{$custom_priority} = '$priority'";
+  }
+
+  $rtql .= ' AND ( ' .
+           join(' OR ', map { "Status = '$_'" } $self->statuses) .
+           ' )';
+
+  $Tickets->FromSQL($rtql);
+
+  $Tickets->RowsPerPage($limit);
+
+  return $Tickets;
+}
+
+sub customer_tickets {
+  my $Tickets = _customer_tickets_search(@_);
+
+  my $conf = FS::Conf->new;
+  my $priority_order =
+    $conf->exists('ticket_system-priority_reverse') ? 'ASC' : 'DESC';
+  my $custom_priority = 
+    $conf->config('ticket_system-custom_priority_field') || '';
+
+  my @order_by;
+  my $ss_priority = selfservice_priority();
+  push @order_by, { FIELD => "CF.{$ss_priority}", ORDER => $priority_order }
+    if $ss_priority;
+  push @order_by,
+    { FIELD => 'Priority', ORDER => $priority_order },
+    { FIELD => 'Id',       ORDER => 'DESC' },
+  ;
+
+  $Tickets->OrderByCols(@order_by);
+
+  my @tickets;
+  while ( my $t = $Tickets->Next ) {
+    push @tickets, _ticket_info($t);
+  }
+  return \@tickets;
+}
+
+sub num_customer_tickets {
+  my $Tickets = _customer_tickets_search(@_);
+  return $Tickets->CountAll;
+}
+
+sub _ticket_info {
+  # Takes an RT::Ticket; returns a hashref of the ticket's fields, including 
+  # custom fields.  Also returns custom and selfservice priority values as 
+  # _custom_priority and _selfservice_priority.
+  my $t = shift;
+
+  my $custom_priority = 
+    FS::Conf->new->config('ticket_system-custom_priority_field') || '';
+  my $ss_priority = selfservice_priority();
+
+  my %ticket_info;
+  foreach my $name ( $t->ReadableAttributes ) {
+    # lowercase names, and skip attributes with non-scalar values
+    $ticket_info{lc($name)} = $t->$name if !ref($t->$name);
+  }
+  $ticket_info{'owner'} = $t->OwnerObj->Name;
+  $ticket_info{'queue'} = $t->QueueObj->Name;
+  foreach my $CF ( @{ $t->CustomFields->ItemsArrayRef } ) {
+    my $name = 'CF.{'.$CF->Name.'}';
+    $ticket_info{$name} = $t->CustomFieldValuesAsString($CF->Id);
+  }
+  # make this easy to find
+  if ( $custom_priority ) {
+    $ticket_info{'_custom_priority'} = $ticket_info{"CF.{$custom_priority}"};
+  }
+  if ( $ss_priority ) {
+    $ticket_info{'_selfservice_priority'} = $ticket_info{"CF.{$ss_priority}"};
+  }
+  return \%ticket_info;
+}
+
 =item create_ticket SESSION_HASHREF, OPTION => VALUE ...
 
 Class method.  Creates a ticket.  If there is an error, returns the scalar
@@ -219,8 +311,8 @@ sub create_ticket {
 
 Class method. Retrieves a ticket. If there is an error, returns the scalar
 error. Otherwise, currently returns a slightly tricky data structure containing
-a list of the linked customers and each transaction's content, description, and
-create time.
+the ticket's attributes, a list of the linked customers, each transaction's 
+content, description, and create time.
 
 Accepts the following options:
 
@@ -262,9 +354,50 @@ sub get_ticket {
 
   { txns => [ @txns ],
     custs => [ @custs ],
+    fields => _ticket_info($Ticket),
   };
 }
 
+=item get_ticket_object SESSION_HASHREF, OPTION => VALUE...
+
+Class method.  Retrieve the RT::Ticket object with the specified 
+ticket_id.  If custnum is supplied, will also check that the object 
+is a member of that customer.  If there is no ticket or the custnum 
+check fails, returns nothing.  The meaning of that case is 
+"to this customer, the ticket does not exist".
+
+Options:
+
+=over 4
+
+=item ticket_id
+
+=item custnum
+
+=back
+
+=cut
+
+sub get_ticket_object {
+  my $self = shift;
+  my ($session, %opt) = @_;
+  $session = $self->session(shift);
+  my $Ticket = RT::Ticket->new($session->{CurrentUser});
+  $Ticket->Load($opt{'ticket_id'});
+  return if ( !$Ticket->id );
+  my $custnum = $opt{'custnum'};
+  if ( defined($custnum) && $custnum =~ /^\d+$/ ) {
+    # probably the most efficient way to check ticket ownership
+    my $Link = RT::Link->new($session->{CurrentUser});
+    $Link->LoadByCols( LocalBase => $opt{'ticket_id'},
+                       Type      => 'MemberOf',
+                       Target    => "freeside://freeside/cust_main/$custnum",
+                     );
+    return if ( !$Link->id );
+  }
+  return $Ticket;
+}
+
 
 =item correspond_ticket SESSION_HASHREF, OPTION => VALUE ...
 
@@ -427,5 +560,20 @@ sub _web_external_auth {
 
 }
 
+=item selfservice_priority
+
+Returns the configured self-service priority field.
+
+=cut
+
+my $selfservice_priority;
+
+sub selfservice_priority {
+  return $selfservice_priority ||= do {
+    my $conf = FS::Conf->new;
+    $conf->config('ticket_system-selfservice_priority_field') || '';
+  }
+}
+
 1;
 
index 4a7ad2e..b1f71fd 100644 (file)
@@ -324,6 +324,10 @@ A suggestion to events (see L<FS::part_bill_event">) to delay until this unix ti
 
 Discourage individual CDR printing, empty or `Y'
 
+=item edit_subject
+
+Allow self-service editing of ticket subjects, empty or 'Y'
+
 =back
 
 =head1 METHODS
index ec0329b..0a153be 100644 (file)
@@ -64,6 +64,7 @@ $socket .= '.'.$tag if defined $tag && length($tag);
   'myaccount_passwd'          => 'MyAccount/myaccount_passwd',
   'create_ticket'             => 'MyAccount/create_ticket',
   'get_ticket'                => 'MyAccount/get_ticket',
+  'adjust_ticket_priority'    => 'MyAccount/adjust_ticket_priority',
   'did_report'                => 'MyAccount/did_report',
   'signup_info'               => 'Signup/signup_info',
   'skin_info'                 => 'MyAccount/skin_info',
index 0e8b990..58f2d0f 100644 (file)
@@ -18,6 +18,7 @@ use FS::SelfService qw(
   unprovision_svc change_pkg suspend_pkg domainselector
   list_svcs list_svc_usage list_cdr_usage list_support_usage
   myaccount_passwd list_invoices create_ticket get_ticket did_report
+  adjust_ticket_priority
   mason_comp port_graph
 );
 
@@ -77,6 +78,7 @@ my @actions = ( qw(
   myaccount
   tktcreate
   tktview
+  ticket_priority
   didreport
   invoices
   view_invoice
@@ -276,11 +278,28 @@ sub tktcreate {
 
 sub tktview {
  get_ticket(   'session_id' => $session_id,
-               'ticket_id' => $cgi->param('ticket_id'),
-               'reply' => $cgi->param('reply'),
+               'ticket_id' => ($cgi->param('ticket_id') || ''),
+                'subject'   => ($cgi->param('subject') || ''),
+               'reply'     => ($cgi->param('reply') || ''),
            );
 }
 
+sub ticket_priority {
+  my %values;
+  foreach ( $cgi->param ) {
+    if ( /^ticket(\d+)$/ ) {
+      # a 'ticket1001' param implies the existence of a 'priority1001' param
+      # but if that's empty, we need to send it as empty rather than forget
+      # it.
+      $values{$1} = $cgi->param("priority$1") || '';
+    }
+  }
+  $action = 'myaccount';
+  # this returns an updated customer_info for myaccount
+  adjust_ticket_priority( 'session_id' => $session_id,
+                          'values'     => \%values );
+}
+
 sub customer_order_pkg {
   my $init_data = signup_info( 'customer_session_id' => $session_id );
   return $init_data if ( $init_data->{'error'} );
diff --git a/fs_selfservice/FS-SelfService/cgi/ticket_summary.html b/fs_selfservice/FS-SelfService/cgi/ticket_summary.html
new file mode 100644 (file)
index 0000000..0d1c5e9
--- /dev/null
@@ -0,0 +1,65 @@
+<SCRIPT TYPE="text/javascript" SRC="overlibmws.js"></SCRIPT>
+<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#eeeeee">
+<TR><TH BGCOLOR="#ff6666" COLSPAN=8>Open Tickets</TH></TR>
+<TR>
+<%=
+my $col1 = "ffffff";
+my $col2 = "dddddd";
+my $col = $col1;
+
+my $can_set_priority = 
+  grep { exists($_->{'_selfservice_priority'}) } @tickets;
+if ( $can_set_priority ) {
+  $OUT .= qq!<FORM ACTION="$selfurl" METHOD="POST">! .
+          qq!<INPUT TYPE="hidden" NAME="session" VALUE="$session_id">! .
+          qq!<INPUT TYPE="hidden" NAME="action" VALUE="ticket_priority">!;
+}
+$date_format ||= '%Y-%m-%d';
+my $date_formatter = sub {
+  my $time = Date::Parse::str2time($_[0], 'GMT'); # RT internal dates are GMT
+  # exclude times within 24 hours of zero
+  ($time > 86400) ? Date::Format::time2str($date_format, $time) : ''
+};
+
+my @titles = ('#', qw(Subject Queue Status Created Due));
+push @titles, 'Estimated<BR>Minutes';
+push @titles, 'Priority';
+
+$OUT .= join("\n", map { "<TH VALIGN=\"top\">$_</TH>" } @titles) . '</TR>';
+
+foreach my $ticket ( @tickets ) {
+  my $id = $ticket->{'id'};
+  my $td = qq!<TD BGCOLOR="#$col">!;
+  my $link = qq!<A HREF="${url}tktview;ticket_id=$id">!;
+  $OUT .= '<TR>' . $td . $link . $id . '</A></TD>'.
+$td. $link . $ticket->{'subject'} . '</A></TD>'.
+$td. $ticket->{'queue'} . '</TD>'.
+$td. $ticket->{'status'} . '</TD>'.
+$td. $date_formatter->($ticket->{'created'}) . '</TD>'.
+$td. $date_formatter->($ticket->{'due'}) . '</TD>'.
+qq!<TD BGCOLOR="#$col" ALIGN="right">!. ($ticket->{'timeestimated'} || '') 
+.  '</TD>'.
+qq!<TD BGCOLOR="#$col" ALIGN="right">!;
+  if ( $can_set_priority ) {
+    $OUT .= '<INPUT TYPE="hidden" NAME="ticket'.$id.'" VALUE="1">' .
+            '<INPUT TYPE="text" SIZE=4 NAME="priority'.$id.'"' .
+            'VALUE="'.$ticket->{'_selfservice_priority'}.'"></TD>';
+    if ( exists($ticket_error{$id}) ) {
+      # display error message compactly
+      $OUT .= '<TD><FONT COLOR="#ff0000" onmouseover="'.
+              "return overlib('".$ticket_error{$id}."', AUTOSTATUS, WRAP);" .
+              '" onmouseout="nd();">*</FONT></TD>';
+    }
+  }
+  else {
+    $OUT .= ($ticket->{'content'} || $ticket->{'priority'}) . '</TD>';
+  }
+  $OUT .= '</TR>';
+  $col = $col eq $col1 ? $col2 : $col1;
+} #foreach my $ticket
+if ( $can_set_priority ) {
+  $OUT .= '<TR><TD COLSPAN=8 ALIGN="right">
+<INPUT TYPE="submit" VALUE="Save changes"></TD></TR></FORM>';
+}
+%>
+</TABLE>
index 6f540bc..72634fe 100644 (file)
@@ -22,9 +22,16 @@ else {
 <FORM ACTION="<%=$selfurl%>" METHOD=POST>
     <input type="hidden" name="session" value="<%=$session_id%>">
     <input type="hidden" name="ticket_id" value="<%=$ticket_id%>">
+<%= if ( $edit_ticket_subject ) { $OUT .= '
+    Subject:<BR><input type="text" name="subject" value="' . 
+    $ticket_fields{'subject'} . '" style="width:440px">
+    <BR><BR>';
+}
+%>
     <input type="hidden" name="action" value="tktview">
     Add reply to ticket:
-    <BR><textarea name="reply" cols="60" rows="10"></textarea>
+    <BR>
+    <textarea name="reply" cols="60" rows="10" style="width:440px"></textarea>
     <BR><input type="submit" value="Reply">
 </form> 
 
index 10fcde3..4c90ebb 100644 (file)
@@ -350,7 +350,7 @@ my @config_items = grep { $page_agent ? $_->per_agent : 1 }
 my @deleteable = qw( invoice_latexreturnaddress invoice_htmlreturnaddress );
 my %deleteable = map { $_ => 1 } @deleteable;
 
-my @sections = qw(required billing invoicing notification UI self-service username password session shell BIND telephony );
+my @sections = qw(required billing invoicing notification UI self-service ticketing username password session shell BIND telephony );
 push @sections, '', 'deprecated';
 
 my %section_items = ();
index 575b737..a7545a0 100644 (file)
     &>
 % }
 
+% # permission to edit ticket subjects
+% if ( $conf->exists('ticket_system-selfservice_edit_subject') ) {
+  <TR>
+    <TD ALIGN="right">
+      <INPUT TYPE="checkbox" NAME="edit_subject" VALUE="Y" <% 
+        $cust_main->edit_subject ? 'CHECKED' : '' %>></TD>
+    <TD ALIGN="left"><% mt('Can edit ticket subjects') |h %></TD>
+  </TR>
+% }
+
 </TABLE>
 
 <%init>
index 2175110..0c48d21 100644 (file)
@@ -12,8 +12,7 @@ function updateTicketLink() {
 <A id="CreateTicketLink" HREF="<% $new_link %>"><% mt('Create new ticket') |h %></A>
  <% mt('in queue') |h %> 
 %# fetch list of queues in which the user can create tickets
-% my $session = FS::TicketSystem->session();
-% my %queues = FS::TicketSystem->queues($session, 'CreateTicket');
+% my %queues = FS::TicketSystem->queues('', 'CreateTicket');
 % if( $conf->exists('ticket_system-force_default_queueid') ) {
 <B><% $queues{$new_param{'Queue'}} %></B>
 <INPUT TYPE="hidden" NAME="Queue" VALUE="<% $new_param{'Queue'} %>">
@@ -46,7 +45,12 @@ function updateTicketLink() {
   <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Status') |h %></TH>
   <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Queue') |h %></TH>
   <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Owner') |h %></TH>
+  <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Due') |h %></TH>
+  <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Estimated Time') |h %></TH>
   <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Priority') |h %></TH>
+% if ( $ss_priority ) {
+  <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Customer Priority') |h %></TH>
+% }
 </TR>
 
 % foreach my $ticket ( @tickets ) {
@@ -78,6 +82,14 @@ function updateTicketLink() {
     <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
       <% $ticket->{owner} %>
     </TD>
+
+    <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+      <% $date_formatter->($ticket->{due}) %>
+    </TD>
+
+    <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+      <% $ticket->{timeestimated} %>
+    </TD>
   
     <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
       <% $ticket->{content}
@@ -85,7 +97,13 @@ function updateTicketLink() {
            : $ticket->{priority}
       %>
     </TD>
-  
+
+%   if ( $ss_priority ) {
+    <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+      <% $ticket->{"CF.{$ss_priority}"} %>
+    </TD>
+%   }
+
   </TR>
 
 % } 
@@ -93,6 +111,8 @@ function updateTicketLink() {
 </TABLE>
 
 <%init>
+use Date::Format 'time2str';
+use Date::Parse 'str2time';
 
 my( $conf ) = new FS::Conf;
 my( $cust_main ) = @_;
@@ -102,7 +122,7 @@ my $open_link = FS::TicketSystem->href_customer_tickets($cust_main->custnum);
 my $openlabel = join('/', FS::TicketSystem->statuses );
 
 my $res_link  = FS::TicketSystem->href_customer_tickets(
-                  $cust_main->custnum,
+                  $cust_main->Custnum,
                   { 'statuses' => [ 'resolved' ] }
                 );
 
@@ -111,4 +131,14 @@ my( $new_base, %new_param ) =
 
 my $new_link = FS::TicketSystem->href_new_ticket( $cust_main );
 
+my $ss_priority = FS::TicketSystem->selfservice_priority;
+
+my $format = $conf->config('date_format') || '%Y-%m-%d';
+
+my $date_formatter = sub {
+  my $time = str2time($_[0], 'GMT');
+  # exclude times within 24 hours of zero
+  ($time > 86400) ? time2str($format, $time) : '';
+};
+
 </%init>