add raw date to invoice templates, RT#21007
[freeside.git] / FS / FS / cust_bill.pm
index 41ff6aa..c9b03c0 100644 (file)
@@ -388,13 +388,29 @@ 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;
 }
 
+=item enable_previous
+
+Whether to show the 'Previous Charges' section when printing this invoice.
+The negation of the 'disable_previous_balance' config setting.
+
+=cut
+
+sub enable_previous {
+  my $self = shift;
+  my $agentnum = $self->cust_main->agentnum;
+  !$self->conf->exists('disable_previous_balance', $agentnum);
+}
+
 =item cust_bill_pkg
 
 Returns the line items (see L<FS::cust_bill_pkg>) for this invoice.
@@ -1314,14 +1330,16 @@ sub send {
     $balance_over = shift if scalar(@_) && $_[0] !~ /^\s*$/;
   }
 
+  my $cust_main = $self->cust_main;
+
   return 'N/A' unless ! $agentnums
-                   or grep { $_ == $self->cust_main->agentnum } @$agentnums;
+                   or grep { $_ == $cust_main->agentnum } @$agentnums;
 
   return ''
-    unless $self->cust_main->total_owed_date($self->_date) > $balance_over;
+    unless $cust_main->total_owed_date($self->_date) > $balance_over;
 
   $invoice_from ||= $self->_agent_invoice_from ||    #XXX should go away
-                    $conf->config('invoice_from', $self->cust_main->agentnum );
+                    $conf->config('invoice_from', $cust_main->agentnum );
 
   my %opt = (
     'template'     => $template,
@@ -1329,11 +1347,12 @@ sub send {
     'notice_name'  => ( $notice_name || 'Invoice' ),
   );
 
-  my @invoicing_list = $self->cust_main->invoicing_list;
+  my @invoicing_list = $cust_main->invoicing_list;
 
   #$self->email_invoice(\%opt)
   $self->email(\%opt)
-    if grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list or !@invoicing_list;
+    if ( grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list or !@invoicing_list )
+    && ! $self->invoice_noemail;
 
   #$self->print_invoice(\%opt)
   $self->print(\%opt)
@@ -1522,7 +1541,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,
+    );
   }
 }
 
@@ -2005,6 +2027,44 @@ sub print_csv {
       '0',                        # 29 | Other Taxes & Fees***         NUM*   9
     );
 
