correct invoice package address display and reduce false laziness
[freeside.git] / FS / FS / cust_bill.pm
index c7b4df7..0f08aaa 100644 (file)
@@ -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 );
 use vars qw( $invoice_lines @buf ); #yuck
 use Fcntl qw(:flock); #for spool_csv
 use List::Util qw(min max);
@@ -37,13 +37,14 @@ 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') || '$';  
+  $date_format = $conf->config('date_format') || '%x';  
 } );
 
 =head1 NAME
@@ -2459,27 +2460,47 @@ 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);
   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
@@ -2557,6 +2578,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 +2586,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 = {
@@ -2684,8 +2709,13 @@ sub print_generic {
                )
       );
     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;
     }
@@ -2768,7 +2798,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 +2813,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 +2826,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 +2935,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.
@@ -3176,7 +3235,8 @@ sub _items_sections {
           }
           
           if ($type && $type eq 'U') {
-            $late_subtotal{$section} += $usage;
+            $late_subtotal{$section} += $usage
+              unless scalar(@$extra_sections);
           }
 
         } else {
@@ -3199,7 +3259,8 @@ sub _items_sections {
           }
           
           if ($type && $type eq 'U') {
-            $subtotal{$section} += $usage;
+            $subtotal{$section} += $usage
+              unless scalar(@$extra_sections);
           }
 
         }
