summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FS/FS/AccessRight.pm1
-rw-r--r--FS/FS/Conf.pm9
-rw-r--r--FS/FS/Mason.pm12
-rw-r--r--FS/FS/svc_external.pm1
-rw-r--r--httemplate/pref/pref.html28
-rwxr-xr-xhttemplate/view/cust_main.cgi12
-rw-r--r--httemplate/view/cust_main/change_history.html302
7 files changed, 345 insertions, 20 deletions
diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm
index 3157d5ff0..29cecd5f2 100644
--- a/FS/FS/AccessRight.pm
+++ b/FS/FS/AccessRight.pm
@@ -94,6 +94,7 @@ tie my %rights, 'Tie::IxHash',
'View customer',
#'View Customer | View tickets',
'Edit customer',
+ 'View customer history',
'Cancel customer',
'Complimentary customer', #aka users-allow_comp
{ rightname=>'Delete customer', desc=>"Enable customer deletions. Be very careful! Deleting a customer will remove all traces that this customer ever existed! It should probably only be used when auditing a legacy database. Normally, you cancel all of a customer's packages if they cancel service." }, #aka. deletecustomers
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 0fe3ca977..20abd4546 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -2319,6 +2319,13 @@ worry that config_items is freeside-specific and icky.
},
{
+ 'key' => 'change_history-years',
+ 'section' => 'UI',
+ 'description' => 'Number of years of change history to show by default. Currently defaults to 0.5.',
+ 'type' => 'text',
+ },
+
+ {
'key' => 'cust_main-packages-years',
'section' => 'UI',
'description' => 'Number of years to show old (cancelled and one-time charge) packages by default. Currently defaults to 2.',
@@ -2976,7 +2983,7 @@ worry that config_items is freeside-specific and icky.
'tickets' => 'Tickets',
'packages' => 'Packages',
'payment_history' => 'Payment History',
- #'' => 'Change History',
+ 'change_history' => 'Change History',
'jumbo' => 'Jumbo',
],
},
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index ac11026cf..ed99bf694 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -186,6 +186,16 @@ Initializes the Mason environment, loads all Freeside and RT libraries, etc.
use FS::part_pkg_taxrate;
use FS::tax_rate;
use FS::part_pkg_report_option;
+ use FS::h_cust_pkg;
+ use FS::h_svc_acct;
+ use FS::h_svc_broadband;
+ use FS::h_svc_domain;
+ #use FS::h_domain_record;
+ use FS::h_svc_external;
+ use FS::h_svc_forward;
+ use FS::h_svc_phone;
+ #use FS::h_phone_device;
+ use FS::h_svc_www;
# Sammath Naur
if ( %%%RT_ENABLED%%% ) {
@@ -223,7 +233,7 @@ Initializes the Mason environment, loads all Freeside and RT libraries, etc.
#slow, unreliable, segfaults and is optional
#see rt/html/Ticket/Elements/ShowTransactionAttachments
- #use Text::Quoted;
+ use Text::Quoted;
#?#use File::Path qw( rmtree );
#?#use File::Glob qw( bsd_glob );
diff --git a/FS/FS/svc_external.pm b/FS/FS/svc_external.pm
index 0fb391fef..aca7c1bcc 100644
--- a/FS/FS/svc_external.pm
+++ b/FS/FS/svc_external.pm
@@ -95,6 +95,7 @@ sub label {
substr('0000000000'.uc($self->title), -10);
} else {
#$self->SUPER::label;
+ return $self->id unless $self->title =~ /\S/;
$self->id. ' - '. $self->title;
}
}
diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html
index 8bdf6c09c..562ef2980 100644
--- a/httemplate/pref/pref.html
+++ b/httemplate/pref/pref.html
@@ -124,25 +124,23 @@ Vonage integration (see <a href="https://secure.click2callu.com/">Click2Call</a>
<INPUT TYPE="submit" VALUE="Update preferences">
<% include('/elements/footer.html') %>
-<%once>
-
- #false laziness w/view/cust_main.cgi and Conf.pm (cust_main-default_view)
-
- tie my %customer_views, 'Tie::IxHash',
- 'Basics' => 'basics',
- 'Notes' => 'notes', #notes and files?
- 'Tickets' => 'tickets',
- 'Packages' => 'packages',
- 'Payment History' => 'payment_history',
- #'Change History' => '',
- 'Jumbo' => 'jumbo',
- ;
-
-</%once>
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
+#false laziness w/view/cust_main.cgi and Conf.pm (cust_main-default_view)
+
+tie my %customer_views, 'Tie::IxHash',
+ 'Basics' => 'basics',
+ 'Notes' => 'notes', #notes and files?
+ 'Tickets' => 'tickets',
+ 'Packages' => 'packages',
+ 'Payment History' => 'payment_history',
+;
+$customer_views{'Change History'} = 'change_history'
+ if $curuser->access_right('View customer history');
+$customer_views{'Jumbo'} = 'jumbo';
+
# XSS via your own preferences? seems unlikely, but nice try anyway...
( $curuser->option('menu_position') || 'top' )
=~ /^(\w+)$/ or die "illegal menu_position";
diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi
index 88fd03713..78bcb1fc1 100755
--- a/httemplate/view/cust_main.cgi
+++ b/httemplate/view/cust_main.cgi
@@ -113,6 +113,7 @@ Comments
% if ( ! $conf->exists('cust_main-disable_notes') || $notecount) {
% unless ( $view eq 'notes' && $cust_main->comments !~ /[^\s\n\r]/ ) {
+ <BR>
<A NAME="cust_main_note"><FONT SIZE="+2">Notes</FONT></A><BR>
% }
@@ -180,6 +181,10 @@ Comments
% }
+% if ( $view eq 'change_history' ) { # || $view eq 'jumbo'
+ <% include('cust_main/change_history.html', $cust_main ) %>
+% }
+
<% include('/elements/footer.html') %>
<%init>
@@ -213,11 +218,12 @@ tie my %views, 'Tie::IxHash',
'Notes' => 'notes', #notes and files?
;
$views{'Tickets'} = 'tickets'
- if $conf->config('ticket_system');
+ if $conf->config('ticket_system');
$views{'Packages'} = 'packages';
$views{'Payment History'} = 'payment_history'
- unless $conf->config('payby-default' eq 'HIDE');
-#$views{'Change History'} = '';
+ unless $conf->config('payby-default' eq 'HIDE');
+$views{'Change History'} = 'change_history'
+ if $curuser->access_right('View customer history');
$views{'Jumbo'} = 'jumbo';
my %viewname = reverse %views;
diff --git a/httemplate/view/cust_main/change_history.html b/httemplate/view/cust_main/change_history.html
new file mode 100644
index 000000000..1700bc34b
--- /dev/null
+++ b/httemplate/view/cust_main/change_history.html
@@ -0,0 +1,302 @@
+% if ( int( time - (keys %years)[0] * 31556736 ) > $start ) {
+ Show:
+% my $chy = $cgi->param('change_history-years');
+% foreach my $y (keys %years) {
+% if ( $y == $years ) {
+ <FONT SIZE="+1"><% $years{$y} %></FONT>
+% } else {
+% $cgi->param('change_history-years', $y);
+ <A HREF="<% $cgi->self_url %>"><% $years{$y} %></A>
+% }
+% last if int( time - $y * 31556736 ) < $start;
+% }
+% $cgi->param('change_history-years', $chy);
+% }
+
+<% include("/elements/table-grid.html") %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">User</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Date</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Time</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Item</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Action</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Description</TH>
+</TR>
+
+% foreach my $item ( sort { $a->history_date <=> $b->history_date
+% #|| table order
+% || $a->historynum <=> $b->historynum
+% }
+% @history
+% )
+% {
+%
+% my $history_other = '';
+% my $act = $item->history_action;
+% if ( $act =~ /^replace/ ) {
+% my $pkey = $item->primary_key;
+% my $date = $item->history_date;
+% $history_other = qsearchs({
+% 'table' => $item->table,
+% 'hashref' => { $pkey => $item->$pkey(),
+% 'history_action' => $replace_other{$act},
+% 'historynum' => { 'op' => $replace_dir{$act},
+% 'value' => $item->historynum
+% },
+% },
+% 'extra_sql' => "
+% AND history_date $replace_direq{$act} $date
+% AND ($date $replace_op{$act} $fuzz) $replace_direq{$act} history_date
+% ORDER BY historynum $replace_ord{$act} LIMIT 1
+% ",
+% });
+% }
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+ <TR>
+ <TD ALIGN="left" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $otaker = $item->history_user;
+% $otaker = '<i>auto billing</i>' if $otaker eq 'fs_daily';
+% $otaker = '<i>customer self-service</i>' if $otaker eq 'fs_selfservice';
+% $otaker = '<i>job queue</i>' if $otaker eq 'fs_queue';
+ <% $otaker %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $d = time2str('%b %o, %Y', $item->history_date );
+% $d =~ s/ /&nbsp;/g;
+ <% $d %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $t = time2str('%r', $item->history_date );
+% $t =~ s/ /&nbsp;/g;
+ <% $t %>
+ </TD>
+ <TD ALIGN="center" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $label = $h_tables{$item->table};
+% $label = &{ $h_table_labelsub{$item->table} }( $item, $label )
+% if $h_table_labelsub{$item->table};
+ <% $label %>
+ </TD>
+ <TD ALIGN="left" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $action{$item->history_action} %>
+ </TD>
+ <TD ALIGN="left" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% join(', ',
+ map { my $value = ( $_ =~ /(^pay(info|cvv)|^ss|_password)$/ )
+ ? 'N/A'
+ : $item->get($_);
+ $value = substr($value, 0, 77).'...' if length($value) > 80;
+ $value = encode_entities($value);
+ "<I>$_</I>:<B>$value</B>";
+ }
+ grep { $history_other
+ ? ( $item->get($_) ne $history_other->get($_) )
+ : ( $item->get($_) =~ /\S/ )
+ }
+ grep { ! /^(history|custnum$)/i }
+ $item->fields
+ )
+ %>
+ </TD>
+ </TR>
+
+% }
+
+</TABLE>
+<%once>
+
+# length-switching
+
+tie my %years, 'Tie::IxHash',
+ .5 => '6 months',
+ 1 => '1 year',
+ 2 => '2 years',
+ 5 => '5 years',
+ 39 => 'all history',
+;
+
+# labeling history rows
+
+my %action = (
+ 'insert' => 'Insert', #'Create',
+ 'replace_old' => 'Change&nbsp;from',
+ 'replace_new' => 'Change&nbsp;to',
+ 'delete' => 'Remove',
+);
+
+# finding the other replace row
+
+my %replace_other = (
+ 'replace_new' => 'replace_old',
+ 'replace_old' => 'replace_new',
+);
+my %replace_dir = (
+ 'replace_new' => '<',
+ 'replace_old' => '>',
+);
+my %replace_direq = (
+ 'replace_new' => '<=',
+ 'replace_old' => '>=',
+);
+my %replace_op = (
+ 'replace_new' => '-',
+ 'replace_old' => '+',
+);
+my %replace_ord = (
+ 'replace_new' => 'DESC',
+ 'replace_old' => 'ASC',
+);
+
+my $fuzz = 5; #seems like a lot
+
+# which tables to search and what to call them
+
+tie my %tables, 'Tie::IxHash',
+ 'cust_main' => 'Customer',
+ 'cust_main_invoice' => 'Invoice destination',
+ 'cust_pkg' => 'Package',
+ #? or just svc_* ? 'cust_svc' =>
+ 'svc_acct' => 'Account',
+ 'radius_usergroup' => 'RADIUS group',
+ 'svc_domain' => 'Domain',
+ 'svc_www' => 'Hosting',
+ 'svc_forward' => 'Mail forward',
+ 'svc_broadband' => 'Broadband',
+ 'svc_external' => 'External service',
+ 'svc_phone' => 'Phone',
+ 'phone_device' => 'Phone device',
+ #? it gets provisioned anyway 'phone_avail' => 'Phone',
+;
+
+my $svc_join = 'JOIN cust_svc USING ( svcnum ) JOIN cust_pkg USING ( pkgnum )';
+
+my %table_join = (
+ 'svc_acct' => $svc_join,
+ 'radius_usergroup' => $svc_join,
+ 'svc_domain' => $svc_join,
+ 'svc_www' => $svc_join,
+ 'svc_forward' => $svc_join,
+ 'svc_broadband' => $svc_join,
+ 'svc_external' => $svc_join,
+ 'svc_phone' => $svc_join,
+ 'phone_device' => $svc_join,
+);
+
+my %h_tables = map { ( "h_$_" => $tables{$_} ) } keys %tables;
+
+my %pkgpart = ();
+my $pkg_labelsub = sub {
+ my($item, $label) = @_;
+ $pkgpart{$item->pkgpart} ||= $item->part_pkg->pkg;
+ $label. ': <b>'. encode_entities($pkgpart{$item->pkgpart}). '</b>';
+};
+
+my $svc_labelsub = sub {
+ my($item, $label) = @_;
+ $label. ': <b>'. encode_entities($item->label). '</b>';
+};
+
+my %h_table_labelsub = (
+ 'h_cust_pkg' => $pkg_labelsub,
+ 'h_svc_acct' => $svc_labelsub,
+ #'h_radius_usergroup' =>
+ 'h_svc_domain' => $svc_labelsub,
+ 'h_svc_www' => $svc_labelsub,
+ 'h_svc_forward' => $svc_labelsub,
+ 'h_svc_broadband' => $svc_labelsub,
+ 'h_svc_external' => $svc_labelsub,
+ 'h_svc_phone' => $svc_labelsub,
+ #'h_phone_device'
+);
+
+# cust_main
+# cust_main_invoice
+
+# cust_pkg
+# cust_pkg_option?
+# cust_pkg_detail
+# cust_pkg_reason? no
+
+#cust_svc
+#cust_svc_option?
+#svc_*
+# svc_acct
+# radius_usergroup
+# acct_snarf? is this even used?
+# svc_domain
+# domain_record
+# registrar
+# svc_forward
+# svc_www
+# svc_broadband
+# (virtual fields? eh... maybe when they're real)
+# svc_external
+# svc_phone
+# phone_device
+# phone_avail
+
+# future:
+
+# inventory_item (from services)
+# pkg_referral? (changed?)
+
+#random others:
+
+# cust_location?
+# cust_main-exemption?? (295.ca named tax exemptions)
+
+</%once>
+<%init>
+
+my( $cust_main ) = @_;
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access deined"
+ unless $curuser->access_right('View customer history');
+
+# find out the beginning of this customer history, if possible
+my $h_insert = qsearchs({
+ 'table' => 'h_cust_main',
+ 'hashref' => { 'custnum' => $cust_main->custnum,
+ 'history_action' => 'insert',
+ },
+ 'extra_sql' => 'ORDER BY historynum LIMIT 1',
+});
+my $start = $h_insert ? $h_insert->history_date : 0;
+
+# retreive the history
+
+my @history = ();
+
+my $years = $conf->config('change_history-years') || .5;
+if ( $cgi->param('change_history-years') =~ /^([\d\.]+)$/ ) {
+ $years = $1;
+}
+my $newer_than = int( time - $years * 31556736 ); #60*60*24*365.24
+
+local($FS::Record::nowarn_classload) = 1;
+
+foreach my $table ( keys %tables ) {
+ my @items = qsearch({
+ 'table' => "h_$table",
+ 'addl_from' => $table_join{$table},
+ 'hashref' => { 'history_date' => { op=>'>=', value=>$newer_than }, },
+ 'extra_sql' => ' AND custnum = '. $cust_main->custnum,
+ });
+ push @history, @items;
+
+}
+
+</%init>