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