From: Mark Wells Date: Tue, 17 Apr 2012 22:52:14 +0000 (-0700) Subject: link tickets to services, #17067 X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=1c59bba12621e154765a8255534e94a041dfd200 link tickets to services, #17067 --- diff --git a/FS/FS/TicketSystem/RT_External.pm b/FS/FS/TicketSystem/RT_External.pm index f976ac0e3..22d2472ef 100644 --- a/FS/FS/TicketSystem/RT_External.pm +++ b/FS/FS/TicketSystem/RT_External.pm @@ -97,6 +97,11 @@ sub customer_tickets { } +sub service_tickets { + warn "service_tickets not available with RT_External.\n"; + return; +} + sub comments_on_tickets { my ($self, $custnum, $limit, $time ) = @_; $limit ||= 0; @@ -206,7 +211,20 @@ sub statuses { } sub href_customer_tickets { - my( $self, $custnum ) = ( shift, shift ); + my($self, $custnum) = (shift, shift); + if ( $custnum =~ /^(\d+)$/ ) { + return $self->href_search_tickets("MemberOf = 'freeside://freeside/cust_main/$1'"); + } + warn "bad custnum $custnum"; return ''; +} + +sub href_service_tickets { + warn "service_tickets not available with RT_External.\n"; + ''; +} + +sub href_search_tickets { + my( $self, $where ) = ( shift, shift ); my( $priority, @statuses); if ( ref($_[0]) ) { my $opt = shift; @@ -225,8 +243,8 @@ sub href_customer_tickets { #$href .= my $href = "Search/Results.html?Order=ASC&". - "Query= MemberOf = 'freeside://freeside/cust_main/$custnum' ". - #" AND ( Status = 'open' OR Status = 'new' OR Status = 'stalled' )" + "Query= $where" . + #MemberOf = 'freeside://freeside/cust_main/$custnum' ". " AND ( ". join(' OR ', map "Status = '$_'", @statuses ). " ) " ; @@ -274,15 +292,19 @@ sub href_customer_tickets { } sub href_params_new_ticket { - my( $self, $custnum_or_cust_main, $requestors ) = @_; - - my( $custnum, $cust_main ); - if ( ref($custnum_or_cust_main) ) { - $cust_main = $custnum_or_cust_main; - $custnum = $cust_main->custnum; - } else { - $custnum = $custnum_or_cust_main; - $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); + # my( $self, $custnum_or_cust_main, $requestors ) = @_; + # no longer takes $custnum--it must be an object + my ( $self, $object, $requestors ) = @_; + my $cust_main; # for default requestors + if ( $object->isa('FS::cust_main') ) { + $cust_main = $object; + } + elsif ( $object->isa('FS::svc_Common') ) { + $object = $object->cust_svc; + $cust_main = $object->cust_pkg->cust_main if ( $object->cust_pkg ); + } + elsif ( $object->isa('FS::cust_svc') ) { + $cust_main = $object->cust_pkg->cust_main if ( $object->cust_pkg ); } # explicit $requestors > config option > invoicing_list @@ -291,9 +313,12 @@ sub href_params_new_ticket { $requestors = $cust_main->invoicing_list_emailonly_scalar if (!$requestors) and defined($cust_main); + my $subtype = $object->table; + my $pkey = $object->get($object->primary_key); + my %param = ( 'Queue' => ($cust_main->agent->ticketing_queueid || $default_queueid), - 'new-MemberOf'=> "freeside://freeside/cust_main/$custnum", + 'new-MemberOf'=> "freeside://freeside/$subtype/$pkey", 'Requestors' => $requestors, ); diff --git a/FS/FS/TicketSystem/RT_Internal.pm b/FS/FS/TicketSystem/RT_Internal.pm index d96e5f05f..ffa8c7c1c 100644 --- a/FS/FS/TicketSystem/RT_Internal.pm +++ b/FS/FS/TicketSystem/RT_Internal.pm @@ -107,10 +107,13 @@ properly. =cut -sub _customer_tickets_search { - my ( $self, $custnum, $limit, $priority ) = @_; +# create an RT::Tickets object for a specified custnum or svcnum - $custnum =~ /^\d+$/ or die "invalid custnum: $custnum"; +sub _tickets_search { + my ( $self, $type, $number, $limit, $priority ) = @_; + + $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(); @@ -119,7 +122,8 @@ sub _customer_tickets_search { my $Tickets = RT::Tickets->new($CurrentUser); - my $rtql = "MemberOf = 'freeside://freeside/cust_main/$custnum'"; + # "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'); @@ -144,8 +148,25 @@ sub _customer_tickets_search { 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 $Tickets = _customer_tickets_search(@_); + my $self = shift; + my $Tickets = $self->_tickets_search('Customer', @_); my $conf = FS::Conf->new; my $priority_order = @@ -168,8 +189,30 @@ sub customer_tickets { sub num_customer_tickets { my ( $self, $custnum, $priority ) = @_; - my $Tickets = $self->_customer_tickets_search($custnum, 0, $priority); - return $Tickets->CountAll; + $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 { @@ -200,6 +243,12 @@ sub _ticket_info { 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; } diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm index fc6e60594..a52791385 100644 --- a/FS/FS/cust_svc.pm +++ b/FS/FS/cust_svc.pm @@ -756,8 +756,94 @@ sub get_session_history { } +=item tickets + +Returns an array of hashes representing the tickets linked to this service. + +=cut + +sub tickets { + my $self = shift; + + my $conf = FS::Conf->new; + my $num = $conf->config('cust_main-max_tickets') || 10; + my @tickets = (); + + if ( $conf->config('ticket_system') ) { + unless ( $conf->config('ticket_system-custom_priority_field') ) { + + @tickets = @{ FS::TicketSystem->service_tickets($self->svcnum, $num) }; + + } else { + + foreach my $priority ( + $conf->config('ticket_system-custom_priority_field-values'), '' + ) { + last if scalar(@tickets) >= $num; + push @tickets, + @{ FS::TicketSystem->service_tickets( $self->svcnum, + $num - scalar(@tickets), + $priority, + ) + }; + } + } + } + (@tickets); +} + + =back +=head1 SUBROUTINES + +=over 4 + +=item smart_search OPTION => VALUE ... + +Accepts the option I, the string to search for. The string will +be searched for as a username, email address, IP address, MAC address, +phone number, and hardware serial number. Unlike the I on +customers, this always requires an exact match. + +=cut + +# though perhaps it should be fuzzy in some cases? +sub smart_search { + my %opt = @_; + # some false laziness w/ search/cust_svc.html + my $string = $opt{'search'}; + $string =~ s/(^\s+|\s+$)//; #trim leading & trailing whitespace + + my @extra_sql = ' ( '. join(' OR ', + map { my $table = $_; + my $search_sql = "FS::$table"->search_sql($string); + " ( svcdb = '$table' + AND 0 < ( SELECT COUNT(*) FROM $table + WHERE $table.svcnum = cust_svc.svcnum + AND $search_sql + ) + ) "; + } + FS::part_svc->svc_tables + ). ' ) '; + push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null_right' => 'View/link unlinked services' + ); + my $extra_sql = ' WHERE '.join(' AND ', @extra_sql); + #for agentnum + my $addl_from = ' LEFT JOIN cust_pkg USING ( pkgnum )'. + ' LEFT JOIN cust_main USING ( custnum )'. + ' LEFT JOIN part_svc USING ( svcpart )'; + + qsearch({ + 'table' => 'cust_svc', + 'addl_from' => $addl_from, + 'hashref' => {}, + 'extra_sql' => $extra_sql, + }); +} + =head1 BUGS Behaviour of changing the svcpart of cust_svc records is undefined and should diff --git a/FS/FS/svc_phone.pm b/FS/FS/svc_phone.pm index 118748ea2..b395ea605 100644 --- a/FS/FS/svc_phone.pm +++ b/FS/FS/svc_phone.pm @@ -218,13 +218,14 @@ Class method which returns an SQL fragment to search for the given string. sub search_sql { my( $class, $string ) = @_; + my $conf = new FS::Conf; + if ( $conf->exists('svc_phone-allow_alpha_phonenum') ) { $string =~ s/\W//g; } else { $string =~ s/\D//g; } - my $conf = new FS::Conf; my $ccode = ( $conf->exists('default_phone_countrycode') && $conf->config('default_phone_countrycode') ) diff --git a/httemplate/elements/form-create_ticket.html b/httemplate/elements/form-create_ticket.html new file mode 100644 index 000000000..362e82397 --- /dev/null +++ b/httemplate/elements/form-create_ticket.html @@ -0,0 +1,38 @@ +
+ +Tickets +<% mt('Create new ticket') |h %> + <% mt('in queue') |h %> +%# fetch list of queues in which the user can create tickets +% my %queues = FS::TicketSystem->queues('', 'CreateTicket'); +% if( $conf->exists('ticket_system-force_default_queueid') ) { +<% $queues{$new_param{'Queue'}} %> + +% } +% else { + + +% } +
+<%init> +my %opt = @_; +my $conf = new FS::Conf; +my $object = $opt{'object'}; # must be a cust_main, cust_svc, or svc_... +my ($new_base, %new_param) = FS::TicketSystem->href_params_new_ticket($object); +my $new_link = FS::TicketSystem->href_new_ticket($object); + diff --git a/httemplate/elements/table-tickets.html b/httemplate/elements/table-tickets.html new file mode 100644 index 000000000..6d1a45a0d --- /dev/null +++ b/httemplate/elements/table-tickets.html @@ -0,0 +1,159 @@ +<& /elements/form-create_ticket.html, object => $object &> + | +View +<% mt($openlabel) |h %> | +<% mt('resolved') |h %> +
+ +<& /elements/table-grid.html &> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; + + + <% mt('#') |h %> + <% mt('Subject') |h %> + <% mt('Status') |h %> + <% mt('Queue') |h %> + <% mt('Owner') |h %> + <% mt('Due') |h %> + <% mt('Estimated Time') |h %> + <% mt('Priority') |h %> +% if ( $ss_priority ) { + <% mt('Customer Priority') |h %> +% } +% if ( $object->isa('FS::cust_main') ) { + <% mt('Service') |h %> +% } + + +% foreach my $ticket ( @tickets ) { +% my $href = FS::TicketSystem->href_ticket($ticket->{id}); +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } + + + + + ><% $ticket->{id} %> + + + + ><% $ticket->{subject} %> + + + + <% $ticket->{status} %> + + + + <% $ticket->{queue} %> + + + + <% $ticket->{owner} %> + + + + <% $date_formatter->($ticket->{due}) %> + + + + <% $ticket->{timeestimated} %> + + + + <% $ticket->{content} + ? $ticket->{content}.' ('.$ticket->{priority}.')' + : $ticket->{priority} + %> + + +% if ( $ss_priority ) { + + <% $ticket->{"CF.{$ss_priority}"} %> + +% } +% if ( $object->isa('FS::cust_main') ) { + +% foreach (@{ $ticket->{svcnums} }) { +% my $cust_svc = FS::cust_svc->by_key($_) or next; + <% FS::UI::Web::svc_label_link($m, $cust_svc->part_svc, $cust_svc) %> +
+% } +
+% } + + + +% } + + + +<%init> +use Date::Parse qw(str2time); +use Date::Format qw(time2str); + +my %opt = @_; +my $conf = new FS::Conf; + +my $object = $opt{'object'}; +$object = $object->cust_svc if $object->isa('FS::svc_Common'); +my( @tickets ) = $object->tickets; + +my ($openlabel, $open_link, $res_link, $thing); +$openlabel = join('/', FS::TicketSystem->statuses ); + +# not the nicest way to do this--FS::has_tickets_Common? +if ( $object->isa('FS::cust_main') ) { + $thing = 'customer'; + $open_link = FS::TicketSystem->href_customer_tickets($object->custnum); + + $res_link = FS::TicketSystem->href_customer_tickets( + $object->custnum, + { 'statuses' => [ 'resolved' ] } + ); +} +elsif ( $object->isa('FS::cust_svc') ) { + $thing = 'service'; + $open_link = FS::TicketSystem->href_service_tickets($object->svcnum); + + $res_link = FS::TicketSystem->href_service_tickets( + $object->svcnum, + { 'statuses' => [ 'resolved' ] } + ); +} + +my $ss_priority = FS::TicketSystem->selfservice_priority; +if ( $ss_priority ) { + my $dir = $conf->exists('ticket_system-priority_reverse') ? -1 : 1; + use sort 'stable'; + # sort in the following way: + @tickets = sort { + # within a severity level... + ( $a->{'content'} eq $b->{'content'} ) ? ( + # no-priority tickets sort last + ( + ($a->{'_selfservice_priority'} eq '') <=> + ($b->{'_selfservice_priority'} eq '') + ) || + # otherwise obey ticket_system-priority_reverse + ( $dir * + ($b->{'_selfservice_priority'} <=> $a->{'_selfservice_priority'}) + ) + ) : 0; # but don't rearrange between severity levels + } @tickets; +} + +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) : ''; +}; + + diff --git a/httemplate/view/cust_main/tickets.html b/httemplate/view/cust_main/tickets.html index 194e90742..f076fcc92 100644 --- a/httemplate/view/cust_main/tickets.html +++ b/httemplate/view/cust_main/tickets.html @@ -1,164 +1,2 @@ -
- -Tickets -<% mt('Create new ticket') |h %> - <% mt('in queue') |h %> -%# fetch list of queues in which the user can create tickets -% my %queues = FS::TicketSystem->queues('', 'CreateTicket'); -% if( $conf->exists('ticket_system-force_default_queueid') ) { -<% $queues{$new_param{'Queue'}} %> - -% } -% else { - - -% } -
- | -View -<% mt($openlabel) |h %> | -<% mt('resolved') |h %> -
- -<& /elements/table-grid.html &> -% my $bgcolor1 = '#eeeeee'; -% my $bgcolor2 = '#ffffff'; -% my $bgcolor = ''; - - - <% mt('#') |h %> - <% mt('Subject') |h %> - <% mt('Status') |h %> - <% mt('Queue') |h %> - <% mt('Owner') |h %> - <% mt('Due') |h %> - <% mt('Estimated Time') |h %> - <% mt('Priority') |h %> -% if ( $ss_priority ) { - <% mt('Customer Priority') |h %> -% } - - -% foreach my $ticket ( @tickets ) { -% my $href = FS::TicketSystem->href_ticket($ticket->{id}); -% if ( $bgcolor eq $bgcolor1 ) { -% $bgcolor = $bgcolor2; -% } else { -% $bgcolor = $bgcolor1; -% } - - - - - ><% $ticket->{id} %> - - - - ><% $ticket->{subject} %> - - - - <% $ticket->{status} %> - - - - <% $ticket->{queue} %> - - - - <% $ticket->{owner} %> - - - - <% $date_formatter->($ticket->{due}) %> - - - - <% $ticket->{timeestimated} %> - - - - <% $ticket->{content} - ? $ticket->{content}.' ('.$ticket->{priority}.')' - : $ticket->{priority} - %> - - -% if ( $ss_priority ) { - - <% $ticket->{"CF.{$ss_priority}"} %> - -% } - - - -% } - - - -<%init> -use Date::Format 'time2str'; -use Date::Parse 'str2time'; - -my( $conf ) = new FS::Conf; -my( $cust_main ) = @_; -my( @tickets ) = $cust_main->tickets; - -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, - { 'statuses' => [ 'resolved' ] } - ); - -my( $new_base, %new_param ) = - FS::TicketSystem->href_params_new_ticket( $cust_main ); - -my $new_link = FS::TicketSystem->href_new_ticket( $cust_main ); - -my $ss_priority = FS::TicketSystem->selfservice_priority; -if ( $ss_priority ) { - my $dir = $conf->exists('ticket_system-priority_reverse') ? -1 : 1; - use sort 'stable'; - # sort in the following way: - @tickets = sort { - # within a severity level... - ( $a->{'content'} eq $b->{'content'} ) ? ( - # no-priority tickets sort last - ( - ($a->{'_selfservice_priority'} eq '') <=> - ($b->{'_selfservice_priority'} eq '') - ) || - # otherwise obey ticket_system-priority_reverse - ( $dir * - ($b->{'_selfservice_priority'} <=> $a->{'_selfservice_priority'}) - ) - ) : 0; # but don't rearrange between severity levels - } @tickets; -} - -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) : ''; -}; - - +% my $cust_main = shift; +<& /elements/table-tickets.html, object => $cust_main &> diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html index 3130c73a0..04d2b2962 100644 --- a/httemplate/view/elements/svc_Common.html +++ b/httemplate/view/elements/svc_Common.html @@ -114,6 +114,10 @@ function areyousure(href) { % } +% if ( $conf->config('ticket_system') ) { +<& /elements/table-tickets.html, object => $cust_svc &> +% } + <% joblisting({'svcnum'=>$svcnum}, 1) %> <% include('/elements/footer.html') %> diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi index 441e4945d..de97a0796 100755 --- a/httemplate/view/svc_acct.cgi +++ b/httemplate/view/svc_acct.cgi @@ -75,6 +75,10 @@ <& elements/svc_export_settings.html, $svc_acct &> +% if ( $conf->config('ticket_system') ) { +<& /elements/table-tickets.html, object => $cust_svc &> +% } + <% joblisting({'svcnum'=>$svcnum}, 1) %> <& /elements/footer.html &> diff --git a/httemplate/view/svc_domain.cgi b/httemplate/view/svc_domain.cgi index 3938a3406..fcccd74b4 100755 --- a/httemplate/view/svc_domain.cgi +++ b/httemplate/view/svc_domain.cgi @@ -36,6 +36,10 @@ <% include('elements/svc_export_settings.html', $svc_domain) %> +% if ( $conf->config('ticket_system') ) { +<& /elements/table-tickets.html, object => $cust_svc &> +% } + <% joblisting({'svcnum'=>$svcnum}, 1) %> <% include('/elements/footer.html') %> diff --git a/httemplate/view/svc_external.cgi b/httemplate/view/svc_external.cgi index 77679d81c..72e553598 100644 --- a/httemplate/view/svc_external.cgi +++ b/httemplate/view/svc_external.cgi @@ -23,6 +23,11 @@ + +% if ( $conf->config('ticket_system') ) { +<& /elements/table-tickets.html, object => $cust_svc &> +% } +
<% joblisting({'svcnum'=>$svcnum}, 1) %> <% include('/elements/footer.html') %> diff --git a/httemplate/view/svc_forward.cgi b/httemplate/view/svc_forward.cgi index 15b5ae56f..2cb78ebd5 100755 --- a/httemplate/view/svc_forward.cgi +++ b/httemplate/view/svc_forward.cgi @@ -53,6 +53,10 @@ <% include('elements/svc_export_settings.html', $svc_forward) %> +% if ( $conf->config('ticket_system') ) { +<& /elements/table-tickets.html, object => $cust_svc &> +% } + <% joblisting({'svcnum'=>$svcnum}, 1) %> <% include('/elements/footer.html') %> diff --git a/httemplate/view/svc_www.cgi b/httemplate/view/svc_www.cgi index 935d139e9..fbb02a00f 100644 --- a/httemplate/view/svc_www.cgi +++ b/httemplate/view/svc_www.cgi @@ -49,6 +49,10 @@
+% if ( $conf->config('ticket_system') ) { +<& /elements/table-tickets.html, object => $cust_svc &> +% } + <% joblisting({'svcnum'=>$svcnum}, 1) %> <% include('/elements/footer.html') %> diff --git a/rt/lib/RT/Interface/Web_Vendor.pm b/rt/lib/RT/Interface/Web_Vendor.pm index ee8c34b55..ae7f0899a 100644 --- a/rt/lib/RT/Interface/Web_Vendor.pm +++ b/rt/lib/RT/Interface/Web_Vendor.pm @@ -76,12 +76,32 @@ sub ProcessTicketCustomers { ### ### + #find new services + ### + + my @svcnums = map { /^Ticket-AddService-(\d+)$/; $1 } + grep { /^Ticket-AddService-(\d+)$/ && $ARGSRef->{$_} } + keys %$ARGSRef; + + my @custnums; + foreach my $svcnum (@svcnums) { + my @link = ( 'Type' => 'MemberOf', + 'Target' => "freeside://freeside/cust_svc/$svcnum", + ); + + my( $val, $msg ) = $Ticket->AddLink(@link); + push @results, $msg; + next if !$val; + + } + + ### #find new customers ### - my @custnums = map { /^Ticket-AddCustomer-(\d+)$/; $1 } - grep { /^Ticket-AddCustomer-(\d+)$/ && $ARGSRef->{$_} } - keys %$ARGSRef; + push @custnums, map { /^Ticket-AddCustomer-(\d+)$/; $1 } + grep { /^Ticket-AddCustomer-(\d+)$/ && $ARGSRef->{$_} } + keys %$ARGSRef; #my @delete_custnums = # map { /^Ticket-AddCustomer-(\d+)$/; $1 } diff --git a/rt/lib/RT/Record.pm b/rt/lib/RT/Record.pm index 121c08686..41e60bd84 100755 --- a/rt/lib/RT/Record.pm +++ b/rt/lib/RT/Record.pm @@ -1177,7 +1177,9 @@ sub DependsOn { =head2 Customers - This returns an RT::Links object which references all the customers that this object is a member of. + This returns an RT::Links object which references all the customers that + this object is a member of. This includes both explicitly linked customers + and links implied by services. =cut @@ -1189,11 +1191,16 @@ sub Customers { $self->{'Customers'} = $self->MemberOf->Clone; - $self->{'Customers'}->Limit( - FIELD => 'Target', - OPERATOR => 'STARTSWITH', - VALUE => 'freeside://freeside/cust_main/', - ); + for my $fstable (qw(cust_main cust_svc)) { + + $self->{'Customers'}->Limit( + FIELD => 'Target', + OPERATOR => 'STARTSWITH', + VALUE => "freeside://freeside/$fstable", + ENTRYAGGREGATOR => 'OR', + SUBCLAUSE => 'customers', + ); + } } warn "->Customers method called on $self; returning ". @@ -1205,6 +1212,34 @@ sub Customers { # }}} +# {{{ Services + +=head2 Services + + This returns an RT::Links object which references all the services this + object is a member of. + +=cut + +sub Services { + my( $self, %opt ) = @_; + + unless ( $self->{'Services'} ) { + + $self->{'Services'} = $self->MemberOf->Clone; + + $self->{'Services'}->Limit( + FIELD => 'Target', + OPERATOR => 'STARTSWITH', + VALUE => "freeside://freeside/cust_svc", + ); + } + + return $self->{'Services'}; +} + +# }}} + # {{{ sub _Links =head2 Links DIRECTION [TYPE] diff --git a/rt/lib/RT/Tickets_Overlay.pm b/rt/lib/RT/Tickets_Overlay.pm index a5d37a378..0d482cd04 100644 --- a/rt/lib/RT/Tickets_Overlay.pm +++ b/rt/lib/RT/Tickets_Overlay.pm @@ -146,11 +146,8 @@ our %FIELD_METADATA = ( HasAttribute => [ 'HASATTRIBUTE', 1 ], HasNoAttribute => [ 'HASATTRIBUTE', 0 ], #freeside - Customer => [ 'FREESIDEFIELD', ], -# Agentnum => [ 'FREESIDEFIELD', ], -# Classnum => [ 'FREESIDEFIELD', ], -# Refnum => [ 'FREESIDEFIELD', ], -# Tagnum => [ 'FREESIDEFIELD', 'cust_tag' ], + Customer => [ 'FREESIDEFIELD' => 'Customer' ], + Service => [ 'FREESIDEFIELD' => 'Service' ], WillResolve => [ 'DATE' => 'WillResolve', ], #loc_left_pair ); @@ -1823,6 +1820,15 @@ sub OrderByCols { } push @res, { %$row, ALIAS => $custalias, FIELD => $cust_field }; + } elsif ( $field eq 'Service' ) { + + my $svcalias = $self->JoinToService; + my $svc_field = lc($subkey); + if ( !$svc_field or $svc_field eq 'number' ) { + $svc_field = 'svcnum'; + } + push @res, { %$row, ALIAS => $svcalias, FIELD => $svc_field }; + } #Freeside else { @@ -1842,7 +1848,7 @@ sub JoinToCustLinks { # and an sql expression to retrieve the custnum. my $self = shift; # only join once for each RT::Tickets object - my $linkalias = $self->{cust_linkalias}; + my $linkalias = $self->{cust_main_linkalias}; if (!$linkalias) { $linkalias = $self->Join( TYPE => 'LEFT', @@ -1864,7 +1870,7 @@ sub JoinToCustLinks { OPERATOR => 'STARTSWITH', VALUE => 'freeside://freeside/cust_main/', ); - $self->{cust_linkalias} = $linkalias; + $self->{cust_main_linkalias} = $linkalias; } my $custnum_sql = "CAST(SUBSTR($linkalias.Target,31) AS "; if ( RT->Config->Get('DatabaseType') eq 'mysql' ) { @@ -1890,9 +1896,79 @@ sub JoinToCustomer { return $custalias; } +sub JoinToSvcLinks { + my $self = shift; + my $linkalias = $self->{cust_svc_linkalias}; + if (!$linkalias) { + $linkalias = $self->Join( + TYPE => 'LEFT', + ALIAS1 => 'main', + FIELD1 => 'id', + TABLE2 => 'Links', + FIELD2 => 'LocalBase', + ); + + $self->SUPER::Limit( + LEFTJOIN => $linkalias, + FIELD => 'Type', + OPERATOR => '=', + VALUE => 'MemberOf', + ); + $self->SUPER::Limit( + LEFTJOIN => $linkalias, + FIELD => 'Target', + OPERATOR => 'STARTSWITH', + VALUE => 'freeside://freeside/cust_svc/', + ); + $self->{cust_svc_linkalias} = $linkalias; + } + my $svcnum_sql = "CAST(SUBSTR($linkalias.Target,30) AS "; + if ( RT->Config->Get('DatabaseType') eq 'mysql' ) { + $svcnum_sql .= 'SIGNED INTEGER)'; + } + else { + $svcnum_sql .= 'INTEGER)'; + } + return ($linkalias, $svcnum_sql); +} + +sub JoinToService { + my $self = shift; + my ($linkalias, $svcnum_sql) = $self->JoinToSvcLinks; + $self->Join( + TYPE => 'LEFT', + EXPRESSION => $svcnum_sql, + TABLE2 => 'cust_svc', + FIELD2 => 'svcnum', + ); +} + +# This creates an alternate left join path to cust_main via cust_svc. +# _FreesideFieldLimit needs to add this as a separate, independent join +# and include all tickets that have a matching cust_main record via +# either path. +sub JoinToCustomerViaService { + my $self = shift; + my $svcalias = $self->JoinToService; + my $cust_pkg = $self->Join( + TYPE => 'LEFT', + ALIAS1 => $svcalias, + FIELD1 => 'pkgnum', + TABLE2 => 'cust_pkg', + FIELD2 => 'pkgnum', + ); + my $cust_main = $self->Join( + TYPE => 'LEFT', + ALIAS1 => $cust_pkg, + FIELD1 => 'custnum', + TABLE2 => 'cust_main', + FIELD2 => 'custnum', + ); + $cust_main; +} + sub _FreesideFieldLimit { my ( $self, $field, $op, $value, %rest ) = @_; - my $alias = $self->JoinToCustomer; my $is_negative = 0; if ( $op eq '!=' || $op =~ /\bNOT\b/i ) { # if the op is negative, do the join as though @@ -1903,40 +1979,70 @@ sub _FreesideFieldLimit { $op =~ s/\bNOT\b//; } - my $cust_field = $rest{SUBKEY} || 'custnum'; + my (@alias, $table2, $subfield, $pkey); + if ( $field eq 'Customer' ) { + push @alias, $self->JoinToCustomer; + push @alias, $self->JoinToCustomerViaService; + $pkey = 'custnum'; + } + elsif ( $field eq 'Service' ) { + push @alias, $self->JoinToService; + $pkey = 'svcnum'; + } + else { + die "malformed Freeside query: $field"; + } + + $subfield = $rest{SUBKEY} || $pkey; my $table2; # compound subkey: separate into table name and field in that table # (must be linked by custnum) - ($table2, $cust_field) = ($1, $2) if $cust_field =~ /^(\w+)?\.(\w+)$/; - - $cust_field = lc($cust_field); - $cust_field = 'custnum' if !$cust_field or $cust_field eq 'number'; - - if ( $table2 ) { - $alias = $self->Join( - TYPE => 'LEFT', - ALIAS1 => $alias, - FIELD1 => 'custnum', - TABLE2 => $table2, - FIELD2 => 'custnum', - ); + $subfield = lc($subfield); + ($table2, $subfield) = ($1, $2) if $subfield =~ /^(\w+)?\.(\w+)$/; + $subfield = $pkey if $subfield eq 'number'; + + # if it's compound, create a join from cust_main or cust_svc to that + # table, using custnum or svcnum, and Limit on that table instead. + foreach my $a (@alias) { + if ( $table2 ) { + $a = $self->Join( + TYPE => 'LEFT', + ALIAS1 => $a, + FIELD1 => $pkey, + TABLE2 => $table2, + FIELD2 => $pkey, + ); + } + + # do the actual Limit + $self->SUPER::Limit( + LEFTJOIN => $a, + FIELD => $subfield, + OPERATOR => $op, + VALUE => $value, + ENTRYAGGREGATOR => 'AND', + # no SUBCLAUSE needed, limits on different aliases across left joins + # are inherently independent + ); + + # then, since it's a left join, exclude tickets for which there is now + # no matching record in the table we just limited on. (Or where there + # is a matching record, if $is_negative.) + # For a cust_main query (where there are two different aliases), this + # will produce a subclause: "cust_main_1.custnum IS NOT NULL OR + # cust_main_2.custnum IS NOT NULL" (or "IS NULL AND..." for a negative + # query). + $self->_SQLLimit( + %rest, + ALIAS => $a, + FIELD => $pkey, + OPERATOR => $is_negative ? 'IS' : 'IS NOT', + VALUE => 'NULL', + QUOTEVALUE => 0, + ENTRYAGGREGATOR => $is_negative ? 'AND' : 'OR', + SUBCLAUSE => 'fs_limit', + ); } - - $self->SUPER::Limit( - LEFTJOIN => $alias, - FIELD => $cust_field, - OPERATOR => $op, - VALUE => $value, - ENTRYAGGREGATOR => 'AND', - ); - $self->_SQLLimit( - %rest, - ALIAS => $alias, - FIELD => 'custnum', - OPERATOR => $is_negative ? 'IS' : 'IS NOT', - VALUE => 'NULL', - QUOTEVALUE => 0, - ); } #Freeside diff --git a/rt/lib/RT/URI/freeside/Internal.pm b/rt/lib/RT/URI/freeside/Internal.pm index 5656a51d8..b5e56ee1f 100644 --- a/rt/lib/RT/URI/freeside/Internal.pm +++ b/rt/lib/RT/URI/freeside/Internal.pm @@ -38,8 +38,14 @@ use FS::Conf; use FS::Record qw(qsearchs qsearch dbdef); use FS::cust_main; use FS::cust_svc; +use FS::part_svc; use FS::payby; +#can I do this? +FS::UID->install_callback( + sub { @RT::URI::freeside::svc_tables = FS::part_svc->svc_tables() } +); + =head1 NAME RT::URI::freeside::Internal @@ -105,7 +111,23 @@ sub FreesideGetConfig { sub smart_search { #Subroutine - return map { { $_->hash } } &FS::cust_main::Search::smart_search(@_); + return map { { $_->hash } } &FS::cust_main::Search::smart_search(@_); + +} + +sub service_search { + + return map { + my $cust_pkg = $_->cust_pkg; + my $custnum = $cust_pkg->custnum if $cust_pkg; + my $label = join(': ',($_->label)[0, 1]); + my %hash = ( + $_->hash, + 'label' => $label, + 'custnum' => $custnum, # so that it's smart_searchable... + ); + \%hash + } &FS::cust_svc::smart_search(@_); } @@ -130,10 +152,22 @@ sub _FreesideURILabelLong { if ( $table eq 'cust_main' ) { my $rec = $self->_FreesideGetRecord(); - return small_custview( $rec->{'_object'}, + return '' . + small_custview( $rec->{'_object'}, scalar(FS::Conf->new->config('countrydefault')), - 1 #nobalance - ); + 1, #nobalance + ) . ''; + + } elsif ( $table eq 'cust_svc' ) { + + my $string = ''; + my $cust = $self->CustomerResolver; + if ( $cust ) { + $string = $cust->AsStringLong; + } + $string .= '' . + $self->AsString . ''; + return $string; } else { @@ -143,18 +177,36 @@ sub _FreesideURILabelLong { } -# no need to have a separate wrapper method for every one of these things +sub CustomerResolver { + my $self = shift; + if ( $self->{fstable} eq 'cust_main' ) { + return $self; + } + elsif ( $self->{fstable} eq 'cust_svc' ) { + my $rec = $self->_FreesideGetRecord(); + return if !$rec; + my $cust_pkg = $rec->{'_object'}->cust_pkg; + if ( $cust_pkg ) { + my $URI = RT::URI->new($self->CurrentUser); + $URI->FromURI('freeside://freeside/cust_main/'.$cust_pkg->custnum); + return $URI->Resolver; + } + } + return; +} + sub CustomerInfo { my $self = shift; + $self = $self->CustomerResolver or return; my $rec = $self->_FreesideGetRecord() or return; - my $cust_main = $rec->{'_object'}; + my $cust_main = delete $rec->{_object}; my $agent = $cust_main->agent; my $class = $cust_main->cust_class; my $referral = qsearchs('part_referral', { refnum => $cust_main->refnum }); my @part_tags = $cust_main->part_tag; return $self->{CustomerInfo} ||= { - $cust_main->hash, + %$rec, AgentName => ($agent ? ($agent->agentnum.': '.$agent->agent) : ''), CustomerClass => ($class ? $class->classname : ''), @@ -170,4 +222,19 @@ sub CustomerInfo { } } +sub ServiceInfo { + my $self = shift; + $self->{fstable} eq 'cust_svc' or return; + my $rec = $self->_FreesideGetRecord() or return; + my $cust_svc = $rec->{'_object'}; + my $svc_x = $cust_svc->svc_x; + my $part_svc = $cust_svc->part_svc; + return $self->{ServiceInfo} ||= { + $cust_svc->hash, + $svc_x->hash, + ServiceType => $part_svc->svc, + Label => $self->AsString, + } +} + 1; diff --git a/rt/share/html/Elements/CustomerFields b/rt/share/html/Elements/CustomerFields index 553a34999..d5419d213 100644 --- a/rt/share/html/Elements/CustomerFields +++ b/rt/share/html/Elements/CustomerFields @@ -16,7 +16,6 @@ About the keys: name to sort by. <%once> -return unless $RT::URI::freeside::IntegrationType eq 'Internal'; my @customer_fields = ( # ordered { @@ -158,8 +157,12 @@ sub select_table { sub ticket_cust_resolvers { my $Ticket = shift; - my @Customers = @{ $Ticket->Customers->ItemsArrayRef }; - return map $_->TargetURI->Resolver, @Customers; + my @Customers = map { $_->TargetURI->Resolver->CustomerResolver } + @{ $Ticket->Customers->ItemsArrayRef }; + # this can contain cust_svc links, careful + # uniq + my %seen = map { $_->URI => $_ } @Customers; + values %seen; } sub cust_info_attribute { # the simple case of $resolver->CustomerInfo->{foo} @@ -177,7 +180,6 @@ sub cust_info_attribute { # the simple case of $resolver->CustomerInfo->{foo} <%init> -return unless $RT::URI::freeside::IntegrationType eq 'Internal'; my $arg = shift; if ( $arg eq 'Names' ) { @@ -198,9 +200,11 @@ elsif ( $arg eq 'ColumnMap' ) { grep { exists $_->{Display} } @customer_fields; } -elsif ( $arg eq 'PickBasics' ) { +elsif ( $arg eq 'Criteria' ) { return map { my $f = $_; + # argument to Search/Elements/ConditionRow + $f->{Condition} || { Name => ($f->{QueryName} || $f->{Name}), Field => ($f->{QueryLabel} || $f->{Label}), @@ -208,7 +212,7 @@ elsif ( $arg eq 'PickBasics' ) { Value => $f->{Value}, } } #map - grep { exists $_->{Value} } + grep { exists $_->{Condition} || exists $_->{Value} } @customer_fields; } else { die "unknown CustomerFields mode '$arg'\n"; } diff --git a/rt/share/html/Elements/RT__Ticket/ColumnMap b/rt/share/html/Elements/RT__Ticket/ColumnMap index 9e6466cdd..35c0aad86 100644 --- a/rt/share/html/Elements/RT__Ticket/ColumnMap +++ b/rt/share/html/Elements/RT__Ticket/ColumnMap @@ -321,6 +321,7 @@ $COLUMN_MAP = { #freeside $m->comp('/Elements/CustomerFields', 'ColumnMap'), + $m->comp('/Elements/ServiceFields', 'ColumnMap'), }; # if no GPG support, then KeyOwnerName and KeyRequestors fall back to the regular diff --git a/rt/share/html/Elements/ServiceFields b/rt/share/html/Elements/ServiceFields new file mode 100644 index 000000000..9c9a248c8 --- /dev/null +++ b/rt/share/html/Elements/ServiceFields @@ -0,0 +1,161 @@ +<%doc> +Accessible Freeside svc_x fields go in here. RT::URI::freeside::Internal +pulls all fields from cust_svc and the svc_x tables into ServiceInfo(). +RT::Tickets_Overlay resolves "Service.foo" as "cust_svc.foo", and +"Service.svc_acct.bar" as "JOIN svc_acct USING (svcnum) ... svc_acct.bar". + +See /Elements/CustomerFields for notes on this data structure. + +<%once> + +my @service_fields = ( # ordered + { + # svcnum + Name => 'Service', + Label => 'Service', + Display => sub { + my $Ticket = shift; + my @return = (); + foreach my $s (ticket_svc_resolvers($Ticket)) { + push @return, \'', + $s->AsString, + \'', + \'
'; + } + pop @return; + @return; + }, + OrderBy => 'Service.Number', + }, + { + #Column name (format string) + Name => 'ServiceType', + # Column heading/query builder name + Label => 'Service Type', + # Column value (coderef, cust_svc/svc_x field, or ServiceInfo key) + Display => 'ServiceType', + # Query builder options + # RT-SQL field, defaults to Name + QueryName => 'Service.svcpart', + Op => equals_notequals, + Value => select_table('part_svc', 'svcpart', 'svc'), + # RT-SQL sort key (if any) + OrderBy => 'Service.svcpart', + }, + { + Name => 'ServiceKey', # loosely corresponds to smartsearch/label field + Label => '', + # not displayable + QueryLabel => { + Type => 'select', + Options => [ + 'Service.svc_acct.username' => loc('Username'), + 'Service.svc_phone.phonenum' => loc('Phone Number'), + 'Service.svc_broadband.ip_addr' => loc('IP Address'), + 'Service.svc_broadband.mac_addr' => loc('MAC Address'), + ], + }, + Op => matches_notmatches, + Value => { Type => 'text', Size => 20 }, + }, + { + Name => 'Router', + Label => 'Router', + QueryName => 'Service.svc_broadband.routernum', + # not displayable + Op => equals_notequals, + Value => select_table('router', 'routernum', 'routername'), + OrderBy => 'Service.svc_broadband.routernum', + }, + +); +#helper subs +#Op +sub equals_notequals { + { + Type => 'component', + Path => '/Elements/SelectBoolean', + Arguments => { TrueVal=> '=', FalseVal=> '!=' }, + } +} +sub matches_notmatches { + { + Type => 'component', + Path => '/Elements/SelectMatch', + }, +} + +#Value +sub select_table { + my ($table, $value_col, $name_col, $hashref) = @_; + $hashref ||= { disabled => '' }; # common case + return { + Type => 'select', + Options => [ + '' => '-', + map { $_->$value_col, $_->$name_col } + qsearch($table, $hashref) + ], + } +} + +sub ticket_svc_resolvers { + my $Ticket = shift; + my @Services = @{ $Ticket->Services->ItemsArrayRef }; + return map $_->TargetURI->Resolver, @Services; +} + +sub svc_info_attribute { + my $attribute = shift; + sub { + my $Ticket = shift; + my @return; + foreach my $s (ticket_svc_resolvers($Ticket)) { + push @return, $s->ServiceInfo->{$attribute}, '
'; + } + pop @return; #trailing
+ @return; + }; +} + + +<%init> +use Data::Dumper; +#warn Dumper(\@service_fields); + +my $arg = shift; +if ( $arg eq 'Names' ) { + return map { $_->{Name} } + grep { exists $_->{Display} } + @service_fields; +} +elsif ( $arg eq 'ColumnMap' ) { + return map { + my $f = $_; + $f->{Name} => { + title => $f->{Label}, + attribute => $f->{OrderBy} || '', + value => ref($f->{Display}) eq 'CODE' ? + $f->{Display} : + svc_info_attribute($f->{Display}) + } + } #map + grep { exists $_->{Display} } + @service_fields; +} +elsif ( $arg eq 'Criteria' ) { + return map { + my $f = $_; + # argument to Search/Elements/ConditionRow + { + Name => ($f->{QueryName} || $f->{Name}), + Field => ($f->{QueryLabel} || $f->{Label}), + Op => $f->{Op}, + Value => $f->{Value}, + } + } #map + grep { exists($_->{Value}) } + @service_fields; +} +else { die "unknown ServiceFields mode '$arg'\n"; } + diff --git a/rt/share/html/Search/Elements/BuildFormatString b/rt/share/html/Search/Elements/BuildFormatString index 96e6a2863..57c767911 100644 --- a/rt/share/html/Search/Elements/BuildFormatString +++ b/rt/share/html/Search/Elements/BuildFormatString @@ -76,6 +76,7 @@ my @fields = ( ), $m->comp('/Elements/CustomerFields', 'Names'), #freeside + $m->comp('/Elements/ServiceFields', 'Names'), #freeside qw( Status ExtendedStatus UpdateStatus diff --git a/rt/share/html/Search/Elements/PickBasics b/rt/share/html/Search/Elements/PickBasics index ff30f7c11..e9534237b 100644 --- a/rt/share/html/Search/Elements/PickBasics +++ b/rt/share/html/Search/Elements/PickBasics @@ -210,7 +210,6 @@ my @lines = ( ); #freeside -push @lines, $m->comp('/Elements/CustomerFields', 'PickBasics'); $m->callback( Conditions => \@lines ); diff --git a/rt/share/html/Search/Elements/PickCriteria b/rt/share/html/Search/Elements/PickCriteria index 44e3b7037..5eb112d17 100644 --- a/rt/share/html/Search/Elements/PickCriteria +++ b/rt/share/html/Search/Elements/PickCriteria @@ -52,6 +52,7 @@ <& PickBasics &> +<& PickCustomerFields &> <& PickCFs, cfqueues => \%cfqueues &>
diff --git a/rt/share/html/Search/Elements/PickCustomerFields b/rt/share/html/Search/Elements/PickCustomerFields new file mode 100644 index 000000000..96d8e47e7 --- /dev/null +++ b/rt/share/html/Search/Elements/PickCustomerFields @@ -0,0 +1,8 @@ +% if ( $RT::URI::freeside::IntegrationType eq 'Internal' ) { +% my @lines; +% push @lines, $m->comp('/Elements/CustomerFields', 'Criteria'); +% push @lines, $m->comp('/Elements/ServiceFields', 'Criteria'); +% foreach( @lines ) { +<& ConditionRow, Condition => $_ &> +% } +% } diff --git a/rt/share/html/Ticket/Elements/AddCustomers b/rt/share/html/Ticket/Elements/AddCustomers index 09acdfd3f..3c2c82add 100644 --- a/rt/share/html/Ticket/Elements/AddCustomers +++ b/rt/share/html/Ticket/Elements/AddCustomers @@ -13,15 +13,25 @@
<%$msg%>
-% if (@Customers) { +% if (@Customers or @Services) { -
(Check box to link) +
(Check box to link) % foreach my $customer (@Customers) { + +% } +% +% foreach my $service (@Services) { + + % } @@ -29,11 +39,21 @@ % } +<%once> +my $freeside_url = &RT::URI::freeside::FreesideURL(); + + +<%def .small_custview> +% my $cust = shift; +<% &RT::URI::freeside::small_custview($cust->{'custnum'}, &RT::URI::freeside::FreesideGetConfig, 1) |n %> + +<%def .small_svcview> +% my $svc = shift; +<% $svc->{'label'} %> + <%INIT> my ($msg); -my $freeside_url = &RT::URI::freeside::FreesideURL(); - my @Customers = (); if ( $CustomerString ) { @Customers = &RT::URI::freeside::smart_search( @@ -43,8 +63,11 @@ if ( $CustomerString ) { } my @Services = (); -if ($ServiceString) { - @Services = (); #service_search(); +if ( $ServiceString + and $RT::URI::freeside::IntegrationType eq 'Internal' ) { + @Services = RT::URI::freeside::service_search( + 'search' => $ServiceString, + ); } diff --git a/rt/share/html/Ticket/Elements/EditCustomers b/rt/share/html/Ticket/Elements/EditCustomers index 0ba6e447b..96207f4cc 100644 --- a/rt/share/html/Ticket/Elements/EditCustomers +++ b/rt/share/html/Ticket/Elements/EditCustomers @@ -12,7 +12,7 @@ %# General Public License for more details.
> - <% &RT::URI::freeside::small_custview($customer->{'custnum'}, &RT::URI::freeside::FreesideGetConfig('countrydefault'), 1) |n %> + <& .small_custview, $customer &> +
+ > + <& .small_custview, $service &> + <& .small_svcview, $service &>
- - + + +%# rowspan + + + + +%#rowspan... +
+

<&|/l&>Current Customers

@@ -25,33 +25,40 @@ %# <& ShowLink, URI => $link->TargetURI &>
- <% $link->TargetURI->Resolver->AsStringLong |n %> -
+ <% $link->TargetURI->Resolver->AsStringLong |n %> +

% }
- +
+

<&|/l&>New Customer Links

-<&|/l&>Find customer
- - -
cust #, name, company or phone -
-%#
-%#<&|/l&>Find service
-%# -%# -%#
username, username@domain, domain, or IP address -%#
- +
+ <&|/l&>Find customer
+ + +
cust #, name, company or phone +
+ <&|/l&>Find service
+ + +
user, email, ip, mac, phone +
<& AddCustomers, Ticket => $Ticket, CustomerString => $CustomerString, - ServiceString => $ServiceString, &> - + ServiceString => $ServiceString &>
diff --git a/rt/share/html/Ticket/Elements/ShowCustomers b/rt/share/html/Ticket/Elements/ShowCustomers index 3acf92dd4..add562440 100644 --- a/rt/share/html/Ticket/Elements/ShowCustomers +++ b/rt/share/html/Ticket/Elements/ShowCustomers @@ -10,20 +10,19 @@ %# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU %# General Public License for more details. -% my $cust = 0; -% foreach my $custResolver ( map { $_->TargetURI->Resolver } -% @{ $Ticket->Customers->ItemsArrayRef } -% ) -% { -% $cust++; -% my $cust_main = ''; +% my @cust = map { $_->TargetURI->Resolver } +% @{ $Ticket->Customers->ItemsArrayRef }; +% +% foreach my $custResolver ( @cust ) { % } -% unless ( $cust ) { + +% unless ( @cust ) {
- <% $custResolver->AsStringLong |n %> + <% $custResolver->AsStringLong |n %> +%# includes service label and view/svc_ link for cust_svc links
(none)