X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2FTemplate_Mixin.pm;h=eb14db00b1d8eff4245a33f1fdf895040a17c15f;hp=4a0834e05114c32258a782a76fdecf20b082855d;hb=38e4d0297f45363d7b95d503bd0892b994d67e4e;hpb=ffc85003aebc38056eb30fa9580b6395c92c0ee0 diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index 4a0834e05..eb14db00b 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -10,10 +10,12 @@ use vars qw( $invoice_lines @buf ); #yuck use List::Util qw(sum); #can't import first, it conflicts with cust_main.first use Date::Format; use Date::Language; +use Time::Local qw( timelocal ); use Text::Template 1.20; use File::Temp 0.14; +use Archive::Zip qw( :ERROR_CODES :CONSTANTS ); +use IO::Scalar; use HTML::Entities; -use Locale::Country; use Cwd; use FS::UID; use FS::Misc qw( send_email ); @@ -146,6 +148,10 @@ sub print_latex { $template ||= $self->_agent_template if $self->can('_agent_template'); + #the new way + $self->set('mode', $params{mode}) + if $params{mode}; + my $pkey = $self->primary_key; my $tmp_template = $self->table. '.'. $self->$pkey. '.XXXXXXXX'; @@ -560,6 +566,7 @@ sub print_generic { 'notice_name' => $notice_name, # escape? 'current_charges' => sprintf("%.2f", $self->charged), 'duedate' => $self->due_date2str('rdate'), #date_format? + 'duedate_long' => $self->due_date2str('long'), #customer info 'custnum' => $cust_main->display_custnum, @@ -649,7 +656,7 @@ sub print_generic { if ( $cust_main->country eq $countrydefault ) { $invoice_data{'country'} = ''; } else { - $invoice_data{'country'} = &$escape_function(code2country($cust_main->country)); + $invoice_data{'country'} = &$escape_function($cust_main->bill_country_full); } my @address = (); @@ -685,7 +692,12 @@ sub print_generic { my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance # my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits #my $balance_due = $self->owed + $pr_total - $cr_total; - my $balance_due = $self->owed + $pr_total; + my $balance_due = $self->owed; + if ( $self->enable_previous ) { + $balance_due += $pr_total; + } + # otherwise the previous balance is not shown, so including it in the + # balance due is just confusing # the sum of amount owed on all invoices # (this is used in the summary & on the payment coupon) @@ -698,12 +710,18 @@ sub print_generic { # XXX should be an FS::cust_bill method to set the defaults, instead # of checking the type here + # info from customer's last invoice before this one, for some + # summary formats + $invoice_data{'last_bill'} = {}; + my $last_bill = $self->previous_bill; if ( $last_bill ) { # "balance_date_range" unfortunately is unsuitable for this, since it # cares about application dates. We want to know the sum of all # _top-level transactions_ dated before the last invoice. + # + # still do this for the "Previous Balance" line of the summary block my @sql = map "$_ WHERE _date <= ? AND custnum = ?", ( "SELECT COALESCE( SUM(charged), 0 ) FROM cust_bill", @@ -736,19 +754,31 @@ sub print_generic { # longer stored in the database) $invoice_data{'true_previous_balance'} = $last_bill_balance; - # the change in balance from immediately after that invoice - # to immediately before this one - my $before_this_bill_balance = 0; + # Now, get all applications of credits/payments dated on or after the + # previous bill, to invoices before the current bill. (The + # credit/payment date restriction prevents these from intersecting + # the "Previous Balance" set.) + # These are "adjustments". The past due balance will be shown as + # Previous Balance - Adjustments. + my $adjustments = 0; + @sql = map { + "SELECT COALESCE(SUM(y.amount),0) FROM $_ JOIN cust_bill USING (invnum) + WHERE cust_bill._date < ? + AND x._date >= ? + AND cust_bill.custnum = ?" + } "cust_credit AS x JOIN cust_credit_bill y USING (crednum)", + "cust_pay AS x JOIN cust_bill_pay y USING (paynum)" + ; foreach (@sql) { my $delta = FS::Record->scalar_sql( $_, - $self->_date - 1, + $self->_date, + $last_bill->_date, $self->custnum, ); - $before_this_bill_balance += $delta; + $adjustments += $delta; } - $invoice_data{'balance_adjustments'} = - sprintf("%.2f", $last_bill_balance - $before_this_bill_balance); + $invoice_data{'balance_adjustments'} = sprintf("%.2f", $adjustments); warn sprintf("BALANCE ADJUSTMENTS: %.2f\n\n", $invoice_data{'balance_adjustments'} @@ -758,9 +788,7 @@ sub print_generic { # ($pr_total is used elsewhere but not as $previous_balance) $invoice_data{'previous_balance'} = sprintf("%.2f", $pr_total); - $invoice_data{'last_bill'} = { - '_date' => $last_bill->_date, #unformatted - }; + $invoice_data{'last_bill'}{'_date'} = $last_bill->_date; #unformatted my (@payments, @credits); # for formats that itemize previous payments foreach my $cust_pay ( qsearch('cust_pay', { @@ -802,11 +830,7 @@ sub print_generic { $invoice_data{'previous_payments'} = []; $invoice_data{'previous_credits'} = []; } - - # info from customer's last invoice before this one, for some - # summary formats - $invoice_data{'last_bill'} = {}; - + if ( $conf->exists('invoice_usesummary', $agentnum) ) { $invoice_data{'summarypage'} = $summarypage = 1; } @@ -820,35 +844,36 @@ sub print_generic { my @include = ( [ $tc, 'notes' ], [ 'invoice_', 'footer' ], [ 'invoice_', 'smallfooter', ], + [ 'invoice_', 'watermark' ], ); push @include, [ $tc, 'coupon', ] unless $params{'no_coupon'}; foreach my $i (@include) { + # load the configuration for this sub-template + my($base, $include) = @$i; my $inc_file = $conf->key_orbase("$base$format$include", $template); - my @inc_src; - - if ( $conf->exists($inc_file, $agentnum) - && length( $conf->config($inc_file, $agentnum) ) ) { - - @inc_src = $conf->config($inc_file, $agentnum); - } else { - - $inc_file = $conf->key_orbase("${base}latex$include", $template); - - my $convert_map = $convert_maps{$format}{$include}; - - @inc_src = map { s/\[\@--/$delimiters{$format}[0]/g; - s/--\@\]/$delimiters{$format}[1]/g; - $_; - } - &$convert_map( $conf->config($inc_file, $agentnum) ); + my @inc_src = $conf->config($inc_file, $agentnum); + if (!@inc_src) { + my $converter = $convert_maps{$format}{$include}; + if ( $converter ) { + # then attempt to convert LaTeX to the requested format + $inc_file = $conf->key_orbase($base.'latex'.$include, $template); + @inc_src = &$converter( $conf->config($inc_file, $agentnum) ); + foreach (@inc_src) { + # this isn't included in the convert_maps + my ($open, $close) = @{ $delimiters{$format} }; + s/\[\@--/$open/g; + s/--\@\]/$close/g; + } + } + } # else @inc_src is empty and that's fine - } + # make a Text::Template out of it my $inc_tt = new Text::Template ( TYPE => 'ARRAY', @@ -862,6 +887,8 @@ sub print_generic { die $error; } + # fill in variables + $invoice_data{$include} = $inc_tt->fill_in( HASH => \%invoice_data ); $invoice_data{$include} =~ s/\n+$// @@ -1038,7 +1065,7 @@ sub print_generic { # start setting up summary subtotals my @summary_subtotals; my $method = $conf->config('summary_subtotals_method'); - if ( $method and $method ne $conf->config($tc.'sections_method') ) { + if ( ( ref($self) ne 'FS::quotation' ) and $method and $method ne $conf->config($tc.'sections_method') ) { # then re-section them by the correct method my %section_method = ( by_category => 1 ); if ( $conf->config('summary_subtotals_method') eq 'location' ) { @@ -1276,11 +1303,17 @@ sub print_generic { if ( $multisection ) { if ( $taxtotal > 0 ) { + # there are taxes, so prepare the section to be displayed. + # $taxtotal already includes any line items that were already in the + # section (fees, taxes that are charged as packages for some reason). + # also set 'summarized' to false so that this isn't a summary-only + # section. $tax_section->{'subtotal'} = $other_money_char. sprintf('%.2f', $taxtotal); $tax_section->{'pretotal'} = 'New charges sub-total '. $total->{'total_amount'}; $tax_section->{'description'} = $self->mt($tax_description); + $tax_section->{'summarized'} = ''; # append it if it's not already there if ( !grep $tax_section, @sections ) { @@ -1295,15 +1328,6 @@ sub print_generic { } $invoice_data{'taxtotal'} = sprintf('%.2f', $taxtotal); - push @buf,['','-----------']; - push @buf,[$self->mt( - (!$self->enable_previous) - ? 'Total Charges' - : 'Total New Charges' - ), - $money_char. sprintf("%10.2f",$self->charged) ]; - push @buf,['','']; - ### # Totals ### @@ -1315,50 +1339,37 @@ sub print_generic { ); my $embolden_function = $embolden_functions{$format}; - if ( $self->can('_items_total') ) { # quotations - - my @new_total_items = $self->_items_total; + if ( $multisection ) { - foreach ( @new_total_items ) { - $_->{'total_item'} = &$embolden_function( $_->{'total_item'} ); - $_->{'total_amount'} = &$embolden_function( $other_money_char.$_->{'total_amount'}); - push @total_items, $_; + if ( $adjust_section->{'sort_weight'} ) { + $adjust_section->{'posttotal'} = $self->mt('Balance Forward').' '. + $other_money_char. sprintf("%.2f", ($self->billing_balance || 0) ); + } else{ + $adjust_section->{'pretotal'} = $self->mt('New charges total').' '. + $other_money_char. sprintf('%.2f', $self->charged ); } - } else { #normal invoice case + } + + if ( $self->can('_items_total') ) { # should always be true now - # calculate total, possibly including total owed on previous - # invoices - my $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; - if ( $self->enable_previous and !$conf->exists('previous_balance-exclude_from_total') ) { - $amount += $pr_total; - } + # even for multisection, need plain text version + + my @new_total_items = $self->_items_total; - $total->{'total_item'} = &$embolden_function($self->mt($item)); - $total->{'total_amount'} = - &$embolden_function( $other_money_char. sprintf( '%.2f', $amount ) ); - if ( $multisection ) { - if ( $adjust_section->{'sort_weight'} ) { - $adjust_section->{'posttotal'} = $self->mt('Balance Forward').' '. - $other_money_char. sprintf("%.2f", ($self->billing_balance || 0) ); - } else { - $adjust_section->{'pretotal'} = $self->mt('New charges total').' '. - $other_money_char. sprintf('%.2f', $self->charged ); - } - } else { - push @total_items, $total; - } push @buf,['','-----------']; - push @buf,[$item, - $money_char. - sprintf( '%10.2f', $amount ) - ]; - push @buf,['','']; + + foreach ( @new_total_items ) { + my ($item, $amount) = ($_->{'total_item'}, $_->{'total_amount'}); + $_->{'total_item'} = &$embolden_function( $item ); + $_->{'total_amount'} = &$embolden_function( $other_money_char.$amount ); + # but if it's multisection, don't append to @total_items. the adjust + # section has all this stuff + push @total_items, $_ if !$multisection; + push @buf, [ $item, $money_char.sprintf('%10.2f',$amount) ]; + } + + push @buf, [ '', '' ]; # if we're showing previous invoices, also show previous # credits and payments @@ -1366,12 +1377,11 @@ sub print_generic { and $self->can('_items_credits') and $self->can('_items_payments') ) { - #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments # credits my $credittotal = 0; foreach my $credit ( - $self->_items_credits( 'template' => $template, 'trim_len' => 50 ) + $self->_items_credits( 'template' => $template, 'trim_len' => 40 ) ) { my $total; @@ -1458,7 +1468,7 @@ sub print_generic { if ( $multisection && !$adjust_section->{sort_weight} ) { $adjust_section->{'posttotal'} = $total->{'total_item'}. ' '. $total->{'total_amount'}; - }else{ + } else { push @total_items, $total; } push @buf,['','-----------']; @@ -1534,7 +1544,7 @@ sub print_generic { # usage subtotals if ( $conf->exists('usage_class_summary') and $self->can('_items_usage_class_summary') ) { - my @usage_subtotals = $self->_items_usage_class_summary(escape => $escape_function); + my @usage_subtotals = $self->_items_usage_class_summary(escape => $escape_function, 'money_char' => $other_money_char); if ( @usage_subtotals ) { unshift @sections, $usage_subtotals[0]->{section}; # do not summarize unshift @detail_items, @usage_subtotals; @@ -1662,6 +1672,13 @@ sub print_generic { } else { # this is where we actually create the invoice + if ( $params{no_addresses} ) { + delete $invoice_data{$_} foreach qw( + payname company address1 address2 city state zip country + ); + $invoice_data{returnaddress} = '~'; + } + warn "filling in template for invoice ". $self->invnum. "\n" if $DEBUG; warn join("\n", map " $_ => ". $invoice_data{$_}, keys %invoice_data). "\n" @@ -1904,6 +1921,12 @@ sub due_date { my $duedate = ''; if ( $self->terms =~ /^\s*Net\s*(\d+)\s*$/ ) { $duedate = $self->_date() + ( $1 * 86400 ); + } elsif ( $self->terms =~ /^End of Month$/ ) { + my ($mon,$year) = (localtime($self->_date) )[4,5]; + $mon++; + until ( $mon < 12 ) { $mon -= 12; $year++; } + my $nextmonth_first = timelocal(0,0,0,1,$mon,$year); + $duedate = $nextmonth_first - 86400; } $duedate; } @@ -1924,12 +1947,22 @@ sub due_date2str { sub balance_due_msg { my $self = shift; my $msg = $self->mt('Balance Due'); - return $msg unless $self->terms; - if ( $self->due_date ) { - $msg .= ' - ' . $self->mt('Please pay by'). ' '. - $self->due_date2str('short'); - } elsif ( $self->terms ) { - $msg .= ' - '. $self->terms; + return $msg unless $self->terms; # huh? + if ( !$self->conf->exists('invoice_show_prior_due_date') + or $self->conf->exists('invoice_sections') ) { + # if enabled, the due date is shown with Total New Charges (see + # _items_total) and not here + # (yes, or if invoice_sections is enabled; this is just for compatibility) + if ( $self->due_date ) { + my $please_pay_by = + $self->conf->config('invoice_pay_by_msg', $self->agentnum) + || 'Please pay by [_1]'; + $msg .= ' - ' . $self->mt($please_pay_by, $self->due_date2str('short')). + ' ' + unless $self->conf->config_bool('invoice_omit_due_date',$self->agentnum); + } elsif ( $self->terms ) { + $msg .= ' - '. $self->mt($self->terms); + } } $msg; } @@ -2113,11 +2146,20 @@ sub generate_email { if (!@text) { - warn "$me generating plain text invoice" - if $DEBUG; + if ( $conf->config($tc.'template') ) { + + warn "$me generating plain text invoice" + if $DEBUG; - # 'print_text' argument is no longer used - @text = $self->print_text(\%args); + # 'print_text' argument is no longer used + @text = $self->print_text(\%args); + + } else { + + warn "$me no plain text version exists; sending empty message body" + if $DEBUG; + + } } @@ -2232,15 +2274,42 @@ sub generate_email { my @otherparts = (); if ( ref($self) eq 'FS::cust_bill' && $cust_main->email_csv_cdr ) { - push @otherparts, build MIME::Entity - 'Type' => 'text/csv', - 'Encoding' => '7bit', - 'Data' => [ map { "$_\n" } - $self->call_details('prepend_billed_number' => 1) - ], - 'Disposition' => 'attachment', - 'Filename' => 'usage-'. $self->invnum. '.csv', - ; + if ( $conf->config('voip-cdr_email_attach') eq 'zip' ) { + + my $data = join('', map "$_\n", + $self->call_details(prepend_billed_number=>1) + ); + + my $zip = new Archive::Zip; + my $file = $zip->addString( $data, 'usage-'.$self->invnum.'.csv' ); + $file->desiredCompressionMethod( COMPRESSION_DEFLATED ); + + my $zipdata = ''; + my $SH = IO::Scalar->new(\$zipdata); + my $status = $zip->writeToFileHandle($SH); + die "Error zipping CDR attachment: $!" unless $status == AZ_OK; + + push @otherparts, build MIME::Entity + 'Type' => 'application/zip', + 'Encoding' => 'base64', + 'Data' => $zipdata, + 'Disposition' => 'attachment', + 'Filename' => 'usage-'. $self->invnum. '.zip', + ; + + } else { # } elsif ( $conf->config('voip-cdr_email_attach') eq 'csv' ) { + + push @otherparts, build MIME::Entity + 'Type' => 'text/csv', + 'Encoding' => '7bit', + 'Data' => [ map { "$_\n" } + $self->call_details('prepend_billed_number' => 1) + ], + 'Disposition' => 'attachment', + 'Filename' => 'usage-'. $self->invnum. '.csv', + ; + + } } @@ -2299,6 +2368,110 @@ sub mimebuild_pdf { ); } +=item postal_mail_fsinc + +Sends this invoice to the Freeside Internet Services, Inc. print and mail +service. + +=cut + +use CAM::PDF; +use IO::Socket::SSL; +use LWP::UserAgent; +use HTTP::Request::Common qw( POST ); +use JSON::XS; +use MIME::Base64; +sub postal_mail_fsinc { + my ( $self, %opt ) = @_; + + my $url = 'https://ws.freeside.biz/print'; + + my $cust_main = $self->cust_main; + my $agentnum = $cust_main->agentnum; + my $bill_location = $cust_main->bill_location; + + die "Extra charges for international mailing; contact support\@freeside.biz to enable\n" + if $bill_location->country ne 'US'; + + my $conf = new FS::Conf; + + my @company_address = $conf->config('company_address', $agentnum); + my ( $company_address1, $company_address2, $company_city, $company_state, $company_zip ); + if ( $company_address[2] =~ /^\s*(\S.*\S)\s*[\s,](\w\w),?\s*(\d{5}(-\d{4})?)\s*$/ ) { + $company_address1 = $company_address[0]; + $company_address2 = $company_address[1]; + $company_city = $1; + $company_state = $2; + $company_zip = $3; + } elsif ( $company_address[1] =~ /^\s*(\S.*\S)\s*[\s,](\w\w),?\s*(\d{5}(-\d{4})?)\s*$/ ) { + $company_address1 = $company_address[0]; + $company_address2 = ''; + $company_city = $1; + $company_state = $2; + $company_zip = $3; + } else { + die "Unparsable company_address; contact support\@freeside.biz\n"; + } + $company_city =~ s/,$//; + + my $file = $self->print_pdf(%opt, 'no_addresses' => 1); + my $pages = CAM::PDF->new($file)->numPages; + + my $ua = LWP::UserAgent->new( + 'ssl_opts' => { + verify_hostname => 0, + SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE, + SSL_version => 'SSLv3', + } + ); + my $response = $ua->request( POST $url, [ + 'support-key' => scalar($conf->config('support-key')), + 'file' => encode_base64($file), + 'pages' => $pages, + + #from: + 'company_name' => scalar( $conf->config('company_name', $agentnum) ), + 'company_address1' => $company_address1, + 'company_address2' => $company_address2, + 'company_city' => $company_city, + 'company_state' => $company_state, + 'company_zip' => $company_zip, + 'company_country' => 'US', + 'company_phonenum' => scalar($conf->config('company_phonenum', $agentnum)), + 'company_email' => scalar($conf->config('invoice_from', $agentnum)), + + #to: + 'name' => ( $cust_main->payname + && $cust_main->payby !~ /^(CARD|DCRD|CHEK|DCHK)$/ + ? $cust_main->payname + : $cust_main->contact_firstlast + ), + 'company' => $cust_main->company, + 'address1' => $bill_location->address1, + 'address2' => $bill_location->address2, + 'city' => $bill_location->city, + 'state' => $bill_location->state, + 'zip' => $bill_location->zip, + 'country' => $bill_location->country, + ]); + + die "Print connection error: ". $response->message. + ' ('. $response->as_string. ")\n" + unless $response->is_success; + + local $@; + my $content = eval { decode_json($response->content) }; + die "Print JSON error : $@\n" if $@; + + die $content->{error}."\n" + if $content->{error}; + + #TODO: store this so we can query for a status later + warn "Invoice printed, ID ". $content->{id}. "\n"; + + $content->{id}; +} + =item _items_sections OPTIONS Generate section information for all items appearing on this invoice. @@ -2499,7 +2672,6 @@ sub _items_sections { foreach my $sectionname (keys %{ $s->{$locationnum} }) { my $section = { 'subtotal' => $s->{$locationnum}{$sectionname}, - 'post_total' => $post_total, 'sort_weight' => 0, }; if ( $locationnum ) { @@ -2860,6 +3032,7 @@ sub _items_fee { my %base_invnums; # invnum => invoice date foreach ($cust_bill_pkg->cust_bill_pkg_fee) { if ($_->base_invnum) { + # XXX what if base_bill has been voided? my $base_bill = FS::cust_bill->by_key($_->base_invnum); my $base_date = $self->time2str_local('short', $base_bill->_date) if $base_bill; @@ -3008,15 +3181,19 @@ sub _items_cust_bill_pkg { } my $summary_page = $opt{summary_page} || ''; #unused my $multisection = defined($category) || defined($locationnum); - my $discount_show_always = 0; + # this variable is the value of the config setting, not whether it applies + # to this particular line item. + my $discount_show_always = $conf->exists('discount-show-always'); - my $maxlength = $conf->config('cust_bill-latex_lineitem_maxlength') || 50; + my $maxlength = $conf->config('cust_bill-latex_lineitem_maxlength') || 40; my $cust_main = $self->cust_main;#for per-agent cust_bill-line_item-ate_style # for location labels: use default location on the invoice date my $default_locationnum; - if ( $self->custnum ) { + if ( $conf->exists('invoice-all_pkg_addresses') ) { + $default_locationnum = 0; # treat them all as non-default + } elsif ( $self->custnum ) { my $h_cust_main; my @h_search = FS::h_cust_main->sql_h_search($self->_date); $h_cust_main = qsearchs({ @@ -3027,7 +3204,10 @@ sub _items_cust_bill_pkg { }) || $cust_main; $default_locationnum = $h_cust_main->ship_locationnum; } elsif ( $self->prospectnum ) { - $default_locationnum = $self->prospect_main->cust_location->locationnum; + my $cust_location = qsearchs('cust_location', + { prospectnum => $self->prospectnum, + disabled => '' }); + $default_locationnum = $cust_location->locationnum if $cust_location; } my @b = (); # accumulator for the line item hashes that we'll return @@ -3045,11 +3225,13 @@ sub _items_cust_bill_pkg { if (exists($_->{unit_amount})) { $_->{unit_amount} = sprintf( "%.2f", $_->{unit_amount} ); } - push @b, { %$_ } - if $_->{amount} != 0 - || $discount_show_always - || ( ! $_->{_is_setup} && $_->{recur_show_zero} ) - || ( $_->{_is_setup} && $_->{setup_show_zero} ) + push @b, { %$_ }; + # we already decided to create this display line; don't reconsider it + # now. + # if $_->{amount} != 0 + # || $discount_show_always + # || ( ! $_->{_is_setup} && $_->{recur_show_zero} ) + # || ( $_->{_is_setup} && $_->{setup_show_zero} ) ; $_ = undef; } @@ -3116,8 +3298,10 @@ sub _items_cust_bill_pkg { # quotation_pkgs are never fees, so don't worry about the case where # part_pkg is undefined + my @details = $cust_bill_pkg->details; + # and I guess they're never bundled either? - if ( $cust_bill_pkg->setup != 0 ) { + if (( $cust_bill_pkg->setup != 0 ) || ( $cust_bill_pkg->setup_show_zero )) { my $description = $desc; $description .= ' Setup' if $cust_bill_pkg->recur != 0 @@ -3131,13 +3315,14 @@ sub _items_cust_bill_pkg { 'amount' => sprintf("%.2f", $cust_bill_pkg->setup), 'unit_amount' => sprintf("%.2f", $cust_bill_pkg->unitsetup), 'quantity' => $cust_bill_pkg->quantity, + 'ext_description' => \@details, 'preref_html' => ( $opt{preref_callback} ? &{ $opt{preref_callback} }( $cust_bill_pkg ) : '' ), }; } - if ( $cust_bill_pkg->recur != 0 ) { + if (( $cust_bill_pkg->recur != 0 ) || ( $cust_bill_pkg->recur_show_zero )) { #push @b, { $r = { 'pkgnum' => $cust_bill_pkg->pkgpart, #so it displays in Ref @@ -3145,6 +3330,7 @@ sub _items_cust_bill_pkg { 'amount' => sprintf("%.2f", $cust_bill_pkg->recur), 'unit_amount' => sprintf("%.2f", $cust_bill_pkg->unitrecur), 'quantity' => $cust_bill_pkg->quantity, + 'ext_description' => \@details, 'preref_html' => ( $opt{preref_callback} ? &{ $opt{preref_callback} }( $cust_bill_pkg ) : '' @@ -3176,6 +3362,7 @@ sub _items_cust_bill_pkg { if ( (!$type || $type eq 'S') && ( $cust_bill_pkg->setup != 0 || $cust_bill_pkg->setup_show_zero + || ($discount_show_always and $cust_bill_pkg->unitsetup > 0) ) ) { @@ -3183,10 +3370,13 @@ sub _items_cust_bill_pkg { warn "$me _items_cust_bill_pkg adding setup\n" if $DEBUG > 1; + # append the word 'Setup' to the setup line if there's going to be + # a recur line for the same package (i.e. not a one-time charge) + # XXX localization my $description = $desc; $description .= ' Setup' if $cust_bill_pkg->recur != 0 - || $discount_show_always + || ($discount_show_always and $cust_bill_pkg->unitrecur > 0) || $cust_bill_pkg->recur_show_zero; $description .= $cust_bill_pkg->time_period_pretty( $part_pkg, @@ -3203,8 +3393,11 @@ sub _items_cust_bill_pkg { # always pass the svc_label through to the template, even if # not displaying it as an ext_description my @svc_labels = map &{$escape_function}($_), - $cust_pkg->h_labels_short($self->_date, undef, 'I'); - + $cust_pkg->h_labels_short($self->_date, + undef, + 'I', + $self->conf->{locale}, + ); $svc_label = $svc_labels[0]; unless ( $cust_pkg->part_pkg->hide_svc_detail @@ -3250,11 +3443,18 @@ sub _items_cust_bill_pkg { } + # should we show a recur line? + # if type eq 'S', then NO, because we've been told not to. + # otherwise, show the recur line if: + # - there's a recurring charge + # - or recur_show_zero is on + # - or there's a positive unitrecur (so it's been discounted to zero) + # and discount-show-always is on if ( ( !$type || $type eq 'R' || $type eq 'U' ) && ( $cust_bill_pkg->recur != 0 - || $cust_bill_pkg->setup == 0 - || $discount_show_always + || !defined($s) + || ($discount_show_always and $cust_bill_pkg->unitrecur > 0) || $cust_bill_pkg->recur_show_zero ) ) @@ -3287,7 +3487,9 @@ sub _items_cust_bill_pkg { push @dates, undef if !$prev; my @svc_labels = map &{$escape_function}($_), - $cust_pkg->h_labels_short(@dates, 'I'); + $cust_pkg->h_labels_short(@dates, + 'I', + $self->conf->{locale}); $svc_label = $svc_labels[0]; # show service labels, unless... @@ -3496,9 +3698,6 @@ sub _items_cust_bill_pkg { } # foreach $display - $discount_show_always = ($cust_bill_pkg->cust_bill_pkg_discount - && $conf->exists('discount-show-always')); - } foreach ( $s, $r, ($opt{skip_usage} ? () : $u ), $d ) { @@ -3510,11 +3709,11 @@ sub _items_cust_bill_pkg { $_->{unit_amount} = sprintf( "%.2f", $_->{unit_amount} ); } - push @b, { %$_ } - if $_->{amount} != 0 - || $discount_show_always - || ( ! $_->{_is_setup} && $_->{recur_show_zero} ) - || ( $_->{_is_setup} && $_->{setup_show_zero} ) + push @b, { %$_ }; + #if $_->{amount} != 0 + # || $discount_show_always + # || ( ! $_->{_is_setup} && $_->{recur_show_zero} ) + # || ( $_->{_is_setup} && $_->{setup_show_zero} ) } }