make date_format a localized config option, #27276
[freeside.git] / FS / FS / cust_bill.pm
index 96dce2c..32e2a09 100644 (file)
@@ -2,7 +2,7 @@ package FS::cust_bill;
 
 use strict;
 use vars qw( @ISA $DEBUG $me 
-             $money_char $date_format $rdate_format $date_format_long );
+             $money_char );
              # but NOT $conf
 use vars qw( $invoice_lines @buf ); #yuck
 use Fcntl qw(:flock); #for spool_csv
@@ -55,9 +55,6 @@ $me = '[FS::cust_bill]';
 FS::UID->install_callback( sub { 
   my $conf = new FS::Conf; #global
   $money_char       = $conf->config('money_char')       || '$';  
-  $date_format      = $conf->config('date_format')      || '%x'; #/YY
-  $rdate_format     = $conf->config('date_format')      || '%m/%d/%Y';  #/YYYY
-  $date_format_long = $conf->config('date_format_long') || '%b %o, %Y';
 } );
 
 =head1 NAME
@@ -377,6 +374,25 @@ sub display_invnum {
   }
 }
 
+=item previous_bill
+
+Returns the customer's last invoice before this one.
+
+=cut
+
+sub previous_bill {
+  my $self = shift;
+  if ( !$self->get('previous_bill') ) {
+    $self->set('previous_bill', qsearchs({
+          'table'     => 'cust_bill',
+          'hashref'   => { 'custnum'  => $self->custnum,
+                           '_date'    => { op=>'<', value=>$self->_date } },
+          'order_by'  => 'ORDER BY _date DESC LIMIT 1',
+    }) );
+  }
+  $self->get('previous_bill');
+}
+
 =item previous
 
 Returns a list consisting of the total previous balance for this customer, 
@@ -388,8 +404,11 @@ sub previous {
   my $self = shift;
   my $total = 0;
   my @cust_bill = sort { $a->_date <=> $b->_date }
-    grep { $_->owed != 0 && $_->_date < $self->_date }
-      qsearch( 'cust_bill', { 'custnum' => $self->custnum } ) 
+    grep { $_->owed != 0 }
+      qsearch( 'cust_bill', { 'custnum'  => $self->custnum,
+                              #'_date'   => { op=>'<', value=>$self->_date },
+                              'invnum'   => { op=>'<', value=>$self->invnum },
+                            } ) 
   ;
   foreach ( @cust_bill ) { $total += $_->owed; }
   $total, @cust_bill;
@@ -1538,7 +1557,10 @@ sub print {
     $self->batch_invoice(\%opt);
   }
   else {
-    do_print $self->lpr_data(\%opt);
+    do_print(
+      $self->lpr_data(\%opt),
+      'agentnum' => $self->cust_main->agentnum,
+    );
   }
 }
 
