show credit balance on invoices, #11564
[freeside.git] / FS / FS / cust_bill.pm
index af388d3..ba308be 100644 (file)
@@ -989,6 +989,19 @@ sub generate_email {
       'Filename'   => 'logo.png',
       'Content-ID' => "<$content_id>",
     ;
+   
+    my $barcode;
+    if($conf->exists('invoice-barcode')){
+       my $barcode_content_id = join('.', rand()*(2**32), $$, time). "\@$from";
+       $barcode = build MIME::Entity
+         'Type'       => 'image/png',
+         'Encoding'   => 'base64',
+         'Data'       => $self->invoice_barcode(0),
+         'Filename'   => 'barcode.png',
+         'Content-ID' => "<$barcode_content_id>",
+       ;
+       $opt{'barcode_cid'} = $barcode_content_id;
+    }
 
     $alternative->attach(
       'Type'        => 'text/html',
@@ -1062,7 +1075,12 @@ sub generate_email {
       #   image/png
 
       $return{'content-type'} = 'multipart/related';
-      $return{'mimeparts'} = [ $alternative, $image, @otherparts ];
+      if($conf->exists('invoice-barcode')){
+         $return{'mimeparts'} = [ $alternative, $image, $barcode, @otherparts ];
+      }
+      else {
+         $return{'mimeparts'} = [ $alternative, $image, @otherparts ];
+      }
       $return{'type'} = 'multipart/alternative'; #Content-Type of first part...
       #$return{'disposition'} = 'inline';
 
@@ -2155,19 +2173,7 @@ sub print_latex {
   $params{'logo_file'} = $lh->filename;
 
   if($conf->exists('invoice-barcode')){
-      my $gdbar = new GD::Barcode('Code39',$self->invnum);
-      die "can't create barcode: " . $GD::Barcode::errStr unless $gdbar;
-      my $gd = $gdbar->plot(Height => 20);
-      my $bh = new File::Temp( TEMPLATE => 'barcode.'. $self->invnum. '.XXXXXXXX',
-                           DIR      => $dir,
-                           SUFFIX   => '.png',
-                           UNLINK   => 0,
-                         ) or die "can't open temp file: $!\n";
-      print $bh $gd->png or die "cannot write barcode to file: $!\n";
-
-      my $png_file = $bh->filename;
-      close $bh;
-
+      my $png_file = $self->invoice_barcode($dir);
       my $eps_file = $png_file;
       $eps_file =~ s/\.png$/.eps/g;
       $png_file =~ /(barcode.*png)/;
@@ -2180,7 +2186,7 @@ sub print_latex {
       # after painfuly long experimentation, it was determined that sam2p won't
       #        accept : and other chars in the path, no matter how hard I tried to
       # escape them, hence the chdir (and chdir back, just to be safe)
-      system('sam2p', $png_file, 'EPS:', $eps_file ) == 0
+      system('sam2p', '-j:quiet', $png_file, 'EPS:', $eps_file ) == 0
        or die "sam2p failed: $!\n";
       unlink($png_file);
       chdir($curr_dir);
@@ -2203,6 +2209,35 @@ sub print_latex {
 
 }
 
+=item invoice_barcode DIR_OR_FALSE
+
+Generates an invoice barcode PNG. If DIR_OR_FALSE is a true value,
+it is taken as the temp directory where the PNG file will be generated and the
+PNG file name is returned. Otherwise, the PNG image itself is returned.
+
+=cut
+
+sub invoice_barcode {
+    my ($self, $dir) = (shift,shift);
+    
+    my $gdbar = new GD::Barcode('Code39',$self->invnum);
+       die "can't create barcode: " . $GD::Barcode::errStr unless $gdbar;
+    my $gd = $gdbar->plot(Height => 30);
+
+    if($dir) {
+       my $bh = new File::Temp( TEMPLATE => 'barcode.'. $self->invnum. '.XXXXXXXX',
+                          DIR      => $dir,
+                          SUFFIX   => '.png',
+                          UNLINK   => 0,
+                        ) or die "can't open temp file: $!\n";
+       print $bh $gd->png or die "cannot write barcode to file: $!\n";
+       my $png_file = $bh->filename;
+       close $bh;
+       return $png_file;
+    }
+    return $gd->png;
+}
+
 =item print_generic OPTION => VALUE ...
 
 Internal method - returns a filled-in template for this invoice as a scalar.
@@ -2398,6 +2433,12 @@ sub print_generic {
                            );
   my $embolden_function = $embolden_functions{$format};
 
+  my %newline_tokens = (  'latex'     => '\\\\',
+                          'html'      => '<br>',
+                          'template'  => "\n",
+                        );
+  my $newline_token = $newline_tokens{$format};
+
   warn "$me generating template variables\n"
     if $DEBUG > 1;
 
@@ -2511,6 +2552,21 @@ sub print_generic {
     'total_pages'     => 1,
 
   );
+  
+  my $min_sdate = 999999999999;
+  my $max_edate = 0;
+  foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
+    next unless $cust_bill_pkg->pkgnum > 0;
+    $min_sdate = $cust_bill_pkg->sdate
+      if length($cust_bill_pkg->sdate) && $cust_bill_pkg->sdate < $min_sdate;
+    $max_edate = $cust_bill_pkg->edate
+      if length($cust_bill_pkg->edate) && $cust_bill_pkg->edate > $max_edate;
+  }
+
+  $invoice_data{'bill_period'} = '';
+  $invoice_data{'bill_period'} = time2str('%e %h', $min_sdate) 
+    . " to " . time2str('%e %h', $max_edate)
+    if ($max_edate != 0 && $min_sdate != 999999999999);
 
   $invoice_data{finance_section} = '';
   if ( $conf->config('finance_pkgclass') ) {
@@ -2564,6 +2620,10 @@ sub print_generic {
     if $params{'logo_file'};
   $invoice_data{'barcode_file'} = $params{'barcode_file'}
     if $params{'barcode_file'};
+  $invoice_data{'barcode_img'} = $params{'barcode_img'}
+    if $params{'barcode_img'};
+  $invoice_data{'barcode_cid'} = $params{'barcode_cid'}
+    if $params{'barcode_cid'};
 
   my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
 #  my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
@@ -3063,6 +3123,26 @@ sub print_generic {
       push @buf,[$self->balance_due_msg, $money_char. 
         sprintf("%10.2f", $balance_due ) ];
     }
+
+    if ( $conf->exists('previous_balance-show_credit')
+        and $cust_main->balance < 0 ) {
+      my $credit_total = {
+        'total_item'    => &$embolden_function($self->credit_balance_msg),
+        'total_amount'  => &$embolden_function(
+          $other_money_char. sprintf('%.2f', -$cust_main->balance)
+        ),
+      };
+      if ( $multisection ) {
+        $adjust_section->{'posttotal'} .= $newline_token .
+          $credit_total->{'total_item'} . ' ' . $credit_total->{'total_amount'};
+      }
+      else {
+        push @total_items, $credit_total;
+      }
+      push @buf,['','-----------'];
+      push @buf,[$self->credit_balance_msg, $money_char. 
+        sprintf("%10.2f", -$cust_main->balance ) ];
+    }
   }
 
   if ( $multisection ) {
@@ -3277,7 +3357,7 @@ sub print_html {
   }
 
   $params{'format'} = 'html';
-
+  
   $self->print_generic( %params );
 }
 
@@ -3422,6 +3502,8 @@ sub balance_due_date {
   $duedate;
 }
 
+sub credit_balance_msg { 'Credit Balance Remaining' }
+
 =item invnum_date_pretty
 
 Returns a string with the invoice number and date, for example:
@@ -4135,6 +4217,85 @@ sub _items_svc_phone_sections {
       push @lines, $l;
     }
   }
+  
+  if($conf->exists('phone_usage_class_summary')) { 
+      # this only works with Latex
+      my @newlines;
+      my @newsections;
+
+      # after this, we'll have only two sections per DID:
+      # Calls Summary and Calls Detail
+      foreach my $section ( @sections ) {
+       if($section->{'post_total'}) {
+           $section->{'description'} = 'Calls Summary: '.$section->{'phonenum'};
+           $section->{'total_line_generator'} = sub { '' };
+           $section->{'total_generator'} = sub { '' };
+           $section->{'header_generator'} = sub { '' };
+           $section->{'description_generator'} = '';
+           push @newsections, $section;
+           my %calls_detail = %$section;
+           $calls_detail{'post_total'} = '';
+           $calls_detail{'sort_weight'} = '';
+           $calls_detail{'description_generator'} = sub { '' };
+           $calls_detail{'header_generator'} = sub {
+               return ' & Date/Time & Called Number & Duration & Price'
+                   if $format eq 'latex';
+               '';
+           };
+           $calls_detail{'description'} = 'Calls Detail: '
+                                                   . $section->{'phonenum'};
+           push @newsections, \%calls_detail;  
+       }
+      }
+
+      # after this, each usage class is collapsed/summarized into a single
+      # line under the Calls Summary section
+      foreach my $newsection ( @newsections ) {
+       if($newsection->{'post_total'}) { # this means Calls Summary
+           foreach my $section ( @sections ) {
+               next unless ($section->{'phonenum'} eq $newsection->{'phonenum'} 
+                               && !$section->{'post_total'});
+               my $newdesc = $section->{'description'};
+               my $tn = $section->{'phonenum'};
+               $newdesc =~ s/$tn//g;
+               my $line = {  ext_description => [],
+                             pkgnum => '',
+                             ref => '',
+                             quantity => '',
+                             calls => $section->{'calls'},
+                             section => $newsection,
+                             duration => $section->{'duration'},
+                             description => $newdesc,
+                             amount => sprintf("%.2f",$section->{'amount'}),
+                             product_code => 'N/A',
+                           };
+               push @newlines, $line;
+           }
+       }
+      }
+
+      # after this, Calls Details is populated with all CDRs
+      foreach my $newsection ( @newsections ) {
+       if(!$newsection->{'post_total'}) { # this means Calls Details
+           foreach my $line ( @lines ) {
+               next unless (scalar(@{$line->{'ext_description'}}) &&
+                       $line->{'section'}->{'phonenum'} eq $newsection->{'phonenum'}
+                           );
+               my @extdesc = @{$line->{'ext_description'}};
+               my @newextdesc;
+               foreach my $extdesc ( @extdesc ) {
+                   $extdesc =~ s/scriptsize/normalsize/g if $format eq 'latex';
+                   push @newextdesc, $extdesc;
+               }
+               $line->{'ext_description'} = \@newextdesc;
+               $line->{'section'} = $newsection;
+               push @newlines, $line;
+           }
+       }
+      }
+
+      return(\@newsections, \@newlines);
+  }
 
   return(\@sections, \@lines);
 
@@ -4421,7 +4582,7 @@ sub _items_cust_bill_pkg {
             if $DEBUG > 1;
 
           push @d, $cust_bill_pkg->details(%details_opt)
-            unless ($is_summary || $type && $type eq 'R');
+            unless $is_summary; # || ($type && $type eq 'R');
 
           warn "$me _items_cust_bill_pkg calculating amount\n"
             if $DEBUG > 1;