X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_bill.pm;h=38e98ce5915f8c2f382c5487b2693c40201b8e79;hb=63bc0021330d9dead09e646bd968ed3e674bceeb;hp=cf339a8c714f324780d4e7c1fa6d2cf6a8a1fc54;hpb=c1d9ee594c7a950e44c0bc6d7e4e25b3d62b9f7b;p=freeside.git diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index cf339a8c7..38e98ce59 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -1,7 +1,7 @@ package FS::cust_bill; use strict; -use vars qw( @ISA $DEBUG $me $conf $money_char ); +use vars qw( @ISA $DEBUG $me $conf $money_char $date_format $rdate_format ); use vars qw( $invoice_lines @buf ); #yuck use Fcntl qw(:flock); #for spool_csv use List::Util qw(min max); @@ -37,13 +37,15 @@ use FS::payby; @ISA = qw( FS::cust_main_Mixin FS::Record ); -$DEBUG = 1; +$DEBUG = 0; $me = '[FS::cust_bill]'; #ask FS::UID to run this stuff for us later FS::UID->install_callback( sub { $conf = new FS::Conf; - $money_char = $conf->config('money_char') || '$'; + $money_char = $conf->config('money_char') || '$'; + $date_format = $conf->config('date_format') || '%x'; + $rdate_format = $conf->config('date_format') || '%m/%d/%Y'; } ); =head1 NAME @@ -2276,11 +2278,13 @@ sub print_generic { } + my $agentnum = $self->cust_main->agentnum; + my %invoice_data = ( #invoice from info - 'company_name' => scalar( $conf->config('company_name', $self->cust_main->agentnum) ), - 'company_address' => join("\n", $conf->config('company_address', $self->cust_main->agentnum) ). "\n", + 'company_name' => scalar( $conf->config('company_name', $agentnum) ), + 'company_address' => join("\n", $conf->config('company_address', $agentnum) ). "\n", 'returnaddress' => $returnaddress, 'agent' => &$escape_function($cust_main->agent->agent), @@ -2292,7 +2296,7 @@ sub print_generic { 'template' => $template, #params{'template'}, 'notice_name' => ($params{'notice_name'} || 'Invoice'),#escape_function? 'current_charges' => sprintf("%.2f", $self->charged), - 'duedate' => $self->due_date2str('%m/%d/%Y'), #date_format? + 'duedate' => $self->due_date2str($rdate_format), #date_format? #customer info 'custnum' => $cust_main->display_custnum, @@ -2306,7 +2310,21 @@ sub print_generic { 'unitprices' => $conf->exists('invoice-unitprice'), 'smallernotes' => $conf->exists('invoice-smallernotes'), 'smallerfooter' => $conf->exists('invoice-smallerfooter'), + 'balance_due_below_line' => $conf->exists('balance_due_below_line'), + #layout info -- would be fancy to calc some of this and bury the template + # here in the code + 'topmargin' => scalar($conf->config('invoice_latextopmargin', $agentnum)), + 'headsep' => scalar($conf->config('invoice_latexheadsep', $agentnum)), + 'textheight' => scalar($conf->config('invoice_latextextheight', $agentnum)), + 'extracouponspace' => scalar($conf->config('invoice_latexextracouponspace', $agentnum)), + 'couponfootsep' => scalar($conf->config('invoice_latexcouponfootsep', $agentnum)), + 'verticalreturnaddress' => $conf->exists('invoice_latexverticalreturnaddress', $agentnum), + 'addresssep' => scalar($conf->config('invoice_latexaddresssep', $agentnum)), + 'amountenclosedsep' => scalar($conf->config('invoice_latexcouponamountenclosedsep', $agentnum)), + 'coupontoaddresssep' => scalar($conf->config('invoice_latexcoupontoaddresssep', $agentnum)), + 'addcompanytoaddress' => $conf->exists('invoice_latexcouponaddcompanytoaddress', $agentnum), + # better hang on to conf_dir for a while (for old templates) 'conf_dir' => "$FS::UID::conf_dir/conf.$FS::UID::datasrc", @@ -2375,8 +2393,6 @@ sub print_generic { $invoice_data{'previous_balance'} = sprintf("%.2f", $pr_total); $invoice_data{'balance'} = sprintf("%.2f", $balance_due); - my $agentnum = $self->cust_main->agentnum; - my $summarypage = ''; if ( $conf->exists('invoice_usesummary', $agentnum) ) { $summarypage = 1; @@ -2459,27 +2475,48 @@ sub print_generic { sprintf('%.2f', $pr_total), 'summarized' => $summarypage ? 'Y' : '', }; + $previous_section->{posttotal} = '0 / 30 / 60/ 90 days overdue '. + join(' / ', map { $cust_main->balance_date_range(@$_) } + $self->_prior_month30s + ) + if $conf->exists('invoice_include_aging'); my $taxtotal = 0; my $tax_section = { 'description' => 'Taxes, Surcharges, and Fees', 'subtotal' => $taxtotal, # adjusted below 'summarized' => $summarypage ? 'Y' : '', }; + my $tax_weight = _pkg_category($tax_section->{description}) + ? _pkg_category($tax_section->{description})->weight + : 0; + $tax_section->{'summarized'} = $summarypage && !$tax_weight ? 'Y' : ''; + $tax_section->{'sort_weight'} = $tax_weight; + my $adjusttotal = 0; my $adjust_section = { 'description' => 'Credits, Payments, and Adjustments', 'subtotal' => 0, # adjusted below 'summarized' => $summarypage ? 'Y' : '', }; + my $adjust_weight = _pkg_category($adjust_section->{description}) + ? _pkg_category($adjust_section->{description})->weight + : 0; + $adjust_section->{'summarized'} = $summarypage && !$adjust_weight ? 'Y' : ''; + $adjust_section->{'sort_weight'} = $adjust_weight; my $unsquelched = $params{unsquelch_cdr} || $cust_main->squelch_cdr ne 'Y'; my $multisection = $conf->exists('invoice_sections', $cust_main->agentnum); + $invoice_data{'multisection'} = $multisection; my $late_sections = []; + my $extra_sections = []; + my $extra_lines = (); if ( $multisection ) { - my ($extra_sections, $extra_lines) = + ($extra_sections, $extra_lines) = $self->_items_extra_usage_sections($escape_function, $format) if $conf->exists('usage_class_as_a_section', $cust_main->agentnum); + push @$extra_sections, $adjust_section if $adjust_section->{sort_weight}; + push @detail_items, @$extra_lines if $extra_lines; push @sections, $self->_items_sections( $late_sections, # this could stand a refactor @@ -2538,6 +2575,12 @@ sub print_generic { foreach my $section (@sections, @$late_sections) { + # begin some normalization + $section->{'subtotal'} = $section->{'amount'} + if $multisection + && !exists($section->{subtotal}) + && exists($section->{amount}); + $invoice_data{finance_amount} = sprintf('%.2f', $section->{'subtotal'} ) if ( $invoice_data{finance_section} && $section->{'description'} eq $invoice_data{finance_section} ); @@ -2546,7 +2589,7 @@ sub print_generic { sprintf('%.2f', $section->{'subtotal'}) if $multisection; - # begin some normalization + # continue some normalization $section->{'amount'} = $section->{'subtotal'} if $multisection; @@ -2557,6 +2600,7 @@ sub print_generic { ); } + my $multilocation = scalar($cust_main->cust_location); #too expensive? my %options = (); $options{'section'} = $section if $multisection; $options{'format'} = $format; @@ -2564,6 +2608,9 @@ sub print_generic { $options{'format_function'} = sub { () } unless $unsquelched; $options{'unsquelched'} = $unsquelched; $options{'summary_page'} = $summarypage; + $options{'skip_usage'} = + scalar(@$extra_sections) && !grep{$section == $_} @$extra_sections; + $options{'multilocation'} = $multilocation; foreach my $line_item ( $self->_items_pkg(%options) ) { my $detail = { @@ -2605,7 +2652,9 @@ sub print_generic { $invoice_data{current_less_finance} = sprintf('%.2f', $self->charged - $invoice_data{finance_amount} ); - if ( $multisection && !$conf->exists('disable_previous_balance') ) { + if ( $multisection && !$conf->exists('disable_previous_balance') + || $conf->exists('previous_balance-summary_only') ) + { unshift @sections, $previous_section if $pr_total; } @@ -2672,32 +2721,34 @@ sub print_generic { { my $total = {}; - $total->{'total_item'} = &$embolden_function('Total'); + my $item = 'Total'; + $item = $conf->config('previous_balance-exclude_from_total') + || 'Total New Charges' + if $conf->exists('previous_balance-exclude_from_total'); + my $amount = $self->charged + + ( $conf->exists('disable_previous_balance') || + $conf->exists('previous_balance-exclude_from_total') + ? 0 + : $pr_total + ); + $total->{'total_item'} = &$embolden_function($item); $total->{'total_amount'} = - &$embolden_function( - $other_money_char. - sprintf( '%.2f', - $self->charged + ( $conf->exists('disable_previous_balance') - ? 0 - : $pr_total - ) - ) - ); + &$embolden_function( $other_money_char. sprintf( '%.2f', $amount ) ); if ( $multisection ) { - $adjust_section->{'pretotal'} = 'New charges total '. $other_money_char. - sprintf('%.2f', $self->charged ); + if ( $adjust_section->{'sort_weight'} ) { + $adjust_section->{'posttotal'} = 'Balance Forward '. $other_money_char. + sprintf("%.2f", ($self->billing_balance || 0) ); + } else { + $adjust_section->{'pretotal'} = 'New charges total '. $other_money_char. + sprintf('%.2f', $self->charged ); + } }else{ push @total_items, $total; } push @buf,['','-----------']; - push @buf,['Total Charges', + push @buf,[$item, $money_char. - sprintf( '%10.2f', $self->charged + - ( $conf->exists('disable_previous_balance') - ? 0 - : $pr_total - ) - ) + sprintf( '%10.2f', $amount ) ]; push @buf,['','']; } @@ -2768,7 +2819,8 @@ sub print_generic { if ( $multisection ) { $adjust_section->{'subtotal'} = $other_money_char. sprintf('%.2f', $adjusttotal); - push @sections, $adjust_section; + push @sections, $adjust_section + unless $adjust_section->{sort_weight}; } { @@ -2782,7 +2834,7 @@ sub print_generic { : $self->owed + $pr_total ) ); - if ( $multisection ) { + if ( $multisection && !$adjust_section->{sort_weight} ) { $adjust_section->{'posttotal'} = $total->{'total_item'}. ' '. $total->{'total_amount'}; }else{ @@ -2795,6 +2847,18 @@ sub print_generic { } if ( $multisection ) { + if ($conf->exists('svc_phone_sections')) { + my $total; + $total->{'total_item'} = &$embolden_function($self->balance_due_msg); + $total->{'total_amount'} = + &$embolden_function( + $other_money_char. sprintf('%.2f', $self->owed + $pr_total) + ); + my $last_section = pop @sections; + $last_section->{'posttotal'} = $total->{'total_item'}. ' '. + $total->{'total_amount'}; + push @sections, $last_section; + } push @sections, @$late_sections if $unsquelched; } @@ -2892,6 +2956,22 @@ sub print_generic { } } +# helper routine for generating date ranges +sub _prior_month30s { + my $self = shift; + my @ranges = ( + [ 1, 2592000 ], # 0-30 days ago + [ 2592000, 5184000 ], # 30-60 days ago + [ 5184000, 7776000 ], # 60-90 days ago + [ 7776000, 0 ], # 90+ days ago + ); + + map { [ $_->[0] ? $self->_date - $_->[0] - 1 : '', + $_->[1] ? $self->_date - $_->[1] - 1 : '', + ] } + @ranges; +} + =item print_ps HASHREF | [ TIME [ , TEMPLATE ] ] Returns an postscript invoice, as a scalar. @@ -3092,7 +3172,7 @@ sub balance_due_msg { my $msg = 'Balance Due'; return $msg unless $self->terms; if ( $self->due_date ) { - $msg .= ' - Please pay by '. $self->due_date2str('%x'); + $msg .= ' - Please pay by '. $self->due_date2str($date_format); } elsif ( $self->terms ) { $msg .= ' - '. $self->terms; } @@ -3104,7 +3184,7 @@ sub balance_due_date { my $duedate = ''; if ( $conf->exists('invoice_default_terms') && $conf->config('invoice_default_terms')=~ /^\s*Net\s*(\d+)\s*$/ ) { - $duedate = time2str("%m/%d/%Y", $self->_date + ($1*86400) ); + $duedate = time2str($rdate_format, $self->_date + ($1*86400) ); } $duedate; } @@ -3129,7 +3209,7 @@ Returns a string with the date, for example: "3/20/2008" sub _date_pretty { my $self = shift; - time2str('%x', $self->_date); + time2str($date_format, $self->_date); } use vars qw(%pkg_category_cache); @@ -3176,7 +3256,8 @@ sub _items_sections { } if ($type && $type eq 'U') { - $late_subtotal{$section} += $usage; + $late_subtotal{$section} += $usage + unless scalar(@$extra_sections); } } else { @@ -3199,7 +3280,8 @@ sub _items_sections { } if ($type && $type eq 'U') { - $subtotal{$section} += $usage; + $subtotal{$section} += $usage + unless scalar(@$extra_sections); } } @@ -3213,8 +3295,11 @@ sub _items_sections { push @$late, map { { 'description' => &{$escape}($_), 'subtotal' => $late_subtotal{$_}, 'post_total' => 1, - 'sort_weight' => _pkg_category($_)->weight, - (_pkg_category($_)->condense + 'sort_weight' => ( _pkg_category($_) + ? _pkg_category($_)->weight + : 0 + ), + ((_pkg_category($_) && _pkg_category($_)->condense) ? $self->_condense_section($format) : () ), @@ -3233,8 +3318,11 @@ sub _items_sections { 'subtotal' => $subtotal{$_}, 'summarized' => $not_tax{$_} ? '' : 'Y', 'tax_section' => $not_tax{$_} ? '' : 'Y', - 'sort_weight' => _pkg_category($_)->weight, - (_pkg_category($_)->condense + 'sort_weight' => ( _pkg_category($_) + ? _pkg_category($_)->weight + : 0 + ), + ((_pkg_category($_) && _pkg_category($_)->condense) ? $self->_condense_section($format) : () ), @@ -3263,7 +3351,9 @@ my %condensed_format = ( 'fields' => [ sub { shift->{description} }, sub { shift->{quantity} }, - sub { shift->{amount} }, + sub { my($href, %opt) = @_; + ($opt{dollar} || ''). $href->{amount}; + }, ], 'align' => [ qw( l r r ) ], 'span' => [ qw( 5 1 1 ) ], # unitprices? @@ -3337,6 +3427,7 @@ sub _condensed_description_generator { my ( $f, $prefix, $suffix, $separator, $column ) = _condensed_generator_defaults($format); + my $money_char = '$'; if ($format eq 'latex') { $prefix = "\\hline\n\\multicolumn{1}{c}{\\rule{0pt}{2.5ex}~} &\n"; $suffix = '\\\\'; @@ -3345,6 +3436,7 @@ sub _condensed_description_generator { sub { my ($d,$a,$s,$w) = @_; return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{\\textbf{$d}}}"; }; + $money_char = '\\dollar'; }elsif ( $format eq 'html' ) { $prefix = '">