@@ -1720,6 +1742,7 @@ sub send_csv {
   my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/cust_bill";
   mkdir $spooldir, 0700 unless -d $spooldir;
 
+  # don't localize dates here, they're a defined format
   my $tracctnum = $self->invnum. time2str('-%Y%m%d%H%M%S', time);
   my $file = "$spooldir/$tracctnum.csv";
   
@@ -1973,13 +1996,13 @@ sub print_csv {
     my $taxtotal = 0;
     $taxtotal += $_->{'amount'} foreach $self->_items_tax;
 
-    my $duedate = $self->due_date2str('%m/%d/%Y'); #date_format?
+    my $duedate = $self->due_date2str('%m/%d/%Y'); # hardcoded, NOT date_format
 
     my( $previous_balance, @unused ) = $self->previous; #previous balance
 
     my $pmt_cr_applied = 0;
     $pmt_cr_applied += $_->{'amount'}
-      foreach ( $self->_items_payments, $self->_items_credits ) ;
+      foreach ( $self->_items_payments(%opt), $self->_items_credits(%opt) ) ;
 
     my $totaldue = sprintf('%.2f', $self->owed + $previous_balance);
 
@@ -2024,12 +2047,16 @@ sub print_csv {
   } elsif ( lc($opt{'format'}) eq 'oneline' ) { #name?
   
     my ($previous_balance) = $self->previous; 
+    $previous_balance = sprintf('%.2f', $previous_balance);
     my $totaldue = sprintf('%.2f', $self->owed + $previous_balance);
     my @items = map {
-      ($_->{pkgnum} || ''),
-      $_->{description},
-      $_->{amount}
-    } $self->_items_pkg;
+                      $_->{pkgnum},
+                      $_->{description},
+                      $_->{amount}
+                    }
+                  $self->_items_pkg, #_items_nontax?  no sections or anything
+                                     # with this format
+                  $self->_items_tax;
 
     $csv->combine(
       $cust_main->agentnum,
@@ -2037,6 +2064,7 @@ sub print_csv {
       $self->custnum,
       $cust_main->first,
       $cust_main->last,
+      $cust_main->company,
       $cust_main->address1,
       $cust_main->address2,
       $cust_main->city,
@@ -2048,6 +2076,8 @@ sub print_csv {
       $self->invnum,
       $self->charged,
       $totaldue,
+      $previous_balance,
+      $self->due_date2str("%x"),
 
       @items,
     );
@@ -2114,7 +2144,7 @@ sub print_csv {
             ? time2str("%x", $cust_bill_pkg->sdate)
             : '' ),
           ($cust_bill_pkg->edate 
-            ?time2str("%x", $cust_bill_pkg->edate)
+            ? time2str("%x", $cust_bill_pkg->edate)
             : '' ),
         );
   
@@ -2620,14 +2650,6 @@ sub print_generic {
   my $escape_function_nonbsp = ($format eq 'html')
                                  ? \&_html_escape : $escape_function;
 
-  my %date_formats = ( 'latex'    => $date_format_long,
-                       'html'     => $date_format_long,
-                       'template' => '%s',
-                     );
-  $date_formats{'html'} =~ s/ /&nbsp;/g;
-
-  my $date_format = $date_formats{$format};
-
   my %embolden_functions = ( 'latex'    => sub { return '\textbf{'. shift(). '}'
                                                },
                              'html'     => sub { return '<b>'. shift(). '</b>'
@@ -2713,13 +2735,14 @@ sub print_generic {
 
     #invoice info
     'invnum'          => $self->invnum,
-    'date'            => time2str($date_format, $self->_date),
-    'today'           => time2str($date_format_long, $today),
+    '_date'           => $self->_date,
+    'date'            => $self->time2str_local('long', $self->_date, $format),
+    'today'           => $self->time2str_local('long', $today, $format),
     'terms'           => $self->terms,
     'template'        => $template, #params{'template'},
     'notice_name'     => ($params{'notice_name'} || 'Invoice'),#escape_function?
     'current_charges' => sprintf("%.2f", $self->charged),
-    'duedate'         => $self->due_date2str($rdate_format), #date_format?
+    'duedate'         => $self->due_date2str('rdate'),
 
     #customer info
     'custnum'         => $cust_main->display_custnum,
@@ -2757,16 +2780,10 @@ sub print_generic {
 
   );
  
-  #localization
-  my $lh = FS::L10N->get_handle( $params{'locale'} || $cust_main->locale );
+  #localization (see FS::cust_main_Mixin)
   $invoice_data{'emt'} = sub { &$escape_function($self->mt(@_)) };
-  my %info = FS::Locales->locale_info($cust_main->locale || 'en_US');
-  # eval to avoid death for unimplemented languages
-  my $dh = eval { Date::Language->new($info{'name'}) } ||
-           Date::Language->new(); # fall back to English
   # prototype here to silence warnings
-  $invoice_data{'time2str'} = sub ($;$$) { $dh->time2str(@_) };
-  # eventually use this date handle everywhere in here, too
+  $invoice_data{'time2str'} = sub ($;$$) { $self->time2str_local(@_, $format) };
 
   my $min_sdate = 999999999999;
   my $max_edate = 0;
@@ -2779,8 +2796,10 @@ sub print_generic {
   }
 
   $invoice_data{'bill_period'} = '';
-  $invoice_data{'bill_period'} = time2str('%e %h', $min_sdate) 
-    . " to " . time2str('%e %h', $max_edate)
+  $invoice_data{'bill_period'} =
+      $self->time2str_local('%e %h', $min_sdate, $format) 
+      . " to " .
+      $self->time2str_local('%e %h', $max_edate, $format)
     if ($max_edate != 0 && $min_sdate != 999999999999);
 
   $invoice_data{finance_section} = '';
@@ -2860,10 +2879,9 @@ sub print_generic {
   # info from customer's last invoice before this one, for some 
   # summary formats
   $invoice_data{'last_bill'} = {};
-  my $last_bill = $pr_cust_bill[-1];
-  if ( $last_bill ) {
+  if ( $self->previous_bill ) {
     $invoice_data{'last_bill'} = {
-      '_date'     => $last_bill->_date, #unformatted
+      '_date'     => $self->previous_bill->_date, #unformatted
       # all we need for now
     };
   }
@@ -2969,6 +2987,7 @@ sub print_generic {
   my $taxtotal = 0;
   my $tax_section = { 'description' => $self->mt('Taxes, Surcharges, and Fees'),
                       'subtotal'    => $taxtotal,   # adjusted below
+                      'tax_section' => 1,
                     };
   my $tax_weight = _pkg_category($tax_section->{description})
                         ? _pkg_category($tax_section->{description})->weight
@@ -2978,10 +2997,11 @@ sub print_generic {
 
 
   my $adjusttotal = 0;
-  my $adjust_section = { 'description' => 
-    $self->mt('Credits, Payments, and Adjustments'),
-                         'subtotal'    => 0,   # adjusted below
-                       };
+  my $adjust_section = {
+    'description'    => $self->mt('Credits, Payments, and Adjustments'),
+    'adjust_section' => 1,
+    'subtotal'       => 0,   # adjusted below
+  };
   my $adjust_weight = _pkg_category($adjust_section->{description})
                         ? _pkg_category($adjust_section->{description})->weight
                         : 0;
@@ -3064,6 +3084,7 @@ sub print_generic {
         ext_description => [],
       };
       $detail->{'ref'} = $line_item->{'pkgnum'};
+      $detail->{'pkgpart'} = $line_item->{'pkgpart'};
       $detail->{'quantity'} = 1;
       $detail->{'section'} = $multisection ? $previous_section
                                            : $default_section;
@@ -3162,6 +3183,7 @@ sub print_generic {
         ext_description => [],
       };
       $detail->{'ref'} = $line_item->{'pkgnum'};
+      $detail->{'pkgpart'} = $line_item->{'pkgpart'};
       $detail->{'quantity'} = $line_item->{'quantity'};
       $detail->{'section'} = $section;
       $detail->{'description'} = &$escape_function($line_item->{'description'});
@@ -3177,6 +3199,7 @@ sub print_generic {
       $detail->{'sdate'} = $line_item->{'sdate'};
       $detail->{'edate'} = $line_item->{'edate'};
       $detail->{'seconds'} = $line_item->{'seconds'};
+      $detail->{'svc_label'} = $line_item->{'svc_label'};
   
       push @detail_items, $detail;
       push @buf, ( [ $detail->{'description'},
@@ -3298,7 +3321,7 @@ sub print_generic {
         $adjust_section->{'pretotal'} = $self->mt('New charges total').' '.
           $other_money_char.  sprintf('%.2f', $self->charged );
       } 
-    }else{
+    } else {
       push @total_items, $total;
     }
     push @buf,['','-----------'];
@@ -3319,7 +3342,9 @@ sub print_generic {
   
     # credits
     my $credittotal = 0;
-    foreach my $credit ( $self->_items_credits('trim_len'=>60) ) {
+    foreach my $credit (
+      $self->_items_credits( 'template' => $template, 'trim_len' => 60)
+    ) {
 
       my $total;
       $total->{'total_item'} = &$escape_function($credit->{'description'});
@@ -3345,13 +3370,17 @@ sub print_generic {
     $invoice_data{'credittotal'} = sprintf('%.2f', $credittotal);
 
     #credits (again)
-    foreach my $credit ( $self->_items_credits('trim_len'=>32) ) {
+    foreach my $credit (
+      $self->_items_credits( 'template' => $template, 'trim_len' => 32)
+    ) {
       push @buf, [ $credit->{'description'}, $money_char.$credit->{'amount'} ];
     }
 
     # payments
     my $paymenttotal = 0;
-    foreach my $payment ( $self->_items_payments ) {
+    foreach my $payment (
+      $self->_items_payments( 'template' => $template )
+    ) {
       my $total = {};
       $total->{'total_item'} = &$escape_function($payment->{'description'});
       $paymenttotal += $payment->{'amount'};
@@ -3390,10 +3419,11 @@ sub print_generic {
       $total->{'total_item'} = &$embolden_function($self->balance_due_msg);
       $total->{'total_amount'} =
         &$embolden_function(
-          $other_money_char. sprintf('%.2f', $summarypage 
-                                               ? $self->charged +
-                                                 $self->billing_balance
-                                               : $self->owed + $pr_total
+          $other_money_char. sprintf('%.2f', #why? $summarypage 
+                                             #  ? $self->charged +
+                                             #    $self->billing_balance
+                                             #  :
+                                                 $self->owed + $pr_total
                                     )
         );
       if ( $multisection && !$adjust_section->{sort_weight} ) {
@@ -3464,6 +3494,10 @@ sub print_generic {
     } } @discounts_avail;
   }
 
+  # debugging hook: call this with 'diag' => 1 to just get a hash of
+  # the invoice variables
+  return \%invoice_data if ( $params{'diag'} );
+
   # All sections and items are built; now fill in templates.
   my @includelist = ();
   push @includelist, 'summary' if $summarypage;
@@ -3780,7 +3814,7 @@ sub due_date {
 
 sub due_date2str {
   my $self = shift;
-  $self->due_date ? time2str(shift, $self->due_date) : '';
+  $self->due_date ? $self->time2str_local(shift, $self->due_date) : '';
 }
 
 sub balance_due_msg {
@@ -3789,7 +3823,7 @@ sub balance_due_msg {
   return $msg unless $self->terms;
   if ( $self->due_date ) {
     $msg .= ' - ' . $self->mt('Please pay by'). ' '.
-      $self->due_date2str($date_format);
+      $self->due_date2str('short');
   } elsif ( $self->terms ) {
     $msg .= ' - '. $self->terms;
   }
@@ -3802,7 +3836,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($rdate_format, $self->_date + ($1*86400) );
+    $duedate = $self->time2str_local('rdate', $self->_date + ($1*86400) );
   }
   $duedate;
 }
@@ -3832,7 +3866,7 @@ Returns a string with the date, for example: "3/20/2008"
 
 sub _date_pretty {
   my $self = shift;
-  time2str($date_format, $self->_date);
+  $self->time2str_local('short', $self->_date);
 }
 
 =item _items_sections LATE SUMMARYPAGE ESCAPE EXTRA_SECTIONS FORMAT
@@ -4774,8 +4808,8 @@ sub _items_previous {
   my @b = ();
   foreach ( @pr_cust_bill ) {
     my $date = $conf->exists('invoice_show_prior_due_date')
-               ? 'due '. $_->due_date2str($date_format)
-               : time2str($date_format, $_->_date);
+               ? 'due '. $_->due_date2str('short')
+               : $self->time2str_local('short', $_->_date);
     push @b, {
       'description' => $self->mt('Previous Balance, Invoice #'). $_->invnum. " ($date)",
       #'pkgpart'     => 'N/A',
@@ -4984,9 +5018,14 @@ sub _items_cust_bill_pkg {
  
         my $cust_pkg = $cust_bill_pkg->cust_pkg;
 
+        # which pkgpart to show for display purposes?
+        my $pkgpart = $cust_bill_pkg->pkgpart_override || $cust_pkg->pkgpart;
+
         # start/end dates for invoice formats that do nonstandard 
         # things with them
-        my %item_dates = map { $_ => $cust_bill_pkg->$_ } ('sdate', 'edate');
+        my %item_dates = ();
+        %item_dates = map { $_ => $cust_bill_pkg->$_ } ('sdate', 'edate')
+          unless $cust_pkg->part_pkg->option('disable_line_item_date_ranges',1);
 
         if (    (!$type || $type eq 'S')
              && (    $cust_bill_pkg->setup != 0
@@ -5005,13 +5044,16 @@ sub _items_cust_bill_pkg {
             || $cust_bill_pkg->recur_show_zero;
 
           my @d = ();
+          my $svc_label;
           unless ( $cust_pkg->part_pkg->hide_svc_detail
                 || $cust_bill_pkg->hidden )
           {
 
-            push @d, map &{$escape_function}($_),
-                         $cust_pkg->h_labels_short($self->_date, undef, 'I')
+            my @svc_labels = map &{$escape_function}($_),
+                        $cust_pkg->h_labels_short($self->_date, undef, 'I');
+            push @d, @svc_labels
               unless $cust_bill_pkg->pkgpart_override; #don't redisplay services
+            $svc_label = $svc_labels[0];
 
             if ( $multilocation ) {
               my $loc = $cust_pkg->location_label;
@@ -5033,13 +5075,14 @@ sub _items_cust_bill_pkg {
             $s = {
               _is_setup       => 1,
               description     => $description,
-              #pkgpart         => $part_pkg->pkgpart,
+              pkgpart         => $pkgpart,
               pkgnum          => $cust_bill_pkg->pkgnum,
               amount          => $cust_bill_pkg->setup,
               setup_show_zero => $cust_bill_pkg->setup_show_zero,
               unit_amount     => $cust_bill_pkg->unitsetup,
               quantity        => $cust_bill_pkg->quantity,
               ext_description => \@d,
+              svc_label       => ($svc_label || ''),
             };
           };
 
@@ -5062,33 +5105,45 @@ sub _items_cust_bill_pkg {
           my $description = ($is_summary && $type && $type eq 'U')
                             ? "Usage charges" : $desc;
 
+          my $part_pkg = $cust_pkg->part_pkg;
+
           #pry be a bit more efficient to look some of this conf stuff up
           # outside the loop
           unless (
             $conf->exists('disable_line_item_date_ranges')
-              || $cust_pkg->part_pkg->option('disable_line_item_date_ranges',1)
+              || $part_pkg->option('disable_line_item_date_ranges',1)
+              || ! $cust_bill_pkg->sdate
+              || ! $cust_bill_pkg->edate
           ) {
             my $time_period;
-            my $date_style = $conf->config( 'cust_bill-line_item-date_style',
+            my $date_style = '';                                               
+            $date_style = $conf->config( 'cust_bill-line_item-date_style-non_monthly',
+                                         $cust_main->agentnum                  
+                                       )                                       
+              if $part_pkg && $part_pkg->freq !~ /^1m?$/;                      
+            $date_style ||= $conf->config( 'cust_bill-line_item-date_style',   
                                             $cust_main->agentnum
                                           );
             if ( defined($date_style) && $date_style eq 'month_of' ) {
-              $time_period = time2str('The month of %B', $cust_bill_pkg->sdate);
+              $time_period = $self->mt('The month of [_1]',
+                               $self->time2str_local('%B', $cust_bill_pkg->sdate)
+                             );
             } elsif ( defined($date_style) && $date_style eq 'X_month' ) {
               my $desc = $conf->config( 'cust_bill-line_item-date_description',
                                          $cust_main->agentnum
                                       );
               $desc .= ' ' unless $desc =~ /\s$/;
-              $time_period = $desc. time2str('%B', $cust_bill_pkg->sdate);
+              $time_period = $desc. $self->time2str_local('%B', $cust_bill_pkg->sdate);
             } else {
-              $time_period =      time2str($date_format, $cust_bill_pkg->sdate).
-                           " - ". time2str($date_format, $cust_bill_pkg->edate);
+              $time_period =      $self->time2str_local('short', $cust_bill_pkg->sdate).
+                           " - ". $self->time2str_local('short', $cust_bill_pkg->edate);
             }
             $description .= " ($time_period)";
           }
 
           my @d = ();
           my @seconds = (); # for display of usage info
+          my $svc_label = '';
 
           #at least until cust_bill_pkg has "past" ranges in addition to
           #the "future" sdate/edate ones... see #3032
@@ -5106,11 +5161,11 @@ sub _items_cust_bill_pkg {
             warn "$me _items_cust_bill_pkg adding service details\n"
               if $DEBUG > 1;
 
-            push @d, map &{$escape_function}($_),
-                         $cust_pkg->h_labels_short(@dates, 'I')
-                                                   #$cust_bill_pkg->edate,
-                                                   #$cust_bill_pkg->sdate)
+            my @svc_labels = map &{$escape_function}($_),
+                        $cust_pkg->h_labels_short(@dates, 'I');
+            push @d, @svc_labels
               unless $cust_bill_pkg->pkgpart_override; #don't redisplay services
+            $svc_label = $svc_labels[0];
 
             warn "$me _items_cust_bill_pkg done adding service details\n"
               if $DEBUG > 1;
@@ -5181,7 +5236,7 @@ sub _items_cust_bill_pkg {
             } else {
               $r = {
                 description     => $description,
-                #pkgpart         => $part_pkg->pkgpart,
+                pkgpart         => $pkgpart,
                 pkgnum          => $cust_bill_pkg->pkgnum,
                 amount          => $amount,
                 recur_show_zero => $cust_bill_pkg->recur_show_zero,
@@ -5189,6 +5244,7 @@ sub _items_cust_bill_pkg {
                 quantity        => $cust_bill_pkg->quantity,
                 %item_dates,
                 ext_description => \@d,
+                svc_label       => ($svc_label || ''),
               };
               $r->{'seconds'} = \@seconds if grep {defined $_} @seconds;
             }
@@ -5205,7 +5261,7 @@ sub _items_cust_bill_pkg {
             } else {
               $u = {
                 description     => $description,
-                #pkgpart         => $part_pkg->pkgpart,
+                pkgpart         => $pkgpart,
                 pkgnum          => $cust_bill_pkg->pkgnum,
                 amount          => $amount,
                 recur_show_zero => $cust_bill_pkg->recur_show_zero,
@@ -5233,8 +5289,8 @@ sub _items_cust_bill_pkg {
         if ( $cust_bill_pkg->recur != 0 ) {
           push @b, {
             'description' => "$desc (".
-                             time2str($date_format, $cust_bill_pkg->sdate). ' - '.
-                             time2str($date_format, $cust_bill_pkg->edate). ')',
+                             $self->time2str_local('short', $cust_bill_pkg->sdate). ' - '.
+                             $self->time2str_local('short', $cust_bill_pkg->edate). ')',
             'amount'      => sprintf("%.2f", $cust_bill_pkg->recur),
           };
         }
@@ -5274,12 +5330,33 @@ sub _items_credits {
 
   my @b;
   #credits
-  foreach ( $self->cust_credited ) {
+  my @objects;
+  if ( $self->conf->exists('previous_balance-payments_since') ) {
+    if ( $opt{'template'} eq 'statement' ) {
+      # then the current bill is a "statement" (i.e. an invoice sent as
+      # a payment receipt)
+      # and in that case we want to see payments on or after THIS invoice
+      @objects = qsearch('cust_credit', {
+          'custnum' => $self->custnum,
+          '_date'   => {op => '>=', value => $self->_date},
+      });
+    } else {
+      my $date = 0;
+      $date = $self->previous_bill->_date if $self->previous_bill;
+      @objects = qsearch('cust_credit', {
+          'custnum' => $self->custnum,
+          '_date'   => {op => '>=', value => $date},
+      });
+    }
+  } else {
+    @objects = $self->cust_credited;
+  }
 
-    #something more elaborate if $_->amount ne $_->cust_credit->credited ?
+  foreach my $obj ( @objects ) {
+    my $cust_credit = $obj->isa('FS::cust_credit') ? $obj : $obj->cust_credit;
 
-    my $reason = substr($_->cust_credit->reason, 0, $trim_len);
-    $reason .= '...' if length($reason) < length($_->cust_credit->reason);
+    my $reason = substr($cust_credit->reason, 0, $trim_len);
+    $reason .= '...' if length($reason) < length($cust_credit->reason);
     $reason = " ($reason) " if $reason;
 
     push @b, {
@@ -5287,8 +5364,8 @@ sub _items_credits {
       #                 " (". time2str("%x",$_->cust_credit->_date) .")".
       #                 $reason,
       'description' => $self->mt('Credit applied').' '.
-                       time2str($date_format,$_->cust_credit->_date). $reason,
-      'amount'      => sprintf("%.2f",$_->amount),
+                       $self->time2str_local('short', $obj->_date). $reason,
+      'amount'      => sprintf("%.2f",$obj->amount),
     };
   }
 
@@ -5298,17 +5375,47 @@ sub _items_credits {
 
 sub _items_payments {
   my $self = shift;
+  my %opt = @_;
 
   my @b;
-  #get & print payments
-  foreach ( $self->cust_bill_pay ) {
+  my $detailed = $self->conf->exists('invoice_payment_details');
+  my @objects;
+  if ( $self->conf->exists('previous_balance-payments_since') ) {
+    # then show payments dated on/after the previous bill...
+    if ( $opt{'template'} eq 'statement' ) {
+      # then the current bill is a "statement" (i.e. an invoice sent as
+      # a payment receipt)
+      # and in that case we want to see payments on or after THIS invoice
+      @objects = qsearch('cust_pay', {
+          'custnum' => $self->custnum,
+          '_date'   => {op => '>=', value => $self->_date},
+      });
+    } else {
+      # the normal case: payments on or after the previous invoice
+      my $date = 0;
+      $date = $self->previous_bill->_date if $self->previous_bill;
+      @objects = qsearch('cust_pay', {
+        'custnum' => $self->custnum,
+        '_date'   => {op => '>=', value => $date},
+      });
+      # and before the current bill...
+      @objects = grep { $_->_date < $self->_date } @objects;
+    }
+  } else {
+    @objects = $self->cust_bill_pay;
+  }
 
-    #something more elaborate if $_->amount ne ->cust_pay->paid ?
+  foreach my $obj (@objects) {
+    my $cust_pay = $obj->isa('FS::cust_pay') ? $obj : $obj->cust_pay;
+    my $desc = $self->mt('Payment received').' '.
+               $self->time2str_local('short', $cust_pay->_date );
+    $desc .= $self->mt(' via ') .
+             $cust_pay->payby_payinfo_pretty( $self->cust_main->locale )
+      if $detailed;
 
     push @b, {
-      'description' => $self->mt('Payment received').' '.
-                       time2str($date_format,$_->cust_pay->_date ),
-      'amount'      => sprintf("%.2f", $_->amount )
+      'description' => $desc,
+      'amount'      => sprintf("%.2f", $obj->amount )
     };
   }
 
@@ -5656,6 +5763,15 @@ sub search_sql_where {
     push @search, "cust_bill.custnum = $1";
   }
 
+  #customer classnum
+  if ( $param->{'cust_classnum'} ) {
+    my $classnums = $param->{'cust_classnum'};
+    $classnums = [ $classnums ] if !ref($classnums);
+    $classnums = [ grep /^\d+$/, @$classnums ];
+    push @search, 'cust_main.classnum in ('.join(',',@$classnums).')'
+      if @$classnums;
+  }
+
   #_date
   if ( $param->{_date} ) {
     my($beginning, $ending) = @{$param->{_date}};