ticket_system-appointment-custom_field, RT#34237
[freeside.git] / FS / FS / TicketSystem / RT_Internal.pm
index 6ae8881..b70ac53 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);
@@ -51,7 +50,7 @@ sub access_right {
 sub session {
   my( $self, $session ) = @_;
 
-  if ( $session && $session->{'Current_User'} ) {
+  if ( $session && $session->{'CurrentUser'} ) { # does this even work?
     warn "$me session: using existing session and CurrentUser: \n".
          Dumper($session->{'CurrentUser'})
       if $DEBUG;
@@ -63,43 +62,37 @@ 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{__WARN__};
     local $SIG{__DIE__};
     eval 'RT::Init("NoSignalHandlers"=>1);';
   }
@@ -108,6 +101,179 @@ 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
+
+# create an RT::Tickets object for a specified custnum or svcnum
+
+sub _tickets_search {
+  my( $self, $type, $number, $limit, $priority, $status, $queueid ) = @_;
+
+  $type =~ /^Customer|Service$/ or die "invalid type: $type";
+  $number =~ /^\d+$/ or die "invalid custnum/svcnum: $number";
+  $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);
+
+  # "Customer.number" searches tickets linked via cust_svc also
+  my $rtql = "$type.number = $number";
+
+  if ( defined( $priority ) ) {
+    my $custom_priority = FS::Conf->new->config('ticket_system-custom_priority_field');
+    if ( length( $priority ) ) {
+      $rtql .= " AND CF.{$custom_priority} = '$priority'";
+    }
+    else {
+      $rtql .= " AND CF.{$custom_priority} IS NULL";
+    }
+  }
+
+  my @statuses;
+  if ( defined($status) && $status ) {
+    if ( ref($status) ) {
+      if ( ref($status) eq 'HASH' ) {
+        @statuses = grep $status->{$_}, keys %$status;
+      } elsif ( ref($status) eq 'ARRAY' ) {
+        @statuses = @$status;
+      } else {
+        #what should be the failure mode here?  die?  return no tickets?
+        die 'unknown status ref '. ref($status);
+      }
+    } else {
+      @statuses = ( $status );
+    }
+    @statuses = grep /^\w+$/, @statuses; #injection prevention
+  } else {
+    @statuses = $self->statuses;
+  }
+
+  $rtql .= ' AND ( '.
+                      join(' OR ', map { "Status = '$_'" } @statuses).
+               ' ) ';
+
+  $rtql .= " AND Queue = $queueid " if $queueid;
+
+  warn "$me _customer_tickets_search:\n$rtql\n" if $DEBUG;
+  $Tickets->FromSQL($rtql);
+
+  $Tickets->RowsPerPage($limit);
+  warn "\n\n" . $Tickets->BuildSelectQuery . "\n\n" if $DEBUG > 1;
+
+  return $Tickets;
+}
+
+sub href_customer_tickets {
+  my ($self, $custnum) = (shift, shift);
+  if ($custnum =~ /^(\d+)$/) {
+    return $self->href_search_tickets("Customer.number = $custnum", @_);
+  }
+  warn "bad custnum $custnum"; '';
+}
+
+sub href_service_tickets {
+  my ($self, $svcnum) = (shift, shift);
+  if ($svcnum =~ /^(\d+)$/ ) {
+    return $self->href_search_tickets("Service.number = $svcnum", @_);
+  }
+  warn "bad svcnum $svcnum"; '';
+}
+
+sub customer_tickets {
+  my $self = shift;
+  my $Tickets = $self->_tickets_search('Customer', @_);
+
+  my $conf = FS::Conf->new;
+  my $priority_order =
+    $conf->exists('ticket_system-priority_reverse') ? 'ASC' : 'DESC';
+
+  my @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 ( $self, $custnum, $priority ) = @_;
+  $self->_tickets_search('Customer', $custnum, 0, $priority)->CountAll;
+}
+
+sub service_tickets  {
+  my $self = shift;
+  my $Tickets = $self->_tickets_search('Service', @_);
+
+  my $conf = FS::Conf->new;
+  my $priority_order =
+    $conf->exists('ticket_system-priority_reverse') ? 'ASC' : 'DESC';
+
+  my @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 _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{'content'} = $ticket_info{"CF.{$custom_priority}"};
+  }
+  if ( $ss_priority ) {
+    $ticket_info{'_selfservice_priority'} = $ticket_info{"CF.{$ss_priority}"};
+  }
+  my $svcnums = [ 
+    map { $_->Target =~ /cust_svc\/(\d+)/; $1 } 
+        @{ $t->Services->ItemsArrayRef }
+  ];
+  $ticket_info{'svcnums'} = $svcnums;
+
+  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 +385,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 +428,48 @@ 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);
+  # use a small search here so we can check ticket ownership
+  my $query;
+  if ( $opt{'ticket_id'} =~ /^(\d+)$/ ) {
+    $query = "id = $1";
+  } else {
+    return;
+  }
+  if ( $opt{'custnum'} =~ /^(\d+)$/ ) {
+    $query .= " AND Customer.number = $1"; # also checks ownership via services
+  }
+  my $Tickets = RT::Tickets->new($session->{CurrentUser});
+  $Tickets->FromSQL($query);
+  return $Tickets->First;
+}
 
 =item correspond_ticket SESSION_HASHREF, OPTION => VALUE ...
 
@@ -403,7 +608,7 @@ sub _web_external_auth {
          # we failed to successfully create the user. abort abort abort.
           delete $session->{'CurrentUser'};
 
-          die "can't auto-create RT user"; #an error message would be nice :/
+          die "can't auto-create RT user: $msg"; #an error message would be nice :/
           #$m->abort() unless $RT::WebFallbackToInternalAuth;
           #$m->comp( '/Elements/Login', %ARGS,
           #    Error => loc( 'Cannot create user: [_1]', $msg ) );
@@ -427,5 +632,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;