diff options
author | Ivan Kohler <ivan@freeside.biz> | 2012-07-26 14:05:08 -0700 |
---|---|---|
committer | Ivan Kohler <ivan@freeside.biz> | 2012-07-26 14:05:08 -0700 |
commit | 8c450aab9bae89373c2c1b35c85597bb52299de3 (patch) | |
tree | 62e7400162703ad0990f80a2d71b90fc1167e759 /httemplate | |
parent | 2528cc7b182781a82844d8bbb1b555560487abc7 (diff) | |
parent | bd647f16de5352722baed016b45baa4e7c695278 (diff) |
Merge branch 'master' of git.freeside.biz:/home/git/freeside
Diffstat (limited to 'httemplate')
26 files changed, 708 insertions, 168 deletions
diff --git a/httemplate/edit/payment_gateway.html b/httemplate/edit/payment_gateway.html index 2840df35b..e5897b035 100644 --- a/httemplate/edit/payment_gateway.html +++ b/httemplate/edit/payment_gateway.html @@ -19,8 +19,7 @@ <SCRIPT TYPE="text/javascript"> -% my $json = JSON->new->canonical; - var modulesForNamespace = <% $json->encode(\%modules_for_namespace) %>; + var modulesForNamespace = <% to_json(\%modules_for_namespace, {canonical=>1}) %>; function changeNamespace(what) { var ns = what.value; var select_module = document.getElementById('gateway_module'); @@ -68,7 +67,6 @@ my %modules = ( 'OpenECHO' => 'Business::OnlinePayment', 'PayConnect' => 'Business::OnlinePayment', 'PayflowPro' => 'Business::OnlinePayment', - 'Paymentech' => 'Business::BatchPayment', 'PaymenTech' => 'Business::OnlinePayment', 'PaymentsGateway' => 'Business::OnlinePayment', 'PayPal' => 'Business::OnlinePayment', @@ -90,6 +88,9 @@ my %modules = ( 'VirtualNet' => 'Business::OnlinePayment', 'WesternACH' => 'Business::OnlinePayment', 'WorldPay' => 'Business::OnlinePayment', + + 'KeyBank' => 'Business::BatchPayment', + 'Paymentech' => 'Business::BatchPayment', ); my %modules_for_namespace; diff --git a/httemplate/edit/prepay_credit.cgi b/httemplate/edit/prepay_credit.cgi index f7a1b0801..c03bbf990 100644 --- a/httemplate/edit/prepay_credit.cgi +++ b/httemplate/edit/prepay_credit.cgi @@ -18,13 +18,11 @@ prepaid cards of characters each -<BR>for <SELECT NAME="agentnum"><OPTION>(any agent) -% foreach my $opt_agent ( qsearch('agent', { 'disabled' => '' } ) ) { +<BR>for - <OPTION VALUE="<% $opt_agent->agentnum %>"<% $opt_agent->agentnum == $agentnum ? ' SELECTED' : '' %>><% $opt_agent->agent %> -% } - -</SELECT> +<& /elements/select-agent.html, + 'empty_label' => '(any agent)', +&> <TABLE> <TR><TD>Value: diff --git a/httemplate/edit/radius_group.html b/httemplate/edit/radius_group.html index 025561159..0c99b4c4c 100644 --- a/httemplate/edit/radius_group.html +++ b/httemplate/edit/radius_group.html @@ -8,6 +8,7 @@ 'attrnum' => 'Attribute', 'priority' => 'Priority', }, + 'viewall_dir' => 'browse', 'menubar' => \@menubar, 'edit_callback' => $edit_callback, 'error_callback' => $edit_callback, diff --git a/httemplate/edit/svc_dsl.cgi b/httemplate/edit/svc_dsl.cgi index 1aeadb376..36345b9c5 100644 --- a/httemplate/edit/svc_dsl.cgi +++ b/httemplate/edit/svc_dsl.cgi @@ -52,12 +52,24 @@ my $edit_cb = sub { elsif($export->exporttype eq 'ikano') { @fields = ( 'password', 'monitored', ); - foreach my $hf ( keys %$ti_fields ) { - push @fields, { - field => $hf, - type => 'hidden', - value => $svc_x->$hf, - } unless ( $hf eq 'password' || $hf eq 'monitored' ); + if ( $svc_x->vendor_qual_id ) { + push @fields, { field => 'vendor_qual_id', + type => 'hidden', + value => $svc_x->vendor_qual_id, + }; + } else { + push @fields, 'vendor_qual_id'; + } + + foreach my $hf ( + grep { $_ !~ /^(password|monitored|vendor_qual_id)$/ } + keys %$ti_fields + ) { + push @fields, { + field => $hf, + type => 'hidden', + value => $svc_x->$hf, + }; } } # else add any other export-specific stuff here diff --git a/httemplate/elements/customer-table.html b/httemplate/elements/customer-table.html index 79443dc8b..75e682d92 100644 --- a/httemplate/elements/customer-table.html +++ b/httemplate/elements/customer-table.html @@ -203,7 +203,11 @@ Example: var customerArrayArray = eval('(' + customers + ')') || []; - if ( customerArrayArray.length == 1 ) { + if ( customerArrayArray.length == 0 ) { + + update_customer(searchrow, []); + + } else if ( customerArrayArray.length == 1 ) { update_customer(searchrow, customerArrayArray[0]); % if ( $opt{custnum_update_callback} ) { @@ -277,7 +281,11 @@ Example: custnum_obj.disabled = false; custnum_obj.style.backgroundColor = '#ffffff'; - if ( customerArrayArray.length == 1 ) { + if ( customerArrayArray.length == 0 ) { + + update_customer(searchrow, []); + + } else if ( customerArrayArray.length == 1 ) { update_customer(searchrow, customerArrayArray[0]); % if ( $opt{custnum_update_callback} ) { diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index 938303521..019afe94e 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -314,6 +314,7 @@ if($curuser->access_right('Financial reports')) { 'Daily Sales, Credits and Receipts' => [ $fsurl.'graph/report_money_time_daily.html', 'Sales, credits and receipts (broken down by day) summary graph' ], 'Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg.html', 'Sales report and graph (by agent, package class and/or date range)' ], 'Rated Call Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg_detail.html', 'Sales report and graph (by agent, package class, usage class and/or date range)' ], + 'Sales With Advertising Source' => [ $fsurl.'search/report_cust_bill_pkg_referral.html' ], 'Employee Commission Report' => [ $fsurl.'search/report_employee_commission.html', '' ], 'Credit Report' => [ $fsurl.'search/report_cust_credit.html', 'Credit report (by employee and/or date range)' ], 'Unapplied Credits' => [ $fsurl.'search/report_cust_credit.html?unapplied=1', 'Unapplied credit report (by type and/or date range)' ], diff --git a/httemplate/elements/select-cust_pkg-status.html b/httemplate/elements/select-cust_pkg-status.html index ec37eaf67..2114c07a1 100644 --- a/httemplate/elements/select-cust_pkg-status.html +++ b/httemplate/elements/select-cust_pkg-status.html @@ -3,7 +3,9 @@ <% $onchange %> > +% if ( !$opt{'disable_empty'} ) { <OPTION VALUE="">all +% } % foreach my $option ( @{ $opt{'statuses'} } ) { diff --git a/httemplate/elements/tr-amount_fee.html b/httemplate/elements/tr-amount_fee.html new file mode 100644 index 000000000..a1a9e3433 --- /dev/null +++ b/httemplate/elements/tr-amount_fee.html @@ -0,0 +1,98 @@ + <TR> + <TH ALIGN="right"><% mt('Payment amount') |h %></TH> + <TD COLSPAN=7> + <TABLE><TR><TD BGCOLOR="#ffffff"> + <% $money_char %><INPUT NAME = "amount" + ID = "amount" + TYPE = "text" + VALUE = "<% $amount %>" + SIZE = 8 + STYLE = "text-align:right;" +% if ( $fee ) { + onChange = "amount_changed(this)" + onKeyDown = "amount_changed(this)" + onKeyUp = "amount_changed(this)" + onKeyPress = "amount_changed(this)" +% } + > + </TD><TD BGCOLOR="#cccccc"> +% if ( $fee ) { + <INPUT TYPE="hidden" NAME="fee_pkgpart" VALUE="<% $fee_pkg->pkgpart %>"> + <INPUT TYPE="hidden" NAME="fee" VALUE="<% $fee_display eq 'add' ? $fee : '' %>"> + <B><FONT SIZE='+1'><% $fee_op %></FONT> + <% $money_char . $fee %> + </B> + <% $fee_pkg->pkg |h %> + <B><FONT SIZE='+1'>=</FONT></B> + </TD><TD ID="ajax_total_cell" BGCOLOR="#dddddd" STYLE="border:1px solid blue"> + <FONT SIZE="+1"><% length($amount) ? $money_char. sprintf('%.2f', ($fee_display eq 'add') ? $amount + $fee : $amount - $fee ) : '' %> <% $fee_display eq 'add' ? 'TOTAL' : 'AVAILABLE' %></FONT> + +% } + </TD></TR></TABLE> + </TD> + </TR> + +% if ( $fee ) { + + <SCRIPT TYPE="text/javascript"> + + function amount_changed(what) { + + + var total = ''; + if ( what.value.length ) { + total = parseFloat(what.value) <% $fee_op %> <% $fee %>; + /* total = Math.round(total*100)/100; */ + total = '<% $money_char %>' + total.toFixed(2); + } + + var total_cell = document.getElementById('ajax_total_cell'); + total_cell.innerHTML = '<FONT SIZE="+1">' + total + ' <% $fee_display eq 'add' ? 'TOTAL' : 'AVAILABLE' %></FONT>'; + + } + + </SCRIPT> + +% } + +<%init> + +my %opt = @_; + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my $fee = ''; +my $fee_pkg = ''; +my $fee_display = ''; +my $fee_op = ''; + +if ( $opt{'process-pkgpart'} + and ! $opt{'process-skip_first'} || $opt{'num_payments'} + ) +{ + + $fee_display = $opt{'process-display'} || 'add'; + $fee_op = $fee_display eq 'add' ? '+' : '-'; + + $fee_pkg = + qsearchs('part_pkg', { pkgpart=>$opt{'process-pkgpart'} } ); + + #well ->unit_setup or ->calc_setup both call for a $cust_pkg + # (though ->unit_setup doesn't use it...) + $fee = $fee_pkg->option('setup_fee') + if $fee_pkg; #in case.. better than dying with a perl traceback + +} + +my $amount = $opt{'amount'}; +if ( $amount > 0 ) { + $amount += $fee + if $fee && $fee_display eq 'subtract'; + + &{ $opt{post_fee_callback} }( \$amount ) if $opt{post_fee_callback}; + + $amount = sprintf("%.2f", $amount); +} + +</%init> diff --git a/httemplate/graph/elements/report.html b/httemplate/graph/elements/report.html index 3600f2c66..f7746165a 100644 --- a/httemplate/graph/elements/report.html +++ b/httemplate/graph/elements/report.html @@ -77,15 +77,16 @@ any delimiter and linked from the elements in @data. % } % % } elsif ( $cgi->param('_type') =~ /(xls)$/ ) { -% -% #http_header('Content-Type' => 'application/excel' ); #eww -% http_header('Content-Type' => 'application/vnd.ms-excel' ); -% #http_header('Content-Type' => 'application/msexcel' ); #alas -% http_header('Content-Disposition' => "attachment;filename=$filename.xls"); +% #false laziness w/ search/elements/search-xls +% my $format = $FS::CurrentUser::CurrentUser->spreadsheet_format; +% $filename .= $format->{extension}; +% +% http_header('Content-Type' => $format->{mime_type} ); +% http_header('Content-Disposition' => qq!attachment;filename="$filename"! ); % % my $output = ''; % my $XLS = new IO::Scalar \$output; -% my $workbook = Spreadsheet::WriteExcel->new($XLS) +% my $workbook = $format->{class}->new($XLS) % or die "Error opening .xls file: $!"; % % my $worksheet = $workbook->add_worksheet(substr($opt{'title'},0,31)); @@ -304,9 +305,6 @@ td.cell { <% include('/elements/footer.html') %> % } -<%once> - -</%once> <%init> my(%opt) = @_; diff --git a/httemplate/misc/cancel_pkg.html b/httemplate/misc/cancel_pkg.html index 348f0a6cb..f9a46a898 100755 --- a/httemplate/misc/cancel_pkg.html +++ b/httemplate/misc/cancel_pkg.html @@ -62,7 +62,19 @@ &> % } -% if ( ( $method eq 'adjourn' or $method eq 'suspend' ) and +% if ( $method eq 'adjourn' || $method eq 'suspend' ) { + <TR><TD COLSPAN=2> +% if ( $part_pkg->option('suspend_bill', 1) ) { + <& /elements/checkbox.html, name=>'no_suspend_bill', value=>'Y' &> + Disable recurring billing while suspended +% } else { + <& /elements/checkbox.html, name=>'suspend_bill', value=>'Y' &> + Continue recurring billing while suspended +% } + </TD></TR> +% } + +% if ( ( $method eq 'adjourn' || $method eq 'suspend' ) and % $curuser->access_right('Unsuspend customer package') ) { #later? % my $resume_date = $cgi->param('error') % ? str2time($cgi->param('resume_date')) diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html index 57fdd64ee..c5f4509ab 100644 --- a/httemplate/misc/order_pkg.html +++ b/httemplate/misc/order_pkg.html @@ -44,6 +44,8 @@ <INPUT TYPE="text" NAME="quantity" SIZE=4 VALUE="<% $quantity %>"> </TD> </TR> +% } else { + <INPUT TYPE="hidden" NAME="quantity" VALUE="1"> % } <TR> diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi index 093494a06..1ae15b930 100644 --- a/httemplate/misc/payment.cgi +++ b/httemplate/misc/payment.cgi @@ -9,67 +9,20 @@ <& /elements/init_overlib.html &> <% ntable('#cccccc') %> - <TR> - <TH ALIGN="right"><% mt('Payment amount') |h %></TH> - <TD COLSPAN=7> - <TABLE><TR><TD BGCOLOR="#ffffff"> - <% $money_char %><INPUT NAME = "amount" - ID = "amount" - TYPE = "text" - VALUE = "<% $amount %>" - SIZE = 8 - STYLE = "text-align:right;" -% if ( $fee ) { - onChange = "amount_changed(this)" - onKeyDown = "amount_changed(this)" - onKeyUp = "amount_changed(this)" - onKeyPress = "amount_changed(this)" -% } - > - </TD><TD BGCOLOR="#cccccc"> -% if ( $fee ) { - <INPUT TYPE="hidden" NAME="fee_pkgpart" VALUE="<% $fee_pkg->pkgpart %>"> - <INPUT TYPE="hidden" NAME="fee" VALUE="<% $fee_display eq 'add' ? $fee : '' %>"> - <B><FONT SIZE='+1'><% $fee_op %></FONT> - <% $money_char . $fee %> - </B> - <% $fee_pkg->pkg |h %> - <B><FONT SIZE='+1'>=</FONT></B> - </TD><TD ID="ajax_total_cell" BGCOLOR="#dddddd" STYLE="border:1px solid blue"> - <FONT SIZE="+1"><% length($amount) ? $money_char. sprintf('%.2f', ($fee_display eq 'add') ? $amount + $fee : $amount - $fee ) : '' %> <% $fee_display eq 'add' ? 'TOTAL' : 'AVAILABLE' %></FONT> - -% } - </TD></TR></TABLE> - </TD> - </TR> - -% if ( $fee ) { - - <SCRIPT TYPE="text/javascript"> - - function amount_changed(what) { - - - var total = ''; - if ( what.value.length ) { - total = parseFloat(what.value) <% $fee_op %> <% $fee %>; - /* total = Math.round(total*100)/100; */ - total = '<% $money_char %>' + total.toFixed(2); - } - - var total_cell = document.getElementById('ajax_total_cell'); - total_cell.innerHTML = '<FONT SIZE="+1">' + total + ' <% $fee_display eq 'add' ? 'TOTAL' : 'AVAILABLE' %></FONT>'; - - } - - </SCRIPT> -% } + <& /elements/tr-amount_fee.html, + 'amount' => $amount, + 'process-pkgpart' => scalar($conf->config('manual_process-pkgpart')), + 'process-display' => scalar($conf->config('manual_process-display')), + 'process-skip-first' => $conf->exists('manual_process-skip_first'), + 'num_payments' => scalar($cust_main->cust_pay), + 'post_fee_callback' => $post_fee_callback, + &> -<& /elements/tr-select-discount_term.html, - 'custnum' => $custnum, - 'amount_id' => 'amount', -&> + <& /elements/tr-select-discount_term.html, + 'custnum' => $custnum, + 'amount_id' => 'amount', + &> % if ( $payby eq 'CARD' ) { % @@ -304,8 +257,6 @@ my $payinfo = ''; my $conf = new FS::Conf; -my $money_char = $conf->config('money_char') || '$'; - #false laziness w/selfservice make_payment.html shortcut for one-country my %states = map { $_->state => 1 } qsearch('cust_main_county', { @@ -313,43 +264,23 @@ my %states = map { $_->state => 1 } } ); my @states = sort { $a cmp $b } keys %states; -my $fee = ''; -my $fee_pkg = ''; -my $fee_display = ''; -my $fee_op = ''; -my $num_payments = scalar($cust_main->cust_pay); -#handle old cust_main.pm (remove...) -$num_payments = scalar( @{ [ $cust_main->cust_pay ] } ) - unless defined $num_payments; -if ( $conf->config('manual_process-pkgpart') - and ! $conf->exists('manual_process-skip_first') || $num_payments - ) -{ - - $fee_display = $conf->config('manual_process-display') || 'add'; - $fee_op = $fee_display eq 'add' ? '+' : '-'; - - $fee_pkg = - qsearchs('part_pkg', { pkgpart=>$conf->config('manual_process-pkgpart') } ); - - #well ->unit_setup or ->calc_setup both call for a $cust_pkg - # (though ->unit_setup doesn't use it...) - $fee = $fee_pkg->option('setup_fee') - if $fee_pkg; #in case.. better than dying with a perl traceback - -} - my $amount = ''; if ( $balance > 0 ) { $amount = $balance; - $amount += $fee - if $fee && $fee_display eq 'subtract'; +} + +my $post_fee_callback = sub { + my( $amountref ) = @_; + + return unless $$amountref > 0; + + my $conf = new FS::Conf; my $cc_surcharge_pct = $conf->config('credit-card-surcharge-percentage'); - $amount += $amount * $cc_surcharge_pct/100 if $cc_surcharge_pct > 0; + $$amountref += $$amountref * $cc_surcharge_pct/100 if $cc_surcharge_pct > 0; - $amount = sprintf("%.2f", $amount); -} + $$amountref = sprintf("%.2f", $$amountref); +}; my $payunique = "webui-payment-". time. "-$$-". rand() * 2**32; diff --git a/httemplate/misc/process/cancel_pkg.html b/httemplate/misc/process/cancel_pkg.html index b2d7bfaa4..a106b845a 100755 --- a/httemplate/misc/process/cancel_pkg.html +++ b/httemplate/misc/process/cancel_pkg.html @@ -52,10 +52,15 @@ if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume') { $method = 'unsuspend' if $method eq 'resume'; } -my $resume_date; +my $resume_date = ''; +my $options = ''; if ( $method eq 'suspend' ) { #or 'adjourn' $resume_date = parse_datetime($cgi->param('resume_date')) if $cgi->param('resume_date'); + + $options = { map { $_ => scalar($cgi->param($_)) } + qw( suspend_bill no_suspend_bill ) + }; } my $cust_pkg = qsearchs( 'cust_pkg', {'pkgnum'=>$pkgnum} ); @@ -88,6 +93,7 @@ my $error = $cust_pkg->$method( 'reason' => $reasonnum, 'last_bill' => $last_bill, 'bill' => $bill, 'svc_fatal' => $svc_fatal, + 'options' => $options, ); if ($error) { diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html index bd6bb860a..932cf1a0a 100644 --- a/httemplate/pref/pref-process.html +++ b/httemplate/pref/pref-process.html @@ -48,7 +48,8 @@ unless ( $error ) { # if ($access_user) { my %param = $access_user->options; #XXX autogen - my @paramlist = qw( locale menu_position default_customer_view mobile_menu + my @paramlist = qw( locale menu_position default_customer_view + spreadsheet_format mobile_menu disable_html_editor disable_enter_submit_onetimecharge email_address snom-ip snom-username snom-password diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html index 8e56355db..9ebf2f1ba 100644 --- a/httemplate/pref/pref.html +++ b/httemplate/pref/pref.html @@ -75,6 +75,21 @@ Interface </SELECT> </TD> </TR> + + <TR> + <TH ALIGN="right">Spreadsheet download format: </TH> + <TD COLSPAN=2> + <SELECT NAME="spreadsheet_format"> +% my $xls = $curuser->option('spreadsheet_format') eq 'XLS'; +% my $xlsx = $curuser->option('spreadsheet_format') eq 'XLSX'; + <OPTION VALUE=""></OPTION> + <OPTION VALUE="XLS"<% $xls ? 'SELECTED' : '' %>>XLS (Excel 97/2000/XP) + </OPTION> + <OPTION VALUE="XLSX"<% $xlsx ? 'SELECTED' : ''%>>XLSX (Excel 2007+) + </OPTION> + </SELECT> + </TD> + </TR> <TR> <TH ALIGN="right" COLSPAN=1>Disable HTML editor for customer notes: </TH> diff --git a/httemplate/search/477partV.html b/httemplate/search/477partV.html index 55ebc0be2..0987fea44 100755 --- a/httemplate/search/477partV.html +++ b/httemplate/search/477partV.html @@ -34,9 +34,11 @@ $search_hash{'classnum'} = [ $cgi->param('classnum') ]; $search_hash{report_option} = $cgi->param('partv_report_option') if $cgi->param('partv_report_option'); -my $sql_query = FS::cust_pkg->search( { %search_hash, 'fcc_line' => 1 }); -$sql_query->{select} = 'DISTINCT substr(zip,1,5) as zip'; -$sql_query->{order_by} = 'ORDER BY substr(zip,1,5)'; +my $sql_query = FS::cust_pkg->search( { %search_hash, + 'fcc_line' => 1, + 'select_zip5' => 1, + } + ); my $count_query = delete($sql_query->{'count_query'}); $count_query =~ s/COUNT\(\*\)/count(DISTINCT substr(zip,1,5))/; $count_query =~ s/ORDER BY [.\w]+//; diff --git a/httemplate/search/cust_bill_pkg_referral.html b/httemplate/search/cust_bill_pkg_referral.html new file mode 100644 index 000000000..3cb434caa --- /dev/null +++ b/httemplate/search/cust_bill_pkg_referral.html @@ -0,0 +1,294 @@ +<& elements/search.html, + 'title' => emt('Sales with advertising source'), + 'name' => emt('line items'), + 'query' => $query, + 'count_query' => $count_query, + 'count_addl' => [ + ($setup ? $money_char. '%.2f setup' : ()), + ($recur ? $money_char. '%.2f recurring' : ()), + ($usage ? $money_char. '%.2f usage' : ()), + ], + 'header' => [ + emt('Description'), + ($setup ? emt('Setup') : ()), + ($recur ? emt('Recurring') : ()), + ($usage ? emt('Usage') : ()), + emt('Invoice'), + emt('Invoice date'), + emt('Paid'), + emt('Payment date'), + emt('Pkg. status'), + emt('Pkg. class'), + '', #report class + emt('Cust#'), + emt('Customer'), + emt('Ad source'), + emt('Agent'), + ], + 'fields' => [ + 'pkg', + ($setup ? money_sub('setup') : ()), + ($recur ? money_sub('recur_no_usage') : ()), + ($usage ? money_sub('recur_usage') : ()), + 'invnum', + date_sub('_date'), + money_sub('paid'), + date_sub('last_pay'), + sub { + my $cust_pkg = shift->cust_pkg; + $cust_pkg ? ucfirst($cust_pkg->status) : ''; + }, + 'classname', + sub { # report_option + my $cust_bill_pkg = shift; + my $pkgpart = $cust_bill_pkg->pkgpart_override + || $cust_bill_pkg->cust_pkg->pkgpart; + if ( !exists($report_classes{$pkgpart}) ) { + my $part_pkg = FS::part_pkg->by_key($pkgpart); + my %opts = $part_pkg->options; + $report_classes{$pkgpart} = [ + map { /^report_option_(\d+)/ ? + $report_option_name{$1} : + () } + keys %opts + ]; + } + join( '<BR>', @{ $report_classes{$pkgpart} }); + }, + 'custnum', + 'name', + 'referral', # from query + 'agent', + ], + 'sort_fields' => [ + '', + ($setup ? 'setup' : ()), + ($recur ? 'recur_no_usage' : ()), + ($usage ? 'recur_usage' : ()), + 'invnum', + '_date', + 'paid', + 'last_pay', + '', #package status + 'classname', + '', #report_option + 'custnum', + '', + 'referral', + 'agent', + ], + 'links' => [ + '', #package/item desc + ('') x $x, #setup/recur/usage + $ilink, #invnum + $ilink, #invoice date + '', #paid amt + '', #payment date + '', #pkg status + '', #classnum + '', #report class + $clink, #custnum + $clink, #customer name + '', #referral + '', #agent + ], + #'align' => 'rlrrrc'.FS::UI::Web::cust_aligns(), + 'align' => 'l' . ('r' x $x) . 'rcrccccrlll', + 'color' => [ ('') x (5 + $x), + sub { + my $cust_pkg = shift->cust_pkg; + $cust_pkg ? ucfirst($cust_pkg->statuscolor) : ''; + }, + ('') x 6, + ], + 'style' => [ + ('') x (5 + $x), + 'b', + ('') x 6 + ], +&> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my $conf = new FS::Conf; + +my $setup = $cgi->param('setup') ? 1 : 0; +my $recur = $cgi->param('recur') ? 1 : 0; +my $usage = $cgi->param('usage') ? 1 : 0; + +my $x = $setup + $recur + $usage; + +my @select = ( 'cust_bill_pkg.*', 'cust_bill._date' ); +my ($join_cust, $join_pkg ) = ('', ''); + +#here is the agent virtualization +my $agentnums_sql = + $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' ); + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); + +my @where = ( $agentnums_sql, + 'cust_bill_pkg.pkgnum != 0', # exclude taxes + "cust_bill._date >= $beginning", + "cust_bill._date <= $ending", + ); + +my @status_where; +foreach my $status ($cgi->param('status')) { + if ( $status =~ /^([- a-z]+)$/ ) { #"one-time charge" + push @status_where, "'$status'"; + } +} +if ( @status_where ) { + push @where, '('. FS::cust_pkg->status_sql. + ') IN (' . join(',', @status_where) .')'; +} + +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @where, "cust_main.agentnum = $1"; +} + +#classnum +# not specified: all classes +# 0: empty class +# N: classnum +my $use_override = 1; #$cgi->param('use_override'); +if ( $cgi->param('classnum') =~ /^(\d+)$/ ) { + my $comparison = ''; + if ( $1 == 0 ) { + $comparison = "IS NULL"; + } else { + $comparison = "= $1"; + } + + if ( $use_override ) { + push @where, "( + part_pkg.classnum $comparison AND pkgpart_override IS NULL OR + override.classnum $comparison AND pkgpart_override IS NOT NULL + )"; + } else { + push @where, "part_pkg.classnum $comparison"; + } +} + +# report option +my @report_option = grep /^\d+$/, ( $cgi->param('report_option') ); +if ( @report_option ) { + @report_option = map { "'report_option_$_'" } @report_option; + push @where, "EXISTS( + SELECT 1 FROM part_pkg_option WHERE optionname IN (". + join(',', @report_option).") AND ( + part_pkg_option.pkgpart = cust_pkg.pkgpart AND pkgpart_override IS NULL + OR part_pkg_option.pkgpart = pkgpart_override + ) + )"; +} + +my $setup_sql = + FS::cust_bill_pkg->charged_sql('', '', setuprecur => 'setup'); +my $recur_sql = + FS::cust_bill_pkg->charged_sql('', '', setuprecur => 'recur', no_usage => 1); +my $usage_sql = FS::cust_bill_pkg->usage_sql; + +# exclude zero-amount items +my @orwhere; +push @orwhere, "(cust_bill_pkg.setup > 0)" if $setup; +push @orwhere, "($recur_sql > 0)" if $recur; +push @orwhere, "($usage_sql > 0)" if $usage; +push @where, '('.join(' OR ', @orwhere).')' if @orwhere; + +$join_cust = ' JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) + LEFT JOIN part_referral USING ( refnum ) + LEFT JOIN agent ON cust_main.agentnum = agent.agentnum + '; + +$join_pkg .= ' LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN part_pkg USING ( pkgpart ) + LEFT JOIN part_pkg AS override + ON pkgpart_override = override.pkgpart + LEFT JOIN pkg_class ON '; #... + +if ( $use_override ) { + # join to whichever pkgpart is appropriate + $join_pkg .= ' + ( pkgpart_override IS NULL AND part_pkg.classnum = pkg_class.classnum ) + OR ( pkgpart_override IS NOT NULL AND override.classnum = pkg_class.classnum )'; +} else { + $join_pkg .= 'part_pkg.classnum = pkg_class.classnum'; +} + +my $where = ' WHERE '. join(' AND ', @where); + +# setup and recurring only +my $count_query = "SELECT + COUNT(billpkgnum)". + ($setup ? ", SUM($setup_sql)" : ''). + ($recur ? ", SUM($recur_sql)" : ''). + ($usage ? ", SUM($usage_sql)" : ''). + " FROM cust_bill_pkg + $join_cust + $join_pkg + $where + "; + +my $paid_sql = FS::cust_bill_pkg->paid_sql('', ''); +my $last_pay_sql = "SELECT MAX(_date) + FROM cust_bill_pay JOIN cust_bill_pay_pkg USING (billpaynum) + WHERE cust_bill_pay_pkg.billpkgnum = cust_bill_pkg.billpkgnum"; + +push @select, 'part_pkg.pkg', + 'part_pkg.freq', + 'cust_main.custnum', + 'cust_main.first', + 'cust_main.last', + 'cust_main.company', + 'part_referral.referral', + "($paid_sql) AS paid", + "($last_pay_sql) AS last_pay", + "($recur_sql) AS recur_no_usage", + "($usage_sql) AS recur_usage", + 'pkg_class.classname', + 'agent.agent', + ; + +my $query = { + 'table' => 'cust_bill_pkg', + 'addl_from' => "$join_cust $join_pkg", + 'hashref' => {}, + 'select' => join(",\n", @select ), + 'extra_sql' => $where, + 'order_by' => 'ORDER BY cust_bill._date, billpkgnum', +}; + +my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ]; +my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my %report_classes; #cache +my %report_option_name = + map { $_->num => $_->name } qsearch('part_pkg_report_option', {}); + +# should this be in Mason.pm or something? +sub money_sub { + $conf ||= new FS::Conf; + $money_char ||= $conf->config('money_char') || '$'; + my $field = shift; + sub { + $money_char . sprintf('%.2f', $_[0]->get($field)); + }; +} + +sub date_sub { + my $field = shift; + sub { + my $value = $_[0]->get($field); + $value ? time2str('%b %d %Y', $value) : ''; + }; +} + +</%init> diff --git a/httemplate/search/cust_main-zip.html b/httemplate/search/cust_main-zip.html index e87b21474..c317dc36f 100644 --- a/httemplate/search/cust_main-zip.html +++ b/httemplate/search/cust_main-zip.html @@ -5,7 +5,7 @@ 'count_query' => $count_sql, 'header' => [ 'Zip code', 'Customers', ], #'fields' => [ 'zip', 'num_cust', ], - 'links' => [ '', sub { 'somewhere'; } ], + #'links' => [ '', sub { 'somewhere'; } ], ) %> <%init> diff --git a/httemplate/search/elements/cust_pay_batch_top.html b/httemplate/search/elements/cust_pay_batch_top.html index 005b76182..739e65b50 100644 --- a/httemplate/search/elements/cust_pay_batch_top.html +++ b/httemplate/search/elements/cust_pay_batch_top.html @@ -103,7 +103,7 @@ Batch is <% $statustext{$status} %><BR> % } </%def> <%shared> -my $show_gateways = FS::payment_gateway->count("gateway_namespace = 'Business::BatchPayment'"); +my $show_gateways = FS::payment_gateway->count("gateway_namespace = 'Business::BatchPayment' AND disabled IS NULL"); </%shared> <%init> my %opt = @_; diff --git a/httemplate/search/elements/cust_pay_or_refund.html b/httemplate/search/elements/cust_pay_or_refund.html index dc3cb2a99..c60411107 100755 --- a/httemplate/search/elements/cust_pay_or_refund.html +++ b/httemplate/search/elements/cust_pay_or_refund.html @@ -357,6 +357,15 @@ if ( $cgi->param('magic') ) { $orderby = "LOWER(company || ' ' || last || ' ' || first )"; + } elsif ( $cgi->param('magic') eq 'batchnum' ) { + + $cgi->param('batchnum') =~ /^(\d+)$/ + or die "illegal batchnum: ".$cgi->param('batchnum'); + + push @search, "batchnum = $1"; + + $orderby = "LOWER(company || ' ' || last || ' ' || first )"; + } else { die "unknown search magic: ". $cgi->param('magic'); } diff --git a/httemplate/search/elements/search-xls.html b/httemplate/search/elements/search-xls.html index 0b5636c0e..09dbe46e0 100644 --- a/httemplate/search/elements/search-xls.html +++ b/httemplate/search/elements/search-xls.html @@ -7,14 +7,20 @@ my $header = $args{'header'}; my $rows = $args{'rows'}; my %opt = %{ $args{'opt'} }; +my $override = scalar(@$rows) >= 65536 ? 'XLSX' : ''; + +my $format = $FS::CurrentUser::CurrentUser->spreadsheet_format($override); + +my $filename = $opt{'name'} || PL($opt{'name_singular'}); +$filename .= $format->{extension}; + #http_header('Content-Type' => 'application/excel' ); #eww #http_header('Content-Type' => 'application/msexcel' ); #alas #http_header('Content-Type' => 'application/x-msexcel' ); #? #http://support.microsoft.com/kb/199841 -http_header('Content-Type' => 'application/vnd.ms-excel' ); -http_header('Content-Disposition' => - 'attachment;filename="'.($opt{'name'} || PL($opt{'name_singular'}) ).'.xls"'); +http_header('Content-Type' => $format->{mime_type} ); +http_header('Content-Disposition' => qq!attachment;filename="$filename"! ); #http://support.microsoft.com/kb/812935 #http://support.microsoft.com/kb/323308 @@ -22,8 +28,8 @@ $HTML::Mason::Commands::r->headers_out->{'Cache-control'} = 'max-age=0'; my $data = ''; my $XLS = new IO::Scalar \$data; -my $workbook = Spreadsheet::WriteExcel->new($XLS) - or die "Error opening .xls file: $!"; +my $workbook = $format->{class}->new($XLS) + or die "Error opening Excel file: $!"; my $worksheet = $workbook->add_worksheet(substr($opt{'title'},0,31)); @@ -42,14 +48,18 @@ my $default_format = $workbook->add_format(locked => 0); my %money_format; my $money_char = FS::Conf->new->config('money_char') || '$'; +my %date_format; +xl_parse_date_init(); + my $writer = sub { # Wrapper for $worksheet->write. # Do any massaging of the value/format here. my ($r, $c, $value, $format) = @_; - if ( $value =~ /^\Q$money_char\E(\d+\.?\d*)$/ ) { + if ( $value =~ /^\Q$money_char\E(-?\d+\.?\d*)$/ ) { # Currency: strip the symbol, clone the requested format, # and format it for currency $value = $1; +# warn "formatting $value as money\n"; if ( !exists($money_format{$format}) ) { $money_format{$format} = $workbook->add_format(); $money_format{$format}->copy($format); @@ -57,6 +67,22 @@ my $writer = sub { } $format = $money_format{$format}; } + elsif ( $value =~ /^([A-Z][a-z]{2}) (\d{2}) (\d{4})$/ ) { + # Date: convert the value to an Excel date number and set + # the format + $value = xl_parse_date($value); +# warn "formatting $value as date\n"; + if ( !exists($date_format{$format}) ) { + $date_format{$format} = $workbook->add_format(); + $date_format{$format}->copy($format); + $date_format{$format}->set_num_format('mmm dd yyyy'); + } + $format = $date_format{$format}; + } + else { + # String: replace line breaks with newlines + $value =~ s/<BR>/\n/gi; + } $worksheet->write($r, $c, $value, $format); }; diff --git a/httemplate/search/pay_batch.cgi b/httemplate/search/pay_batch.cgi index 05415f36e..aeaa012f4 100755 --- a/httemplate/search/pay_batch.cgi +++ b/httemplate/search/pay_batch.cgi @@ -14,12 +14,13 @@ 'Type', 'First Download', 'Last Upload', - 'Items', - 'Unresolved', - 'Amount', + '', # requests + '', # req amt + '', # payments + '', # pay amt 'Status', ], - 'align' => 'rcllrrc', + 'align' => 'rcllrrrrc', 'fields' => [ 'batchnum', sub { FS::payby->shortname(shift->payby); @@ -47,33 +48,44 @@ } }, sub { - FS::cust_pay_batch->count( - 'batchnum = '.$_[0]->batchnum - ) + my $c = FS::cust_pay_batch->count('batchnum = '.$_[0]->batchnum); + $c ? "$c requested" : '' }, sub { - FS::cust_pay_batch->count( - 'status is null and batchnum = '. - $_[0]->batchnum - ) - }, - sub { my $st = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=" . shift->batchnum; my $sth = dbh->prepare($st) - or die dbh->errstr. "doing $st"; + or die dbh->errstr. "doing $st"; $sth->execute - or die "Error executing \"$st\": ". $sth->errstr; - $sth->fetchrow_arrayref->[0]; - }, + or die "Error executing \"$st\": ". $sth->errstr; + my $total = $sth->fetchrow_arrayref->[0]; + $total ? $money_char.sprintf('%.2f',$total) : ''; + }, + sub { + my $c = FS::cust_pay->count('batchnum = '.$_[0]->batchnum); + $c ? "$c paid" : '' + }, + sub { + my $st = "SELECT SUM(paid) from cust_pay WHERE batchnum=" . shift->batchnum; + my $sth = dbh->prepare($st) + or die dbh->errstr. "doing $st"; + $sth->execute + or die "Error executing \"$st\": ". $sth->errstr; + my $total = $sth->fetchrow_arrayref->[0]; + $total ? $money_char.sprintf('%.2f',$total) : ''; + }, sub { $statusmap{shift->status}; }, ], 'links' => [ - $link, + '', '', - sub { shift->status eq 'O' ? $link : '' }, - sub { shift->status eq 'I' ? $link : '' }, + sub { shift->status eq 'O' ? $cpb_link : '' }, + sub { shift->status eq 'I' ? $cpb_link : '' }, + $cpb_link, + $cpb_link, + $pay_link, + $pay_link, ], 'size' => [ '', @@ -88,9 +100,42 @@ sub { shift->status eq 'I' ? "b" : '' }, ], 'html_init' => $html_init, + 'html_foot' => include('.upload_incoming'), ) - %> +<%def .upload_incoming> +% if ( FS::payment_gateway->count("gateway_namespace = 'Business::BatchPayment' AND disabled IS NULL") > 0 ) { +<& /elements/form-file_upload.html, + name => 'FileUpload', + action => $p.'misc/upload-batch.cgi', + num_files => 1, + fields => [ 'gatewaynum' ], + message => 'Incoming batch uploaded.', +&> +<BR> +<BR> +Upload incoming batch from gateway +<& /elements/select-table.html, + table => 'payment_gateway', + field => 'gatewaynum', + name_col => 'label', + value_col => 'gatewaynum', + order_by => 'ORDER BY gatewaynum', + empty_label => ' ', + hashref => + { 'gateway_namespace' => 'Business::BatchPayment', + 'disabled' => '' }, +&> +<BR> +<& '/elements/file-upload.html', + field => 'file', + label => 'Filename', + no_table => 1, +&> +<INPUT TYPE="submit" VALUE="Upload"> +</FORM> +% } +</%def> <%init> die "access denied" @@ -134,11 +179,14 @@ push @where, my $extra_sql = scalar(@where) ? 'WHERE ' . join(' AND ', @where) : ''; -my $link = [ "${p}search/cust_pay_batch.cgi?dcln=1;batchnum=", 'batchnum' ]; +my $cpb_link = [ "${p}search/cust_pay_batch.cgi?dcln=1;batchnum=", 'batchnum' ]; +my $pay_link = [ "${p}search/cust_pay.html?magic=batchnum;batchnum=", 'batchnum' ]; my $resolved = $cgi->param('resolved') || 0; $cgi->param('resolved' => !$resolved); my $html_init = '<A HREF="' . $cgi->self_url . '"><I>'. ($resolved ? 'Hide' : 'Show') . ' resolved batches</I></A><BR>'; +my $money_char = FS::Conf->new->config('money_char') || '$'; + </%init> diff --git a/httemplate/search/report_cust_bill_pkg_referral.html b/httemplate/search/report_cust_bill_pkg_referral.html new file mode 100644 index 000000000..ff2caa1fa --- /dev/null +++ b/httemplate/search/report_cust_bill_pkg_referral.html @@ -0,0 +1,61 @@ +<% include('/elements/header.html', 'Sales Report with Advertising Source' ) %> + +<FORM ACTION="cust_bill_pkg_referral.html" METHOD="GET"> + +<TABLE> + +<& /elements/tr-input-beginning_ending.html &> + +<& /elements/tr-select-agent.html, + 'label' => 'For agent: ', + 'disable_empty' => 0, + 'empty_label' => 'all', +&> + +<& /elements/tr-select-cust_pkg-status.html, + 'label' => 'Package status', + 'multiple' => 1, + 'disable_empty' => 1, +&> + +<& /elements/tr-select-pkg_class.html, + 'pre_options' => [ '' => 'all', '0' => '(empty class)' ], + 'disable_empty' => 1, +&> + +<& /elements/tr-select-table.html, + 'label' => 'Report classes', + 'table' => 'part_pkg_report_option', + 'name_col' => 'name', + 'hashref' => { disabled => '' }, + 'element_name' => 'report_option', + 'multiple' => 1, +&> + +<TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="setup" VALUE="1" CHECKED></TD> + <TD>Show setup/one-time fees</TD> +</TR> + +<TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="recur" VALUE="1" CHECKED></TD> + <TD>Show recurring fees</TD> +</TR> + +<TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="usage" VALUE="1" CHECKED></TD> + <TD>Show usage charges</TD> +</TR> + +</TABLE> + +<BR><INPUT TYPE="submit" VALUE="Display"> +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +</%init> diff --git a/httemplate/search/report_tax-xls.cgi b/httemplate/search/report_tax-xls.cgi index 1c278dfd1..f19f85aaa 100755 --- a/httemplate/search/report_tax-xls.cgi +++ b/httemplate/search/report_tax-xls.cgi @@ -1,9 +1,25 @@ <% $data %> <%init> +my $htmldoc = include('report_tax.cgi'); + +my ($title) = ($htmldoc =~ /<title>\s*(.*)\s*<\/title>/i); + +# do this first so we can override the format if it's too many rows +# attribs option: how to locate the table? It's the only one with class="grid". +my $te = HTML::TableExtract->new(attribs => {class => 'grid'}); +$te->parse($htmldoc); +my $table = $te->first_table_found; + +my $override = ($table->row_count >= 65536 ? 'XLSX' : ''); +my $format = $FS::CurrentUser::CurrentUser->spreadsheet_format($override); +my $filename = 'report_tax'.$format->{extension}; + +http_header('Content-Type' => $format->{mime_type}); +http_header('Content-Disposition' => qq!attachment;filename="$filename"! ); my $data = ''; my $XLS = new IO::Scalar \$data; -my $workbook = Spreadsheet::WriteExcel->new($XLS) +my $workbook = $format->{class}->new($XLS) or die "Error opening .xls file: $!"; # hardcoded formats, this could be handled better @@ -66,15 +82,6 @@ foreach (keys(%format)) { } my $ws = $workbook->add_worksheet('taxreport'); -my $htmldoc = include('report_tax.cgi'); - -my ($title) = ($htmldoc =~ /<title>\s*(.*)\s*<\/title>/i); - -# attribs option: how to locate the table? It's the only one with class="grid". -my $te = HTML::TableExtract->new(attribs => {class => 'grid'}); -$te->parse($htmldoc); -my $table = $te->first_table_found; - my @sheet; $sheet[0][0] = { text => $title, @@ -148,6 +155,4 @@ for my $x (0..scalar(@widths)-1) { $workbook->close; -http_header('Content-Type' => 'application/vnd.ms-excel'); -http_header('Content-Disposition' => 'attachment;filename="report_tax.xls"'); </%init> diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html index 4aec90efb..e9017745b 100644 --- a/httemplate/view/cust_main/packages/status.html +++ b/httemplate/view/cust_main/packages/status.html @@ -73,7 +73,12 @@ <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %> <% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %> -% if ( $part_pkg->option('suspend_bill', 1) ) { +% if ( $cust_pkg->option('suspend_bill', 1) +% || ( $part_pkg->option('suspend_bill', 1) +% && ! $cust_pkg->option('no_suspend_bill',1) +% ) +% ) +% { <% pkg_status_row_if( $cust_pkg, emt('Next bill'), 'bill', %opt, curuser=>$curuser ) %> % } <% pkg_status_row_if( $cust_pkg, emt('Will resume'), 'resume', %opt, curuser=>$curuser ) %> diff --git a/httemplate/view/cust_main_statement-pdf.cgi b/httemplate/view/cust_main_statement-pdf.cgi index 7a0e19838..7c2c20799 100755 --- a/httemplate/view/cust_main_statement-pdf.cgi +++ b/httemplate/view/cust_main_statement-pdf.cgi @@ -23,13 +23,17 @@ my $cust_main = qsearchs({ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, }); die "Customer #$custnum not found!" unless $cust_main; +my $cust_bill = ($cust_main->cust_bill)[-1] + or die "Customer #$custnum has no invoices!"; my $cust_statement = FS::cust_statement->new({ 'custnum' => $custnum, - 'statementnum' => 'ALL', #magic +# 'statementnum' => 'ALL', #magic + 'invnum' => $cust_bill->invnum, '_date' => time, }); + my $pdf = $cust_statement->print_pdf( '', $templatename ); http_header('Content-Type' => 'application/pdf' ); |