summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Wells <mark@freeside.biz>2012-04-17 15:52:14 -0700
committerMark Wells <mark@freeside.biz>2012-04-17 15:52:14 -0700
commit1c59bba12621e154765a8255534e94a041dfd200 (patch)
tree5f9acae2881b035e9e3b9a21d8bc6bab1f4b2a73
parent71cbdde5012550846390e9f0ebafdb48e06da5e8 (diff)
link tickets to services, #17067
-rw-r--r--FS/FS/TicketSystem/RT_External.pm51
-rw-r--r--FS/FS/TicketSystem/RT_Internal.pm63
-rw-r--r--FS/FS/cust_svc.pm86
-rw-r--r--FS/FS/svc_phone.pm3
-rw-r--r--httemplate/elements/form-create_ticket.html38
-rw-r--r--httemplate/elements/table-tickets.html159
-rw-r--r--httemplate/view/cust_main/tickets.html166
-rw-r--r--httemplate/view/elements/svc_Common.html4
-rwxr-xr-xhttemplate/view/svc_acct.cgi4
-rwxr-xr-xhttemplate/view/svc_domain.cgi4
-rw-r--r--httemplate/view/svc_external.cgi5
-rwxr-xr-xhttemplate/view/svc_forward.cgi4
-rw-r--r--httemplate/view/svc_www.cgi4
-rw-r--r--rt/lib/RT/Interface/Web_Vendor.pm26
-rwxr-xr-xrt/lib/RT/Record.pm47
-rw-r--r--rt/lib/RT/Tickets_Overlay.pm182
-rw-r--r--rt/lib/RT/URI/freeside/Internal.pm81
-rw-r--r--rt/share/html/Elements/CustomerFields16
-rw-r--r--rt/share/html/Elements/RT__Ticket/ColumnMap1
-rw-r--r--rt/share/html/Elements/ServiceFields161
-rw-r--r--rt/share/html/Search/Elements/BuildFormatString1
-rw-r--r--rt/share/html/Search/Elements/PickBasics1
-rw-r--r--rt/share/html/Search/Elements/PickCriteria1
-rw-r--r--rt/share/html/Search/Elements/PickCustomerFields8
-rw-r--r--rt/share/html/Ticket/Elements/AddCustomers37
-rw-r--r--rt/share/html/Ticket/Elements/EditCustomers45
-rw-r--r--rt/share/html/Ticket/Elements/ShowCustomers17
27 files changed, 934 insertions, 281 deletions
diff --git a/FS/FS/TicketSystem/RT_External.pm b/FS/FS/TicketSystem/RT_External.pm
index f976ac0..22d2472 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 d96e5f0..ffa8c7c 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 fc6e605..a527913 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<search>, 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<smart_search> 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 118748e..b395ea6 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 0000000..362e823
--- /dev/null
+++ b/httemplate/elements/form-create_ticket.html
@@ -0,0 +1,38 @@
+<FORM METHOD="GET" NAME="CreateTicketForm" STYLE="display:inline">
+<SCRIPT TYPE="text/javascript">
+function updateTicketLink() {
+ var link = document.getElementById('CreateTicketLink');
+ var selector = document.getElementById('Queue')
+ link.href = "<% $new_base.'?'.
+ join(';', map(
+ { ($_ eq 'Queue') ? () : "$_=$new_param{$_}"}
+ keys %new_param),'Queue=') %>" + selector.options[selector.selectedIndex].value;
+}
+</SCRIPT>
+<A NAME="tickets"><FONT CLASS="fsinnerbox-title">Tickets</FONT></A>
+<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 %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'} %>">
+% }
+% else {
+<SELECT NAME="Queue" id="Queue" onchange="updateTicketLink()">
+% foreach my $queueid ( sort { $queues{$a} cmp $queues{$b} } keys %queues ) {
+ <OPTION VALUE="<% $queueid %>"
+ <% $queueid == $new_param{'Queue'} ? 'SELECTED' : '' %>
+ ><% $queues{$queueid} |h %>
+% }
+</SELECT>
+<SCRIPT DEFER TYPE="text/javascript">updateTicketLink();</SCRIPT>
+% }
+</FORM>
+<%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);
+</%init>
diff --git a/httemplate/elements/table-tickets.html b/httemplate/elements/table-tickets.html
new file mode 100644
index 0000000..6d1a45a
--- /dev/null
+++ b/httemplate/elements/table-tickets.html
@@ -0,0 +1,159 @@
+<& /elements/form-create_ticket.html, object => $object &>
+ |
+View
+<A HREF="<% $open_link %>"><% mt($openlabel) |h %></A> |
+<A HREF="<% $res_link %>"><% mt('resolved') |h %></A>
+ <BR>
+
+<& /elements/table-grid.html &>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('#') |h %></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Subject') |h %></TH>
+ <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>
+% }
+% if ( $object->isa('FS::cust_main') ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Service') |h %></TH>
+% }
+</TR>
+
+% foreach my $ticket ( @tickets ) {
+% my $href = FS::TicketSystem->href_ticket($ticket->{id});
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+ <TR>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF=<%$href%>><% $ticket->{id} %></A>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF=<%$href%>><% $ticket->{subject} %></A>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $ticket->{status} %>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $ticket->{queue} %>
+ </TD>
+
+ <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}
+ ? $ticket->{content}.' ('.$ticket->{priority}.')'
+ : $ticket->{priority}
+ %>
+ </TD>
+
+% if ( $ss_priority ) {
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $ticket->{"CF.{$ss_priority}"} %>
+ </TD>
+% }
+% if ( $object->isa('FS::cust_main') ) {
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE=-1><B>
+% 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) %>
+ <BR>
+% }
+ </B></FONT></TD>
+% }
+
+ </TR>
+
+% }
+
+</TABLE>
+
+<%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) : '';
+};
+
+</%init>
diff --git a/httemplate/view/cust_main/tickets.html b/httemplate/view/cust_main/tickets.html
index 194e907..f076fcc 100644
--- a/httemplate/view/cust_main/tickets.html
+++ b/httemplate/view/cust_main/tickets.html
@@ -1,164 +1,2 @@
-<FORM METHOD="GET" NAME="CreateTicketForm" STYLE="display:inline">
-<SCRIPT TYPE="text/javascript">
-function updateTicketLink() {
- var link = document.getElementById('CreateTicketLink');
- var selector = document.getElementById('Queue')
- link.href = "<% $new_base.'?'.
- join(';', map(
- { ($_ eq 'Queue') ? () : "$_=$new_param{$_}"}
- keys %new_param),'Queue=') %>" + selector.options[selector.selectedIndex].value;
-}
-</SCRIPT>
-<A NAME="tickets"><FONT CLASS="fsinnerbox-title">Tickets</FONT></A>
-<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 %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'} %>">
-% }
-% else {
-<SELECT NAME="Queue" id="Queue" onchange="updateTicketLink()">
-% foreach my $queueid ( sort { $queues{$a} cmp $queues{$b} } keys %queues ) {
- <OPTION VALUE="<% $queueid %>"
- <% $queueid == $new_param{'Queue'} ? 'SELECTED' : '' %>
- ><% $queues{$queueid} |h %>
-% }
-</SELECT>
-<SCRIPT DEFER TYPE="text/javascript">updateTicketLink();</SCRIPT>
-% }
-</FORM>
- |
-View
-<A HREF="<% $open_link %>"><% mt($openlabel) |h %></A> |
-<A HREF="<% $res_link %>"><% mt('resolved') |h %></A>
-<BR>
-
-<& /elements/table-grid.html &>
-% my $bgcolor1 = '#eeeeee';
-% my $bgcolor2 = '#ffffff';
-% my $bgcolor = '';
-
-<TR>
- <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('#') |h %></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Subject') |h %></TH>
- <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 ) {
-% my $href = FS::TicketSystem->href_ticket($ticket->{id});
-% if ( $bgcolor eq $bgcolor1 ) {
-% $bgcolor = $bgcolor2;
-% } else {
-% $bgcolor = $bgcolor1;
-% }
-
- <TR>
-
- <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
- <A HREF=<%$href%>><% $ticket->{id} %></A>
- </TD>
-
- <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
- <A HREF=<%$href%>><% $ticket->{subject} %></A>
- </TD>
-
- <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
- <% $ticket->{status} %>
- </TD>
-
- <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
- <% $ticket->{queue} %>
- </TD>
-
- <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}
- ? $ticket->{content}.' ('.$ticket->{priority}.')'
- : $ticket->{priority}
- %>
- </TD>
-
-% if ( $ss_priority ) {
- <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
- <% $ticket->{"CF.{$ss_priority}"} %>
- </TD>
-% }
-
- </TR>
-
-% }
-
-</TABLE>
-
-<%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) : '';
-};
-
-</%init>
+% 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 3130c73..04d2b29 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 441e494..de97a07 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 3938a34..fcccd74 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 77679d8..72e5535 100644
--- a/httemplate/view/svc_external.cgi
+++ b/httemplate/view/svc_external.cgi
@@ -23,6 +23,11 @@
</TABLE></TD></TR></TABLE>
+
+% if ( $conf->config('ticket_system') ) {
+<& /elements/table-tickets.html, object => $cust_svc &>
+% }
+
<BR><% joblisting({'svcnum'=>$svcnum}, 1) %>
<% include('/elements/footer.html') %>
diff --git a/httemplate/view/svc_forward.cgi b/httemplate/view/svc_forward.cgi
index 15b5ae5..2cb78eb 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 935d139..fbb02a0 100644
--- a/httemplate/view/svc_www.cgi
+++ b/httemplate/view/svc_www.cgi
@@ -49,6 +49,10 @@
</TABLE>
<BR>
+% 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 ee8c34b..ae7f089 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 121c086..41e60bd 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 a5d37a3..0d482cd 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 5656a51..b5e56ee 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 '<A HREF="' . $self->HREF . '">' .
+ small_custview( $rec->{'_object'},
scalar(FS::Conf->new->config('countrydefault')),
- 1 #nobalance
- );
+ 1, #nobalance
+ ) . '</A>';
+
+ } elsif ( $table eq 'cust_svc' ) {
+
+ my $string = '';
+ my $cust = $self->CustomerResolver;
+ if ( $cust ) {
+ $string = $cust->AsStringLong;
+ }
+ $string .= '<B><A HREF="' . $self->HREF . '">' .
+ $self->AsString . '</A></B>';
+ 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 553a349..d5419d2 100644
--- a/rt/share/html/Elements/CustomerFields
+++ b/rt/share/html/Elements/CustomerFields
@@ -16,7 +16,6 @@ About the keys:
name to sort by.
</%doc>
<%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}
</%once>
<%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 9e6466c..35c0aad 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 0000000..9c9a248
--- /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.
+</%doc>
+<%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, \'<A HREF="', $s->HREF, \'">',
+ $s->AsString,
+ \'</A>',
+ \'<BR>';
+ }
+ 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}, '<BR>';
+ }
+ pop @return; #trailing <BR>
+ @return;
+ };
+}
+
+</%once>
+<%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"; }
+</%init>
diff --git a/rt/share/html/Search/Elements/BuildFormatString b/rt/share/html/Search/Elements/BuildFormatString
index 96e6a28..57c7679 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 ff30f7c..e953423 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 44e3b70..5eb112d 100644
--- a/rt/share/html/Search/Elements/PickCriteria
+++ b/rt/share/html/Search/Elements/PickCriteria
@@ -52,6 +52,7 @@
<& PickBasics &>
+<& PickCustomerFields &>
<& PickCFs, cfqueues => \%cfqueues &>
<tr class="separator"><td colspan="3"><hr /></td></tr>
diff --git a/rt/share/html/Search/Elements/PickCustomerFields b/rt/share/html/Search/Elements/PickCustomerFields
new file mode 100644
index 0000000..96d8e47
--- /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 09acdfd..3c2c82a 100644
--- a/rt/share/html/Ticket/Elements/AddCustomers
+++ b/rt/share/html/Ticket/Elements/AddCustomers
@@ -13,15 +13,25 @@
<BR>
<%$msg%><br>
-% if (@Customers) {
+% if (@Customers or @Services) {
-<br><i>(Check box to link)<i>
+<br><i>(Check box to link)</i>
<table>
% foreach my $customer (@Customers) {
<tr>
<td>
<input type="checkbox" name="Ticket-AddCustomer-<% $customer->{'custnum'} %>" VALUE="1" <% scalar(@Customers) == 1 ? 'CHECKED' : '' %>>
- <A HREF="<%$freeside_url%>/view/cust_main.cgi?<% $customer->{'custnum'} %>"><% &RT::URI::freeside::small_custview($customer->{'custnum'}, &RT::URI::freeside::FreesideGetConfig('countrydefault'), 1) |n %>
+ <& .small_custview, $customer &>
+ </td>
+</tr>
+% }
+%
+% foreach my $service (@Services) {
+<tr>
+ <td>
+ <input type="checkbox" name="Ticket-AddService-<% $service->{'svcnum'} %>" VALUE="1" <% scalar(@Services) == 1 ? 'CHECKED' : '' %>>
+ <& .small_custview, $service &>
+ <& .small_svcview, $service &>
</td>
</tr>
% }
@@ -29,11 +39,21 @@
% }
+<%once>
+my $freeside_url = &RT::URI::freeside::FreesideURL();
+
+</%once>
+<%def .small_custview>
+% my $cust = shift;
+<A HREF="<%$freeside_url%>/view/cust_main.cgi?<% $cust->{'custnum'}%>"><% &RT::URI::freeside::small_custview($cust->{'custnum'}, &RT::URI::freeside::FreesideGetConfig, 1) |n %></A>
+</%def>
+<%def .small_svcview>
+% my $svc = shift;
+<A HREF="<%$freeside_url%>/view/cust_svc.cgi?<% $svc->{'svcnum'}%>"><B><% $svc->{'label'} %></B></A>
+</%def>
<%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,
+ );
}
</%INIT>
diff --git a/rt/share/html/Ticket/Elements/EditCustomers b/rt/share/html/Ticket/Elements/EditCustomers
index 0ba6e44..96207f4 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.
<TABLE width=100%>
<TR>
- <TD VALIGN=TOP WIDTH=50%>
+ <TD VALIGN=TOP WIDTH=50% ROWSPAN=3>
<h3><&|/l&>Current Customers</&></h3>
<table>
@@ -25,33 +25,40 @@
<INPUT TYPE=CHECKBOX NAME="DeleteLink--<%$link->Type%>-<%$link->Target%>">
%# <& ShowLink, URI => $link->TargetURI &><br>
- <A HREF="<% $link->TargetURI->Resolver->HREF %>"><% $link->TargetURI->Resolver->AsStringLong |n %></A>
- <BR>
+ <% $link->TargetURI->Resolver->AsStringLong |n %></A>
+ <BR><BR>
% }
</td>
</tr>
</table>
-
+
</TD>
-<TD VALIGN=TOP>
+<TD VALIGN=TOP WIDTH=50% COLSPAN=2>
<h3><&|/l&>New Customer Links</&></h3>
-<&|/l&>Find customer</&><BR>
-<input name="CustomerString">
-<input type=submit name="OnlySearchForCustomers" value="<&|/l&>Go!</&>">
-<br><i>cust #, name, company or phone</i>
-<BR>
-%#<BR>
-%#<&|/l&>Find service</&><BR>
-%#<input name="ServiceString">
-%#<input type=submit name="OnlySearchForServices" value="<&|/l&>Go!</&>">
-%#<br><i>username, username@domain, domain, or IP address</i>
-%#<BR>
-
+</TD>
+</TR>
+<TR VALIGN="top">
+%# rowspan
+ <td width=25%>
+ <&|/l&>Find customer</&><br>
+ <input name="CustomerString">
+ <input type=submit name="OnlySearchForCustomers" value="<&|/l&>Go!</&>">
+ <br><i>cust #, name, company or phone</i>
+ </td>
+ <td width=25%>
+ <&|/l&>Find service</&><br>
+ <input name="ServiceString">
+ <input type=submit name="OnlySearchForServices" value="<&|/l&>Go!</&>">
+ <br><i>user, email, ip, mac, phone</i>
+ </td>
+</TR>
+<TR>
+%#rowspan...
+<TD COLSPAN=2>
<& AddCustomers, Ticket => $Ticket,
CustomerString => $CustomerString,
- ServiceString => $ServiceString, &>
-
+ ServiceString => $ServiceString &>
</TD>
</TR>
</TABLE>
diff --git a/rt/share/html/Ticket/Elements/ShowCustomers b/rt/share/html/Ticket/Elements/ShowCustomers
index 3acf92d..add5624 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.
<table>
-% 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 ) {
<tr>
<td class="value">
- <A HREF="<% $custResolver->HREF %>"><% $custResolver->AsStringLong |n %></A>
+ <% $custResolver->AsStringLong |n %>
+%# includes service label and view/svc_ link for cust_svc links
</td>
</tr>
% }
-% unless ( $cust ) {
+
+% unless ( @cust ) {
<tr>
<td class="labeltop">
<i>(none)<i>