@@ -3691,10 +3752,10 @@ sub _items_svc_phone_sections {
 
   my %sectionmap = ();
   my $simple = new FS::usage_class { format => 'simple' }; #bleh
-  my $minimal = new FS::usage_class { format => 'minimal' }; #bleh
+  my $usage_simple = new FS::usage_class { format => 'usage_simple' }; #bleh
   foreach ( keys %sections ) {
     my $summary = $sections{$_}{sort_weight} < 0 ? 1 : 0;
-    my $usage_class = $summary ? $simple : $minimal;
+    my $usage_class = $summary ? $simple : $usage_simple;
     my $ending = $summary ? ' usage charges' : '';
     $sectionmap{$_} = { 'description' => &{$escape}($_. $ending),
                         'amount'    => $sections{$_}{amount},    #subtotal
@@ -3704,6 +3765,7 @@ sub _items_svc_phone_sections {
                         'tax_section' => '',
                         'phonenum'    => $sections{$_}{phonenum},
                         'sort_weight' => $sections{$_}{sort_weight},
+                        'post_total'  => $summary, #inspire pagebreak
                         (
                           ( map { $_ => $usage_class->$_($format) }
                             qw( description_generator
@@ -3759,9 +3821,13 @@ sub _items_previous {
   my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
   my @b = ();
   foreach ( @pr_cust_bill ) {
+    my $date = $conf->exists('invoice_show_prior_due_date')
+               ? 'due '. $_->due_date2str($date_format)
+               : time2str('%x', $_->_date); # date_format here, too,
+                                            # but fix _items_cust_bill_pkg,
+                                            # header, others?
     push @b, {
-      'description' => 'Previous Balance, Invoice #'. $_->invnum. 
-                       ' ('. time2str('%x',$_->_date). ')',
+      'description' => 'Previous Balance, Invoice #'. $_->invnum. " ($date)",
       #'pkgpart'     => 'N/A',
       'pkgnum'      => 'N/A',
       'amount'      => sprintf("%.2f", $_->owed),
@@ -3837,17 +3903,20 @@ sub _items_cust_bill_pkg {
   my $unsquelched = $opt{unsquelched} || '';
   my $section = $opt{section}->{description} if $opt{section};
   my $summary_page = $opt{summary_page} || '';
+  my $multilocation = $opt{multilocation} || '';
 
   my @b = ();
   my ($s, $r, $u) = ( undef, undef, undef );
   foreach my $cust_bill_pkg ( @$cust_bill_pkg )
   {
 
-    foreach ( $s, $r, $u ) {
+    foreach ( $s, $r, ($opt{skip_usage} ? () : $u ) ) {
       if ( $_ && !$cust_bill_pkg->hidden ) {
         $_->{amount}      = sprintf( "%.2f", $_->{amount} ),
+        $_->{amount}      =~ s/^\-0\.00$/0.00/;
         $_->{unit_amount} = sprintf( "%.2f", $_->{unit_amount} ),
-        push @b, { %$_ };
+        push @b, { %$_ }
+          unless $_->{amount} == 0;
         $_ = undef;
       }
     }
@@ -3882,10 +3951,18 @@ sub _items_cust_bill_pkg {
           $description .= ' Setup' if $cust_bill_pkg->recur != 0;
 
           my @d = ();
-          push @d, map &{$escape_function}($_),
-                       $cust_pkg->h_labels_short($self->_date)
-            unless $cust_pkg->part_pkg->hide_svc_detail
-                || $cust_bill_pkg->hidden;
+          unless ( $cust_pkg->part_pkg->hide_svc_detail
+                || $cust_bill_pkg->hidden )
+          {
+            push @d, map &{$escape_function}($_),
+                         $cust_pkg->h_labels_short($self->_date);
+            if ( $multilocation ) {
+              my $loc = $cust_pkg->location_label;
+              $loc = substr($desc, 0, 50). '...'
+                if $format eq 'latex' && length($loc) > 50;
+              push @d, &{$escape_function}($loc);
+            }
+          }
           push @d, $cust_bill_pkg->details(%details_opt)
             if $cust_bill_pkg->recur == 0;
 
@@ -3929,14 +4006,23 @@ sub _items_cust_bill_pkg {
           my $prev = $cust_bill_pkg->previous_cust_bill_pkg;
           push @dates, $prev->sdate if $prev;
 
-          push @d, map &{$escape_function}($_),
-                       $cust_pkg->h_labels_short(@dates)
-                                                 #$cust_bill_pkg->edate,
-                                                 #$cust_bill_pkg->sdate)
-            unless $cust_pkg->part_pkg->hide_svc_detail
+          unless ( $cust_pkg->part_pkg->hide_svc_detail
                 || $cust_bill_pkg->itemdesc
                 || $cust_bill_pkg->hidden
-                || $is_summary && $type && $type eq 'U';
+                || $is_summary && $type && $type eq 'U' )
+          {
+            push @d, map &{$escape_function}($_),
+                         $cust_pkg->h_labels_short(@dates)
+                                                   #$cust_bill_pkg->edate,
+                                                   #$cust_bill_pkg->sdate)
+            ;
+            if ( $multilocation ) {
+              my $loc = $cust_pkg->location_label;
+              $loc = substr($desc, 0, 50). '...'
+                if $format eq 'latex' && length($loc) > 50;
+              push @d, &{$escape_function}($loc);
+            }
+          }
 
           push @d, $cust_bill_pkg->details(%details_opt)
             unless ($is_summary || $type && $type eq 'R');
@@ -4013,11 +4099,13 @@ sub _items_cust_bill_pkg {
 
   }
 
-  foreach ( $s, $r, $u ) {
-    if ( $_ ) {
+  foreach ( $s, $r, ($opt{skip_usage} ? () : $u ) ) {
+    if ( $_  ) {
       $_->{amount}      = sprintf( "%.2f", $_->{amount} ),
+      $_->{amount}      =~ s/^\-0\.00$/0.00/;
       $_->{unit_amount} = sprintf( "%.2f", $_->{unit_amount} ),
-      push @b, { %$_ };
+      push @b, { %$_ }
+        unless $_->{amount} == 0;
     }
   }
 
@@ -4181,7 +4269,7 @@ sub re_X {
   my $distinct = '';
   my $orderby = 'ORDER BY cust_bill._date';
 
-  my $extra_sql = ' WHERE '. FS::cust_bill->search_sql(\%param);
+  my $extra_sql = ' WHERE '. FS::cust_bill->search_sql_where(\%param);
 
   my $addl_from = 'LEFT JOIN cust_main USING ( custnum )';
      
@@ -4271,7 +4359,7 @@ sub credited_sql {
        WHERE cust_bill.invnum = cust_credit_bill.invnum   )";
 }
 
-=item search_sql HASHREF
+=item search_sql_where HASHREF
 
 Class method which returns an SQL WHERE fragment to search for parameters
 specified in HASHREF.  Valid parameters are
@@ -4314,10 +4402,10 @@ Note: validates all passed-in data; i.e. safe to use with unchecked CGI params.
 
 =cut
 
-sub search_sql {
+sub search_sql_where {
   my($class, $param) = @_;
   if ( $DEBUG ) {
-    warn "$me search_sql called with params: \n".
+    warn "$me search_sql_where called with params: \n".
          join("\n", map { "  $_: ". $param->{$_} } keys %$param ). "\n";
   }