summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormark <mark>2010-10-25 22:22:42 +0000
committermark <mark>2010-10-25 22:22:42 +0000
commit0a51b87d75f8c93de863f729ef1ca568ca227e32 (patch)
tree17305873562562672fa2239fff869fc38b2fafa7
parent716be4fdacc0995d54a0df8c77268606f23fa768 (diff)
summary display of bulk packages with many services, RT#9821
-rw-r--r--FS/FS/Conf.pm7
-rw-r--r--FS/FS/part_pkg/bulk.pm27
-rw-r--r--FS/FS/part_pkg/voip_cdr.pm30
-rwxr-xr-xhttemplate/misc/unprovision.cgi32
-rw-r--r--httemplate/search/cust_pkg_svc.html117
-rw-r--r--httemplate/search/cust_svc.html6
-rwxr-xr-xhttemplate/view/cust_main/packages.html1
-rw-r--r--httemplate/view/cust_main/packages/services.html44
-rw-r--r--httemplate/view/elements/svc_Common.html16
9 files changed, 251 insertions, 29 deletions
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index b0b44e262..72e38d3d2 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -2632,6 +2632,13 @@ and customer address. Include units.',
},
{
+ 'key' => 'cust_pkg-large_pkg_size',
+ 'section' => 'UI',
+ 'description' => "In customer view, summarize packages with more than this many services. Set to zero to never summarize packages.",
+ 'type' => 'text',
+ },
+
+ {
'key' => 'svc_acct-edit_uid',
'section' => 'shell',
'description' => 'Allow UID editing.',
diff --git a/FS/FS/part_pkg/bulk.pm b/FS/FS/part_pkg/bulk.pm
index 69fe98e92..a346b9096 100644
--- a/FS/FS/part_pkg/bulk.pm
+++ b/FS/FS/part_pkg/bulk.pm
@@ -29,9 +29,13 @@ $me = '[FS::part_pkg::bulk]';
' of service at cancellation',
'type' => 'checkbox',
},
+ 'summarize_svcs'=> { 'name' => 'Show a count of services on the invoice, '.
+ 'instead of a detailed list',
+ 'type' => 'checkbox',
+ },
},
'fieldorder' => [ 'setup_fee', 'recur_fee', 'svc_setup_fee', 'svc_recur_fee',
- 'unused_credit', ],
+ 'unused_credit', 'summarize_svcs' ],
'weight' => 50,
);
@@ -50,6 +54,11 @@ sub calc_recur {
unless $$sdate > $last_bill;
my $total_svc_charge = 0;
+ my %n_setup = ();
+ my %n_recur = ();
+ my %part_svc_label = ();
+
+ my $summarize = $self->option('summarize_svcs',1);
warn "$me billing for bulk services from ". time2str('%x', $last_bill).
" to ". time2str('%x', $$sdate). "\n"
@@ -61,6 +70,7 @@ sub calc_recur {
my @label = $h_cust_svc->label_long( $$sdate, $last_bill );
die "fatal: no historical label found, wtf?" unless scalar(@label); #?
my $svc_details = $label[0]. ': '. $label[1]. ': ';
+ $part_svc_label{$h_cust_svc->svcpart} ||= $label[0];
my $svc_charge = 0;
@@ -70,6 +80,7 @@ sub calc_recur {
} elsif ( $svc_setup_fee ) {
$svc_charge += $svc_setup_fee;
$svc_details .= $money_char. sprintf('%.2f setup, ', $svc_setup_fee);
+ $n_setup{$h_cust_svc->svcpart}++;
}
my $svc_end = $h_cust_svc->date_deleted;
@@ -85,11 +96,21 @@ sub calc_recur {
if $recur_charge;
$svc_charge += $recur_charge;
-
- push @$details, $svc_details;
+ $n_recur{$h_cust_svc->svcpart}++;
+ push @$details, $svc_details if !$summarize;
$total_svc_charge += $svc_charge;
}
+ if ( $summarize ) {
+ foreach my $svcpart (keys %part_svc_label) {
+ push @$details, sprintf('Setup fee: %d @ '.$money_char.'%.2f',
+ $n_setup{$svcpart}, $svc_setup_fee )
+ if $svc_setup_fee and $n_setup{$svcpart};
+ push @$details, sprintf('%d services @ '.$money_char.'%.2f',
+ $n_recur{$svcpart}, $self->option('svc_recur_fee') )
+ if $n_recur{$svcpart};
+ }
+ }
sprintf('%.2f', $self->base_recur($cust_pkg) + $total_svc_charge );
}
diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm
index 9981da0c9..bcc2c6bc6 100644
--- a/FS/FS/part_pkg/voip_cdr.pm
+++ b/FS/FS/part_pkg/voip_cdr.pm
@@ -226,6 +226,10 @@ tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities();
'type' => 'checkbox',
},
+ 'bill_inactive_svcs' => { 'name' => 'Bill for all phone numbers that were active during the billing period',
+ 'type' => 'checkbox',
+ },
+
'count_available_phones' => { 'name' => 'Consider for tax purposes the number of lines to be svc_phones that may be provisioned rather than those that actually are.',
'type' => 'checkbox',
},
@@ -278,7 +282,7 @@ tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities();
use_duration
411_rewrite
output_format usage_mandate summarize_usage usage_section
- bill_every_call
+ bill_every_call bill_inactive_svcs
count_available_phones
)
],
@@ -365,11 +369,25 @@ sub calc_usage {
my($svc_table, $svc_field) = split('\.', $cdr_svc_method);
- foreach my $cust_svc (
- grep { $_->part_svc->svcdb eq $svc_table } $cust_pkg->cust_svc
- ) {
+ my @cust_svc;
+ if( $self->option('bill_inactive_svcs',1) ) {
+ #XXX in this mode do we need to restrict the set of CDRs by date also?
+ @cust_svc = $cust_pkg->h_cust_svc($$sdate, $last_bill);
+ }
+ else {
+ @cust_svc = $cust_pkg->cust_svc;
+ }
+ @cust_svc = grep { $_->part_svc->svcdb eq $svc_table } @cust_svc;
+
+ foreach my $cust_svc (@cust_svc) {
- my $svc_x = $cust_svc->svc_x;
+ my $svc_x;
+ if( $self->option('bill_inactive_svcs',1) ) {
+ $svc_x = $cust_svc->h_svc_x($$sdate, $last_bill);
+ }
+ else {
+ $svc_x = $cust_svc->svc_x;
+ }
my %options = (
'disable_src' => $self->option('disable_src'),
'default_prefix' => $self->option('default_prefix'),
@@ -709,7 +727,7 @@ sub calc_usage {
if ( $charge > 0 ) {
#just use FS::cust_bill_pkg_detail objects?
my $call_details;
- my $phonenum = $cust_svc->svc_x->phonenum;
+ my $phonenum = $svc_x->phonenum;
if ( scalar(@call_details) == 1 ) {
$call_details =
diff --git a/httemplate/misc/unprovision.cgi b/httemplate/misc/unprovision.cgi
index 4ab15fdc0..6f2c23815 100755
--- a/httemplate/misc/unprovision.cgi
+++ b/httemplate/misc/unprovision.cgi
@@ -1,6 +1,8 @@
%if ( $error ) {
% errorpage($error);
-%} else {
+%} elsif ( $pkgnum ) {
+<% $cgi->redirect(popurl(2)."search/cust_pkg_svc.html?svcpart=$svcpart;pkgnum=$pkgnum") %>
+%} else { # $custnum should always exist
<% $cgi->redirect(popurl(2)."view/cust_main.cgi?$custnum") %>
%}
<%init>
@@ -9,18 +11,28 @@ die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Unprovision customer service');
#untaint svcnum
-my($query) = $cgi->keywords;
-$query =~ /^(\d+)$/;
-my $svcnum = $1;
+my @svcnums;
+my ($pkgnum, $svcpart, $custnum);
+if( $cgi->param('svcnum') ) {
+ @svcnums = grep { $_ } map { /^(\d+)$/ && $1 } $cgi->param('svcnum');
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $cgi->param('svcpart');
+ $custnum = $cgi->param('custnum');
+}
+else {
+ @svcnums = map { /^(\d+)$/ && $1 } $cgi->keywords;
+}
-#my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum});
-#die "Unknown svcnum!" unless $svc_acct;
+my $error = '';
+foreach my $svcnum (@svcnums) {
-my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
-die "Unknown svcnum!" unless $cust_svc;
+ my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
+ die "Unknown svcnum!" unless $cust_svc;
-my $custnum = $cust_svc->cust_pkg->custnum;
+ $custnum ||= $cust_svc->cust_pkg->custnum;
-my $error = $cust_svc->cancel;
+ $error .= $cust_svc->cancel;
+
+}
</%init>
diff --git a/httemplate/search/cust_pkg_svc.html b/httemplate/search/cust_pkg_svc.html
new file mode 100644
index 000000000..4f27d6617
--- /dev/null
+++ b/httemplate/search/cust_pkg_svc.html
@@ -0,0 +1,117 @@
+<% include( 'elements/search.html',
+ 'title' => $part_svc->svc.' services in package #'.$pkgnum,
+ 'name' => 'services',
+ 'html_form' => $html_form,
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'redirect' => $link,
+ 'header' => [ '#',
+ 'Service',
+ '', #checkboxes
+ ],
+ 'fields' => [ 'svcnum',
+ sub {
+ ($_[0]->label)[1]
+ },
+ sub {
+ $areboxes = 1;
+ '<INPUT TYPE="checkbox" NAME="svcnum" VALUE='.$_[0]->svcnum.'>'
+ },
+ ],
+ 'links' => [ $link,
+ $link,
+ '',
+ ],
+ 'align' => 'rrlc',
+ 'color' => [
+ ('')x4,
+ ],
+ 'style' => [
+ ('')x4,
+ ],
+ 'html_foot' => sub { $areboxes ? $html_foot : '' }
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List services');
+
+my $pkgnum = $cgi->param('pkgnum');
+$pkgnum =~ /^(\d+)$/ or die "invalid pkgnum: $pkgnum";
+my @extra_sql = ( "cust_svc.pkgnum = $pkgnum" );
+
+my $svcpart = $cgi->param('svcpart');
+$svcpart =~ /^(\d+)$/ or die "invalid svcpart: $svcpart";
+push @extra_sql, "cust_svc.svcpart = $svcpart";
+my $part_svc = qsearchs('part_svc', {svcpart => $svcpart});
+my $svcdb = $part_svc->svcdb;
+
+my $orderby = 'ORDER BY svcnum'; #others?
+
+my $addl_from = " LEFT JOIN part_svc USING (svcpart)
+LEFT JOIN cust_pkg USING (pkgnum)
+LEFT JOIN cust_main USING (custnum)
+INNER JOIN $svcdb USING (svcnum)";
+
+my $search_string;
+if ( length( $cgi->param('search_svc') ) ) {
+
+ $search_string = $cgi->param('search_svc');
+ $search_string =~ s/(^\s+|\s+$)//;
+ push @extra_sql, "FS::$svcdb"->search_sql($search_string);
+
+}
+
+#here is the agent virtualization
+push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ );
+
+my $extra_sql = ' WHERE '. join(' AND ', @extra_sql );
+
+my $sql_query = {
+ 'select' => join(', ',
+ 'cust_svc.*',
+ 'part_svc.svc',
+ ),
+ 'table' => 'cust_svc',
+ 'addl_from' => $addl_from,
+ 'hashref' => {},
+ 'extra_sql' => "$extra_sql $orderby",
+};
+
+#warn Dumper($sql_query)."\n";
+
+my $count_query = "SELECT COUNT(*) FROM cust_svc $addl_from $extra_sql";
+
+my $link = sub {
+ my $cust_svc = shift;
+ my $url = svc_url(
+ 'm' => $m,
+ 'action' => 'view',
+ 'svcdb' => $svcdb,
+ 'query' => '',
+ );
+ [ $url, 'svcnum' ];
+};
+
+my $html_form = qq!
+<SCRIPT TYPE="text/javascript">
+function areyousure(obj) {
+ return confirm('Permanently delete the selected services?');
+}
+</SCRIPT>
+<FORM METHOD="POST" ACTION="${p}misc/unprovision.cgi" onsubmit="return areyousure()">!;
+
+my $areboxes = 0;
+
+my $html_foot = qq!
+<BR>
+<INPUT TYPE="submit" NAME="submit" VALUE="Unprovision selected">
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE=$pkgnum>
+<INPUT TYPE="hidden" NAME="svcpart" VALUE=$svcpart>
+</FORM>!;
+
+
+</%init>
diff --git a/httemplate/search/cust_svc.html b/httemplate/search/cust_svc.html
index 2c17561f2..61bfc7091 100644
--- a/httemplate/search/cust_svc.html
+++ b/httemplate/search/cust_svc.html
@@ -93,6 +93,10 @@ if ( length( $cgi->param('search_svc') ) ) {
errorpage("No search term specified");
}
+if ( $cgi->param('pkgnum') =~ /^(\d+)$/ ) {
+ push @extra_sql, "cust_svc.pkgnum = $1";
+}
+
#here is the agent virtualization
push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
'null_right' => 'View/link unlinked services'
@@ -113,6 +117,8 @@ my $sql_query = {
'extra_sql' => "$extra_sql $orderby",
};
+warn Dumper($sql_query)."\n";
+
my $count_query = "SELECT COUNT(*) FROM cust_svc $addl_from $extra_sql";
my $link = sub {
diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html
index f0f156bf8..660d0ef86 100755
--- a/httemplate/view/cust_main/packages.html
+++ b/httemplate/view/cust_main/packages.html
@@ -163,6 +163,7 @@ my %conf_opt = (
'legacy_link' => $conf->exists('legacy_link'),
'svc_broadband-manage_link' => scalar($conf->config('svc_broadband-manage_link')),
'maestro-status_test' => $conf->exists('maestro-status_test'),
+ 'cust_pkg-large_pkg_size' => $conf->config('cust_pkg-large_pkg_size'),
);
#subroutines
diff --git a/httemplate/view/cust_main/packages/services.html b/httemplate/view/cust_main/packages/services.html
index 6e30922c5..512efccc4 100644
--- a/httemplate/view/cust_main/packages/services.html
+++ b/httemplate/view/cust_main/packages/services.html
@@ -4,12 +4,40 @@
<TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
<TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%">
+ <SCRIPT TYPE="text/javascript">
+function clearhint_search_cust_svc(obj, str) {
+ if (obj.value == str) obj.value = '';
+}
+ </SCRIPT>
% #foreach my $svcpart (sort {$a->{svcpart} <=> $b->{svcpart}} @{$pkg->{svcparts}}) {
% foreach my $part_svc ( $cust_pkg->part_svc ) {
-% #foreach my $service (@{$svcpart->{services}}) {
-% foreach my $cust_svc ( @{ $part_svc->cust_pkg_svc } ) {
+% if ( $opt{'cust_pkg-large_pkg_size'} > 0 and
+% $opt{'cust_pkg-large_pkg_size'} <= $cust_pkg->num_svcs ) {
+% # summarize
+ <TR>
+ <TD ALIGN="center" VALIGN="top">
+% my $href="${p}search/cust_pkg_svc.html?svcpart=".$part_svc->svcpart.
+% ";pkgnum=".$cust_pkg->pkgnum;
+ <A HREF="<% $href %>"><% $part_svc->svc %></A>&nbsp;
+ <A HREF="<% $href %>"><B>(view all <% $cust_pkg->num_svcs %>)</B></A>
+% my $hint = $hints{$part_svc->svcdb};
+% if ( $hint ) {
+ <BR>
+ <FORM name="svcpart<%$part_svc->svcpart%>_search" STYLE="display:inline"
+ ACTION="<%$p%>search/cust_pkg_svc.html" METHOD="GET">
+ <INPUT TYPE="hidden" NAME="svcpart" VALUE="<%$part_svc->svcpart%>">
+ <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%$cust_pkg->pkgnum%>">
+ <INPUT TYPE="text" NAME="search_svc"
+ onfocus="clearhint_search_cust_svc(this, '<%$hint%>')" VALUE="<%$hint%>">
+ <INPUT TYPE="submit" VALUE="Search"></FORM>
+% } #$hint
+ </TD>
+ </TR>
+% }
+% else {
+% foreach my $cust_svc ( @{ $part_svc->cust_pkg_svc } ) {
<TR>
<TD ALIGN="right" VALIGN="top"><% FS::UI::Web::svc_link($m, $part_svc, $cust_svc) %></TD>
@@ -65,7 +93,8 @@
</TD>
</TR>
-% }
+% } #foreach $cust_svc
+% }
% if ( ! $cust_pkg->get('cancel')
% && $curuser->access_right('Provision customer service')
@@ -137,4 +166,13 @@ sub svc_unprovision_link {
qq!', 'Permanently unprovision and delete this service?')">Unprovision</A>!;
}
+my %hints = (
+svc_acct => '(user or email)',
+svc_domain => '(domain)',
+svc_broadband => '(ip or mac)',
+svc_forward => '(email)',
+svc_phone => '(phone)',
+svc_pbx => '(phone)',
+);
+
</%init>
diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html
index 852640e0c..8a352f3fa 100644
--- a/httemplate/view/elements/svc_Common.html
+++ b/httemplate/view/elements/svc_Common.html
@@ -21,6 +21,13 @@
)
</%doc>
+<SCRIPT>
+function areyousure(href) {
+ if (confirm("Permanently delete this <% $label %>?") == true)
+ window.location.href = href;
+}
+</SCRIPT>
+
% if ( $custnum ) {
<% include("/elements/header.html","View $label: $value") %>
@@ -36,18 +43,13 @@
"javascript:areyousure(\'${p}misc/cancel-unaudited.cgi?$svcnum\')"
)) %>
- <SCRIPT>
- function areyousure(href) {
- if (confirm("Permanently delete this <% $label %>?") == true)
- window.location.href = href;
- }
- </SCRIPT>
-
% }
Service #<B><% $svcnum %></B>
% my $url = $opt{'edit_url'} || $p. 'edit/'. $opt{'table'}. '.cgi?';
| <A HREF="<%$url%><%$svcnum%>">Edit this <% $label %></A>
+| <A HREF="javascript:areyousure('<%$p.'misc/unprovision.cgi?'.$svcnum%>')">
+Unprovision this Service</A>
<BR>
<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>