diff options
Diffstat (limited to 'httemplate')
-rw-r--r-- | httemplate/edit/elements/rate_detail.html | 102 | ||||
-rw-r--r-- | httemplate/edit/process/rate_detail.html | 32 | ||||
-rw-r--r-- | httemplate/edit/rate_detail.html | 22 | ||||
-rw-r--r-- | httemplate/graph/cust_pkg.html (renamed from httemplate/graph/cust_pkg.cgi) | 44 | ||||
-rw-r--r-- | httemplate/graph/elements/monthly.html | 16 | ||||
-rw-r--r-- | httemplate/graph/elements/report.html | 6 | ||||
-rw-r--r-- | httemplate/graph/report_cust_pkg.html | 8 | ||||
-rw-r--r-- | httemplate/search/cust_pkg_churn.html | 186 |
8 files changed, 343 insertions, 73 deletions
diff --git a/httemplate/edit/elements/rate_detail.html b/httemplate/edit/elements/rate_detail.html index 14b52110b..7b5ec314a 100644 --- a/httemplate/edit/elements/rate_detail.html +++ b/httemplate/edit/elements/rate_detail.html @@ -47,34 +47,85 @@ with row headers showing the region name and prefixes. % } % foreach my $rate_time (@rate_time, '') { <TD> -% my $detail = $details[$row][$col]; -% if($detail) { + <& .detail_box, + detail => $details[$row][$col], + ratetimenum => ($rate_time ? $rate_time->ratetimenum : ''), + cdrtypenum => $cdrtypenum, + regionnum => $region->regionnum, + ratenum => $rate->ratenum + &> +% $col++; + </TD> +% } # foreach @rate_time +</TR> +% $row++; +% }# foreach @rate_region +% if ( !$opt{regionnum} ) { +%# global default +<TR> + <TD COLSPAN=2 STYLE="padding-top: 10px"> + <B>Global default</B> (for calls not matching any prefix) + </TD> + <TD STYLE="padding-top: 10px"> +% # default rate: set a null region + <B> + <& .detail_box, + detail => $rate->default_detail, + ratetimenum => '', + cdrtypenum => '', + regionnum => '', + ratenum => $rate->ratenum + &> + </B> + </TD> +% } +</TABLE> +<%def .detail_box> +<%args> +$detail => undef, +$ratetimenum +$cdrtypenum +$regionnum +$ratenum +</%args> +% if ($detail) { <TABLE CLASS="inv" STYLE="border:none"> - <TR><TD><% edit_link($detail) %><% $money_char.$detail->min_charge %> + <TR><TD><% edit_link($detail) %> +% if ( $detail->min_charge > 0 or $detail->conn_charge > 0) { + <% $money_char.$detail->min_charge %> <% $detail->sec_granularity ? ' / minute':' / call' %> % if ( $detail->min_cost ) { (<% $money_char.$detail->min_cost %> cost) % } +% if ( $detail->upstream_mult_charge > 0 +% or $detail->upstream_mult_cost > 0) { + <BR>+ +% } +% } +% if ( $detail->upstream_mult_charge > 0 +% or $detail->upstream_mult_cost > 0) { + <% $detail->upstream_mult_charge %> × upstream price +% if ( $detail->upstream_mult_cost > 0 ) { + (<% $detail->upstream_mult_cost %> cost) +% } +% } +% if ( $detail->upstream_mult_charge == 0 +% and $detail->min_charge == 0 +% and $detail->conn_charge == 0 ) { + Free +% } <% $edit_hint %></A> </TD></TR> <% granularity_detail($detail) %> <% min_included_detail($detail) %> <% conn_charge_detail($detail) %> - <TR><TD><% ( $rate_time || $cdrtypenum ) ? delete_link($detail) : '' %> + <TR><TD><% ( $ratetimenum || $cdrtypenum ) ? delete_link($detail) : '' %> </TD></TR> </TABLE> -% } -% else { #!$detail - <% add_link($rate, $region, $rate_time, $cdrtypenum) %> -% } -% $col++; - </TD> -% } # foreach @rate_time -</TR> -% $row++; -% }# foreach @rate_region -</TABLE> - +% } else { + <% add_link($ratenum, $regionnum, $ratetimenum, $cdrtypenum) %> +% } +</%def> <%once> tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities(); @@ -95,25 +146,27 @@ sub edit_link { include( '/elements/popup_link_onclick.html', 'action' => "${p}edit/rate_detail.html?$ratedetailnum", 'actionlabel' => 'Edit rate', - 'height' => 460, + 'height' => 550, + 'width' => 580, #default# 'width' => 540, #default# 'color' => '#333399', ) . '">' } sub add_link { - my ($rate, $region, $rate_time, $cdrtypenum) = @_; + my ($ratenum, $regionnum, $ratetimenum, $cdrtypenum) = @_; '<A HREF="javascript:void(0);" onclick="'. include( '/elements/popup_link_onclick.html', 'action' => "${p}edit/rate_detail.html?ratenum=". - $rate->ratenum. + $ratenum. ';dest_regionnum='. - $region->regionnum. + $regionnum. ';ratetimenum='. - ($rate_time ? $rate_time->ratetimenum : ''). + ($ratetimenum || ''). ";cdrtypenum=$cdrtypenum", 'actionlabel' => 'Add rate', - 'height' => 460, + 'width' => 580, + 'height' => 550, ).'">'.small('(add)').'</A>' } @@ -133,7 +186,10 @@ sub delete_link { sub granularity_detail { my $rate_detail = shift; - if($rate_detail->sec_granularity != 60 && $rate_detail->sec_granularity > 0) { + if( + $rate_detail->sec_granularity != 60 + && $rate_detail->sec_granularity > 0 + && $rate_detail->min_charge > 0) { '<TR><TD>'. small('in '.$granularity{$rate_detail->sec_granularity}.' increments'). '</TD></TR>'; diff --git a/httemplate/edit/process/rate_detail.html b/httemplate/edit/process/rate_detail.html index 6200d615f..0709d5079 100644 --- a/httemplate/edit/process/rate_detail.html +++ b/httemplate/edit/process/rate_detail.html @@ -1,13 +1,35 @@ -<% include( 'elements/process.html', - 'table' => 'rate_detail', - 'popup_reload' => 'Rate changed', #a popup "parent reload" for now +<& elements/process.html, + 'table' => 'rate_detail', + 'popup_reload' => 'Rate changed', #a popup "parent reload" for now #someday change the individual element and go away instead - ) -%> + 'noerror_callback' => $set_default_detail +&> <%init> my $conf = new FS::Conf; die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); +my $set_default_detail = sub { + my ($cgi, $rate_detail) = @_; +warn Dumper $rate_detail; + if (!$rate_detail->dest_regionnum) { + # then this is a global default rate + my $rate = $rate_detail->rate; + if ($rate->default_detailnum) { + if ($rate->default_detailnum == $rate_detail->ratedetailnum) { + return; + } else { + # there's somehow an existing default rate. remove it. + my $old_default = $rate->default_detail; + my $error = $old_default->delete; + die "$error (removing old default rate)\n" if $error; + } + } + $rate->set('default_detailnum' => $rate_detail->ratedetailnum); + my $error = $rate->replace; + die "$error (setting default rate)\n" if $error; + } +}; + </%init> diff --git a/httemplate/edit/rate_detail.html b/httemplate/edit/rate_detail.html index 0de6ecc1e..3e800726e 100644 --- a/httemplate/edit/rate_detail.html +++ b/httemplate/edit/rate_detail.html @@ -15,6 +15,8 @@ 'conn_cost' => 'Wholesale connection cost', 'min_cost' => 'Wholesale cost per minute/call', 'classnum' => 'Usage class', + 'upstream_mult_charge'=> 'Upstream multiplier (retail)', + 'upstream_mult_cost' => 'Upstream multiplier (cost)', }, 'fields' => [ { field=>'ratenum', type=>'hidden', }, @@ -46,13 +48,15 @@ labels => \%granularity, disable_empty => 1, }, - { field =>'classnum', - type =>'select-table', - table =>'usage_class', - name_col =>'classname', - empty_label =>'(default)', - hashref =>{ disabled => '' }, + { field => 'classnum', + type => 'select-table', + table => 'usage_class', + name_col => 'classname', + empty_label => '(default)', + hashref => { disabled => '' }, }, + { field => 'upstream_mult_charge', type => 'text', }, + { field => 'upstream_mult_cost', type => 'text', }, ], 'new_hashref_callback' => sub { @@ -62,6 +66,8 @@ cdrtypenum => scalar($cgi->param('cdrtypenum')), min_included => 0, conn_charge => 0, + upstream_mult_charge => 0, + upstream_mult_cost => 0, } }, ) @@ -85,8 +91,8 @@ if ( $keywords =~ /^(\d+)$/ || $cgi->param('ratedetailnum') =~ /^(\d+)$/ ) { my $rate_detail = qsearchs('rate_detail', { 'ratedetailnum' => $1 } ) or die "unknown ratedetailnum $1"; - $name = - $rate_detail->rate->ratename. ' rate for '. $rate_detail->dest_regionname; + $name = $rate_detail->rate->ratename. ' rate for '. + ($rate_detail->dest_regionname || 'global default'); } #sec_granularity should default to 60! for new rates when this gets used for em diff --git a/httemplate/graph/cust_pkg.cgi b/httemplate/graph/cust_pkg.html index cdd95e10a..3b6552ba8 100644 --- a/httemplate/graph/cust_pkg.cgi +++ b/httemplate/graph/cust_pkg.html @@ -7,9 +7,12 @@ 'links' => \@links, 'params' => \@params, 'agentnum' => $agentnum, - 'sprintf' => '%u', + 'sprintf' => ( $normalize ? '%0.1f%%' : '%u'), + 'normalize' => ( $normalize ? 0 : undef ), 'disable_money' => 1, 'remove_empty' => (scalar(@group_keys) > 1 ? 1 : 0), + 'nototal' => 1, + 'no_graph' => [ 1, 0, 0, 0, 0 ], # don't graph 'active' &> <%init> @@ -30,36 +33,29 @@ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { my $agentname = $agent ? $agent->agent.' ' : ''; -my @base_items = qw( setup_pkg susp_pkg cancel_pkg ); +my @base_items = qw( active_pkg setup_pkg susp_pkg unsusp_pkg cancel_pkg ); my %base_labels = ( + 'active_pkg' => 'Active packages', 'setup_pkg' => 'New orders', 'susp_pkg' => 'Suspensions', -# 'unsusp' => 'Unsuspensions', + 'unsusp_pkg' => 'Unsuspensions', 'cancel_pkg' => 'Cancellations', ); my %base_colors = ( + 'active_pkg' => '000000', #black 'setup_pkg' => '00cc00', #green 'susp_pkg' => 'ff9900', #yellow - #'unsusp' => '', #light green? - 'cancel_pkg' => 'cc0000', #red ? 'ff0000' + 'unsusp_pkg' => '44ff44', #light green + 'cancel_pkg' => 'cc0000', #red ); -my %base_links = ( - 'setup_pkg' => { 'link' => "${p}search/cust_pkg.cgi?agentnum=$agentnum;", - 'fromparam' => 'setup_begin', - 'toparam' => 'setup_end', - }, - 'susp_pkg' => { 'link' => "${p}search/cust_pkg.cgi?agentnum=$agentnum;", - 'fromparam' => 'susp_begin', - 'toparam' => 'susp_end', - }, - 'cancel_pkg' => { 'link' => "${p}search/cust_pkg.cgi?agentnum=$agentnum;", - 'fromparam' => 'cancel_begin', - 'toparam' => 'cancel_end', - }, -); +my %base_links; +foreach my $status (qw(active setup cancel susp unsusp)) { + $base_links{$status.'_pkg'} = + "${p}search/cust_pkg_churn.html?agentnum=$agentnum;status=$status;"; +} my %filter_params = ( # not agentnum, that's elsewhere @@ -76,7 +72,7 @@ foreach my $link (values %base_links) { if (ref($value)) { $value = join(',', @$value); } - $link->{'link'} .= "$key=$value;" if length($value); + $link .= "$key=$value;" if length($value); } } @@ -143,9 +139,9 @@ if (scalar(@group_keys) > 1) { # and colors (?!) push @colors, $scheme->colorset->[$i]->[1]; # and links... - my %this_link = %{ $base_links{$_} }; - $this_link{link} .= "$breakdown=$key;"; - push @links, \%this_link; + my $this_link = $base_links{$_}; + $this_link .= "$breakdown=$key;"; + push @links, $this_link; $i++; } #foreach (@base_items $hue += 35; @@ -158,4 +154,6 @@ if (scalar(@group_keys) > 1) { @params = map { [ %filter_params ] } @base_items; } +my $normalize = $cgi->param('normalize'); + </%init> diff --git a/httemplate/graph/elements/monthly.html b/httemplate/graph/elements/monthly.html index 939f18a35..4b988f166 100644 --- a/httemplate/graph/elements/monthly.html +++ b/httemplate/graph/elements/monthly.html @@ -125,6 +125,7 @@ my %reportopts = ( 'cust_classnum'=> $opt{'cust_classnum'}, 'remove_empty' => $opt{'remove_empty'}, 'doublemonths' => $opt{'doublemonths'}, + 'normalize' => $opt{'normalize'}, ); warn Dumper({ 'REPORTOPTS' => \%reportopts }) if $opt{'debug'}; @@ -147,17 +148,12 @@ $col_labels = $data->{label} if $opt{'daily'}; my @colors; my @graph_labels; my @no_graph; -if ( $opt{'remove_empty'} ) { +#if ( $opt{'remove_empty'} ) { # no, always do this # then filter out per-item things for collapsed rows - foreach my $i (@{ $data->{'indices'} }) { - push @colors, $opt{'colors'}[$i]; - push @graph_labels, $opt{'graph_labels'}[$i]; - push @no_graph, $opt{'no_graph'}[$i]; - } -} else { - @colors = @{ $opt{'colors'} }; - @graph_labels = @{ $opt{'graph_labels'} }; - @no_graph = @{ $opt{'no_graph'} || [] }; +foreach my $i (@{ $data->{'indices'} }) { + push @colors, $opt{'colors'}[$i]; + push @graph_labels, $opt{'graph_labels'}[$i]; + push @no_graph, $opt{'no_graph'}[$i]; } my @links; diff --git a/httemplate/graph/elements/report.html b/httemplate/graph/elements/report.html index b3ba9ee22..cffc82816 100644 --- a/httemplate/graph/elements/report.html +++ b/httemplate/graph/elements/report.html @@ -108,11 +108,11 @@ any delimiter and linked from the elements in @data. % foreach ( @{ shift( @data ) } ) { % $total += $_; % $bottom_total[$col-1] += $_ unless $opt{no_graph}[$row]; -% $worksheet->write($row, $col++, sprintf($sprintf, $_) ); +% $worksheet->write_number($row, $col++, sprintf($sprintf, $_) ); % } % if ( !$opt{'nototal'} ) { % $bottom_total[$col-1] += $total unless $opt{no_graph}[$row]; -% $worksheet->write($row, $col++, sprintf($sprintf, $total) ); +% $worksheet->write_number($row, $col++, sprintf($sprintf, $total) ); % } % } % @@ -120,7 +120,7 @@ any delimiter and linked from the elements in @data. % if ( $opt{'bottom_total'} ) { % $row++; % $worksheet->write($row, $col++, 'Total'); -% $worksheet->write($row, $col++, sprintf($sprintf, $_)) foreach @bottom_total; +% $worksheet->write_number($row, $col++, sprintf($sprintf, $_)) foreach @bottom_total; % } % % $workbook->close();# or die "Error creating .xls file: $!"; diff --git a/httemplate/graph/report_cust_pkg.html b/httemplate/graph/report_cust_pkg.html index 1425ff089..0da5016a7 100644 --- a/httemplate/graph/report_cust_pkg.html +++ b/httemplate/graph/report_cust_pkg.html @@ -1,6 +1,6 @@ <% include('/elements/header.html', 'Package Churn Summary' ) %> -<FORM ACTION="cust_pkg.cgi" METHOD="GET"> +<FORM ACTION="cust_pkg.html" METHOD="GET"> <TABLE BGCOLOR="#cccccc" CELLSPACING=0> @@ -54,6 +54,12 @@ }, &> +<& /elements/tr-checkbox.html, + 'field' => 'normalize', + 'value' => 1, + 'label' => 'Show percentages' +&> + </TABLE> <BR><INPUT TYPE="submit" VALUE="Display"> diff --git a/httemplate/search/cust_pkg_churn.html b/httemplate/search/cust_pkg_churn.html new file mode 100644 index 000000000..0ab99aa97 --- /dev/null +++ b/httemplate/search/cust_pkg_churn.html @@ -0,0 +1,186 @@ +<& elements/search.html, + 'title' => $title, + 'name' => 'packages', + 'query' => $sql_query, + 'count_query' => $count_query, + 'header' => [ emt('#'), + emt('Quantity'), + emt('Package'), + emt('Class'), + emt('Sales Person'), + emt('Ordered by'), + emt('Setup Fee'), + emt('Base Recur'), + emt('Freq.'), + emt('Setup'), + emt('Last bill'), + emt('Next bill'), + emt('Susp.'), + emt('Changed'), + emt('Cancel'), + #emt('Reason'), # hard to do this right + FS::UI::Web::cust_header( + $cgi->param('cust_fields') + ), + #emt('Services'), # even harder + ], + 'fields' => [ + 'pkgnum', + 'quantity', + 'pkg', + 'classname', + 'salesperson', + 'otaker', + sub { sprintf( $money_char.'%.2f', + shift->part_pkg->option('setup_fee'), + ); + }, + sub { my $c = shift; + sprintf( $money_char.'%.2f', + $c->part_pkg->base_recur($c) + ); + }, + sub { FS::part_pkg::freq_pretty(shift); }, + + ( map { time_or_blank($_) } + qw( setup last_bill bill susp change_date cancel ) ), + + \&FS::UI::Web::cust_fields, + ], + 'sort_fields' => [ + 'cust_pkg.pkgnum', + ('') x 5, # can use as-is + ('') x 3, # can't use at all + # use the plain SQL column names + qw( setup last_bill bill susp change_date cancel ), + # cust_fields can take care of themselves + ], + 'color' => [ + ('') x 15, + FS::UI::Web::cust_colors(), + ], + 'style' => [ ('') x 15, + FS::UI::Web::cust_styles() ], + 'size' => [ '', '', '', '', '-1' ], + 'align' => 'rrlcccrrlrrrrrr'. FS::UI::Web::cust_aligns(). 'r', + 'links' => [ + $link, + $link, + $link, + ('') x 12, + ( map { $_ ne 'Cust. Status' ? $clink : '' } + FS::UI::Web::cust_header( + $cgi->param('cust_fields') + ) + ), + ], +&> +<%once> +my %title = ( + 'active' => 'Active packages as of ', + 'setup' => 'Packages started between ', + 'cancel' => 'Packages canceled between ', + 'susp' => 'Packages suspended between ', + 'unsusp' => 'Packages unsuspended between ', +); +</%once> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('List packages'); + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my %search_hash = (); + +# pass a very limited set of parameters through +#scalars +for (qw( agentnum zip )) +{ + $search_hash{$_} = $cgi->param($_) if length($cgi->param($_)); +} + +#arrays / comma-separated lists +for my $param (qw( pkgpart classnum refnum towernum )) { + my @values = map { split(',') } $cgi->param($param); + $search_hash{$param} = \@values if scalar(@values); +} + +### +# do not pass dates to FS::cust_pkg->search; use the special churn_fromwhere +# logic. +### + +my $pkg_query = FS::cust_pkg->search(\%search_hash); +#warn Dumper $pkg_query; + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +my $status = $cgi->param('status'); + +my $title = emt($title{$status}) . + time2str('%b %o %Y', $beginning); +if ($status ne 'active') { + $title .= emt(' to ') . time2str('%b %o %Y', $ending); +} + +my ($from, @where) = FS::h_cust_pkg->churn_fromwhere_sql($status, $beginning, $ending); + +push @where, "freq != '0'"; + +# split off the primary table name +$from =~ s/^(\w+)(.*)$/$2/s; +my $table = $1; + +# merge with $pkg_query +$from .= ' ' . $pkg_query->{addl_from}; + +my $extra_sql; +if ($pkg_query->{extra_sql}) { + $extra_sql = $pkg_query->{extra_sql} . ' AND '; +} else { + $extra_sql = 'WHERE '; +} +$extra_sql .= join(' AND ', @where); + +my $sql_query = { + 'table' => $table, + 'addl_from' => $from, + 'extra_sql' => $extra_sql, +}; +warn (Dumper $sql_query) if $cgi->param('debug'); + +my $count_query = "SELECT COUNT(*) FROM $table $from $extra_sql"; + +my $show = $curuser->default_customer_view =~ /^(jumbo|packages)$/ + ? '' + : ';show=packages'; + +my $link = sub { + my $self = shift; + my $frag = 'cust_pkg'. $self->pkgnum; #hack for IE ignoring real #fragment + [ "${p}view/cust_main.cgi?custnum=".$self->custnum. + "$show;fragment=$frag#cust_pkg", + 'pkgnum' + ]; +}; + +my $clink = sub { + my $cust_pkg = shift; + $cust_pkg->cust_main_custnum + ? [ "${p}view/cust_main.cgi?", 'custnum' ] + : ''; +}; + +sub time_or_blank { + my $column = shift; + return sub { + my $record = shift; + my $value = $record->get($column); #mmm closures + $value ? time2str('%b %d %Y', $value ) : ''; + }; +} + +</%init> |