+  } 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, #_items_nontax?  no sections or anything
+                                     # with this format
+                  $self->_items_tax;
+
+    $csv->combine(
+      $cust_main->agentnum,
+      $cust_main->agent->agent,
+      $self->custnum,
+      $cust_main->first,
+      $cust_main->last,
+      $cust_main->company,
+      $cust_main->address1,
+      $cust_main->address2,
+      $cust_main->city,
+      $cust_main->state,
+      $cust_main->zip,
+
+      # invoice fields
+      time2str("%x", $self->_date),
+      $self->invnum,
+      $self->charged,
+      $totaldue,
+      $previous_balance,
+      $self->due_date2str("%x"),
+
+      @items,
+    );
+
   } else {
   
     $csv->combine(
@@ -2044,6 +2104,10 @@ sub print_csv {
 
     }
 
+  } elsif ( lc($opt{'format'}) eq 'oneline' ) {
+
+    #do nothing
+
   } else {
 
     foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
@@ -2662,6 +2726,7 @@ sub print_generic {
 
     #invoice info
     'invnum'          => $self->invnum,
+    '_date'           => $self->_date,
     'date'            => time2str($date_format, $self->_date),
     'today'           => time2str($date_format_long, $today),
     'terms'           => $self->terms,
@@ -2927,10 +2992,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;
@@ -2943,6 +3009,12 @@ sub print_generic {
   my $late_sections = [];
   my $extra_sections = [];
   my $extra_lines = ();
+
+  my $default_section = { 'description' => '',
+                          'subtotal'    => '', 
+                          'no_subtotal' => 1,
+                        };
+
   if ( $multisection ) {
     ($extra_sections, $extra_lines) =
       $self->_items_extra_usage_sections($escape_function_nonbsp, $format)
@@ -2974,8 +3046,7 @@ sub print_generic {
     }
   } else {# not multisection
     # make a default section
-    push @sections, { 'description' => '', 'subtotal' => '', 
-      'no_subtotal' => 1 };
+    push @sections, $default_section;
     # and calculate the finance charge total, since it won't get done otherwise.
     # XXX possibly other totals?
     # XXX possibly finance_pkgclass should not be used in this manner?
@@ -2993,10 +3064,11 @@ sub print_generic {
     }
   }
 
-  unless (    $conf->exists('disable_previous_balance')
-           || $conf->exists('previous_balance-summary_only')
-         )
-  {
+  # previous invoice balances in the Previous Charges section if there
+  # is one, otherwise in the main detail section
+  if ( $self->can('_items_previous') &&
+       $self->enable_previous &&
+       ! $conf->exists('previous_balance-summary_only') ) {
 
     warn "$me adding previous balances\n"
       if $DEBUG > 1;
@@ -3007,8 +3079,10 @@ sub print_generic {
         ext_description => [],
       };
       $detail->{'ref'} = $line_item->{'pkgnum'};
+      $detail->{'pkgpart'} = $line_item->{'pkgpart'};
       $detail->{'quantity'} = 1;
-      $detail->{'section'} = $previous_section;
+      $detail->{'section'} = $multisection ? $previous_section
+                                           : $default_section;
       $detail->{'description'} = &$escape_function($line_item->{'description'});
       if ( exists $line_item->{'ext_description'} ) {
         @{$detail->{'ext_description'}} = map {
@@ -3026,8 +3100,8 @@ sub print_generic {
     }
 
   }
-  
-  if ( @pr_cust_bill && !$conf->exists('disable_previous_balance') ) {
+
+  if ( @pr_cust_bill && $self->enable_previous ) {
     push @buf, ['','-----------'];
     push @buf, [ $self->mt('Total Previous Balance'),
                  $money_char. sprintf("%10.2f", $pr_total) ];
@@ -3104,6 +3178,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'});
@@ -3119,6 +3194,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'},
@@ -3143,7 +3219,9 @@ sub print_generic {
   $invoice_data{current_less_finance} =
     sprintf('%.2f', $self->charged - $invoice_data{finance_amount} );
 
-  if ( $multisection && !$conf->exists('disable_previous_balance')
+  # create a major section for previous balance if we have major sections,
+  # or if previous_section is in summary form
+  if ( ( $multisection && $self->enable_previous )
     || $conf->exists('previous_balance-summary_only') )
   {
     unshift @sections, $previous_section if $pr_total;
@@ -3207,25 +3285,26 @@ sub print_generic {
 
   push @buf,['','-----------'];
   push @buf,[$self->mt( 
-              $conf->exists('disable_previous_balance') 
+              (!$self->enable_previous)
                ? 'Total Charges'
                : 'Total New Charges'
              ),
              $money_char. sprintf("%10.2f",$self->charged) ];
   push @buf,['',''];
 
+  # 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 +
-                   ( $conf->exists('disable_previous_balance') ||
-                     $conf->exists('previous_balance-exclude_from_total')
-                     ? 0
-                     : $pr_total
-                   );
+    my $amount = $self->charged;
+    if ( $self->enable_previous and !$conf->exists('previous_balance-exclude_from_total') ) {
+      $amount += $pr_total;
+    }
+
     $total->{'total_item'} = &$embolden_function($self->mt($item));
     $total->{'total_amount'} =
       &$embolden_function( $other_money_char.  sprintf( '%.2f', $amount ) );
@@ -3247,8 +3326,13 @@ sub print_generic {
               ];
     push @buf,['',''];
   }
-  
-  unless ( $conf->exists('disable_previous_balance') ) {
+
+  # if we're showing previous invoices, also show previous
+  # credits and payments 
+  if ( $self->enable_previous 
+        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
@@ -3324,10 +3408,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} ) {
@@ -3846,17 +3931,20 @@ sub _items_sections {
         if ( $display->post_total && !$summarypage ) {
           if (! $type || $type eq 'S') {
             $late_subtotal{$section} += $cust_bill_pkg->setup
-              if $cust_bill_pkg->setup != 0;
+              if $cust_bill_pkg->setup != 0
+              || $cust_bill_pkg->setup_show_zero;
           }
 
           if (! $type) {
             $late_subtotal{$section} += $cust_bill_pkg->recur
-              if $cust_bill_pkg->recur != 0;
+              if $cust_bill_pkg->recur != 0
+              || $cust_bill_pkg->recur_show_zero;
           }
 
           if ($type && $type eq 'R') {
             $late_subtotal{$section} += $cust_bill_pkg->recur - $usage
-              if $cust_bill_pkg->recur != 0;
+              if $cust_bill_pkg->recur != 0
+              || $cust_bill_pkg->recur_show_zero;
           }
           
           if ($type && $type eq 'U') {
@@ -3870,17 +3958,20 @@ sub _items_sections {
 
           if (! $type || $type eq 'S') {
             $subtotal{$section} += $cust_bill_pkg->setup
-              if $cust_bill_pkg->setup != 0;
+              if $cust_bill_pkg->setup != 0
+              || $cust_bill_pkg->setup_show_zero;
           }
 
           if (! $type) {
             $subtotal{$section} += $cust_bill_pkg->recur
-              if $cust_bill_pkg->recur != 0;
+              if $cust_bill_pkg->recur != 0
+              || $cust_bill_pkg->recur_show_zero;
           }
 
           if ($type && $type eq 'R') {
             $subtotal{$section} += $cust_bill_pkg->recur - $usage
-              if $cust_bill_pkg->recur != 0;
+              if $cust_bill_pkg->recur != 0
+              || $cust_bill_pkg->recur_show_zero;
           }
           
           if ($type && $type eq 'U') {
@@ -4851,6 +4942,8 @@ sub _items_cust_bill_pkg {
 
   my $maxlength = $conf->config('cust_bill-latex_lineitem_maxlength') || 50;
 
+  my $cust_main = $self->cust_main;#for per-agent cust_bill-line_item-ate_style
+
   my @b = ();
   my ($s, $r, $u) = ( undef, undef, undef );
   foreach my $cust_bill_pkg ( @$cust_bill_pkgs )
@@ -4871,6 +4964,8 @@ sub _items_cust_bill_pkg {
       }
     }
 
+    my @cust_bill_pkg_display = $cust_bill_pkg->cust_bill_pkg_display;
+
     warn "$me _items_cust_bill_pkg considering cust_bill_pkg ".
          $cust_bill_pkg->billpkgnum. ", pkgnum ". $cust_bill_pkg->pkgnum. "\n"
       if $DEBUG > 1;
@@ -4881,7 +4976,7 @@ sub _items_cust_bill_pkg {
                                }
                           #grep { !$_->summary || !$summary_page } # bunk!
                           grep { !$_->summary || $multisection }
-                          $cust_bill_pkg->cust_bill_pkg_display
+                          @cust_bill_pkg_display
                         )
     {
 
@@ -4908,9 +5003,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
@@ -4929,13 +5029,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;
@@ -4957,13 +5060,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 || ''),
             };
           };
 
@@ -4986,14 +5090,43 @@ sub _items_cust_bill_pkg {
           my $description = ($is_summary && $type && $type eq 'U')
                             ? "Usage charges" : $desc;
 
-          $description .= " (" . time2str($date_format, $cust_bill_pkg->sdate).
-                          " - ". time2str($date_format, $cust_bill_pkg->edate).
-                          ")"
-            unless $conf->exists('disable_line_item_date_ranges')
-                || $cust_pkg->part_pkg->option('disable_line_item_date_ranges',1);
+          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')
+              || $part_pkg->option('disable_line_item_date_ranges',1)
+              || ! $cust_bill_pkg->sdate
+              || ! $cust_bill_pkg->edate
+          ) {
+            my $time_period;
+            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);
+            } 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);
+            } else {
+              $time_period =      time2str($date_format, $cust_bill_pkg->sdate).
+                           " - ". time2str($date_format, $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
@@ -5011,11 +5144,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($self->_date, undef, '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;
@@ -5086,7 +5219,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,
@@ -5094,6 +5227,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;
             }
@@ -5110,7 +5244,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,
@@ -5210,11 +5344,16 @@ sub _items_payments {
 
     #something more elaborate if $_->amount ne ->cust_pay->paid ?
 
+    my $desc = $self->mt('Payment received').' '.
+               time2str($date_format,$_->cust_pay->_date );
+    $desc   .= $self->mt(' via ' . $_->cust_pay->payby_payinfo_pretty)
+      if ( $self->conf->exists('invoice_payment_details') );
     push @b, {
-      'description' => $self->mt('Payment received').' '.
-                       time2str($date_format,$_->cust_pay->_date ),
+      'description' => $desc,
       'amount'      => sprintf("%.2f", $_->amount )
     };
+
   }
 
   @b;
@@ -5251,6 +5390,9 @@ sub _items_discounts_avail {
                  join(', ', map { "#$_" } $plan->pkgnums)
       if $list_pkgnums;
 
+    # discounts for non-integer months don't work anyway
+    $months = sprintf("%d", $months);
+
     +{
       description => $self->mt('Save [_1]% by paying for [_2] months',
                                 $percent, $months),
@@ -5548,11 +5690,25 @@ sub search_sql_where {
     push @search, "cust_main.agentnum = $1";
   }
 
-  #agentnum
+  #refnum
+  if ( $param->{'refnum'} =~ /^(\d+)$/ ) {
+    push @search, "cust_main.refnum = $1";
+  }
+
+  #custnum
   if ( $param->{'custnum'} =~ /^(\d+)$/ ) {
     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}};