add barcodes to invoices, HTML part, RT10698
[freeside.git] / FS / FS / cust_bill.pm
index 49d9128..84ef089 100644 (file)
@@ -13,6 +13,7 @@ use String::ShellQuote;
 use HTML::Entities;
 use Locale::Country;
 use Storable qw( freeze thaw );
+use GD::Barcode;
 use FS::UID qw( datasrc );
 use FS::Misc qw( send_email send_fax generate_ps generate_pdf do_print );
 use FS::Record qw( qsearch qsearchs dbh );
@@ -37,6 +38,7 @@ use FS::part_bill_event;
 use FS::payby;
 use FS::bill_batch;
 use FS::cust_bill_batch;
+use Cwd;
 
 @ISA = qw( FS::cust_main_Mixin FS::Record );
 
@@ -987,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',
@@ -1060,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';
 
@@ -2152,6 +2172,28 @@ sub print_latex {
   close $lh;
   $params{'logo_file'} = $lh->filename;
 
+  if($conf->exists('invoice-barcode')){
+      my $png_file = $self->invoice_barcode($dir);
+      my $eps_file = $png_file;
+      $eps_file =~ s/\.png$/.eps/g;
+      $png_file =~ /(barcode.*png)/;
+      $png_file = $1;
+      $eps_file =~ /(barcode.*eps)/;
+      $eps_file = $1;
+
+      my $curr_dir = cwd();
+      chdir($dir); 
+      # 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', '-j:quiet', $png_file, 'EPS:', $eps_file ) == 0
+       or die "sam2p failed: $!\n";
+      unlink($png_file);
+      chdir($curr_dir);
+
+      $params{'barcode_file'} = $eps_file;
+  }
+
   my @filled_in = $self->print_generic( %params );
   
   my $fh = new File::Temp( TEMPLATE => 'invoice.'. $self->invnum. '.XXXXXXXX',
@@ -2163,10 +2205,39 @@ sub print_latex {
   close $fh;
 
   $fh->filename =~ /^(.*).tex$/ or die "unparsable filename: ". $fh->filename;
-  return ($1, $params{'logo_file'});
+  return ($1, $params{'logo_file'}, $params{'barcode_file'});
 
 }
 
+=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.
@@ -2217,6 +2288,9 @@ sub print_generic {
                      'template' => [ '{', '}' ],
                    );
 
+  warn "$me print_generic creating template\n"
+    if $DEBUG > 1;
+
   #create the template
   my $template = $params{template} ? $params{template} : $self->_agent_template;
   my $templatefile = "invoice_$format";
@@ -2234,12 +2308,18 @@ sub print_generic {
     @invoice_template = _translate_old_latex_format(@invoice_template);
   } 
 
+  warn "$me print_generic creating T:T object\n"
+    if $DEBUG > 1;
+
   my $text_template = new Text::Template(
     TYPE => 'ARRAY',
     SOURCE => \@invoice_template,
     DELIMITERS => $delimiters{$format},
   );
 
+  warn "$me print_generic compiling T:T object\n"
+    if $DEBUG > 1;
+
   $text_template->compile()
     or die "Can't compile $templatefile: $Text::Template::ERROR\n";
 
@@ -2353,6 +2433,8 @@ sub print_generic {
                            );
   my $embolden_function = $embolden_functions{$format};
 
+  warn "$me generating template variables\n"
+    if $DEBUG > 1;
 
   # generate template variables
   my $returnaddress;
@@ -2406,6 +2488,9 @@ sub print_generic {
 
   }
 
+  warn "$me generating invoice data\n"
+    if $DEBUG > 1;
+
   my $agentnum = $self->cust_main->agentnum;
 
   my %invoice_data = (
@@ -2512,6 +2597,12 @@ sub print_generic {
 
   $invoice_data{'logo_file'} = $params{'logo_file'}
     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
@@ -2528,7 +2619,9 @@ sub print_generic {
   }
   $invoice_data{'summarypage'} = $summarypage;
 
-  #do variable substitution in notes, footer, smallfooter
+  warn "$me substituting variables in notes, footer, smallfooter\n"
+    if $DEBUG > 1;
+
   foreach my $include (qw( notes footer smallfooter coupon )) {
 
     my $inc_file = $conf->key_orbase("invoice_${format}$include", $template);
@@ -2599,6 +2692,9 @@ sub print_generic {
   $invoice_data{'buf'} = \@buf;
   $invoice_data{'sections'} = \@sections;
 
+  warn "$me generating sections\n"
+    if $DEBUG > 1;
+
   my $previous_section = { 'description' => 'Previous Charges',
                            'subtotal'    => $other_money_char.
                                             sprintf('%.2f', $pr_total),
@@ -2669,6 +2765,9 @@ sub print_generic {
          )
   {
 
+    warn "$me adding previous balances\n"
+      if $DEBUG > 1;
+
     foreach my $line_item ( $self->_items_previous ) {
 
       my $detail = {
@@ -2703,6 +2802,9 @@ sub print_generic {
   }
  
   if ( $conf->exists('svc_phone-did-summary') ) {
+      warn "$me adding DID summary\n"
+        if $DEBUG > 1;
+
       my ($didsummary,$minutes) = $self->_did_summary;
       my $didsummary_desc = 'DID Activity Summary (Past 30 days)';
       push @detail_items, 
@@ -2714,6 +2816,9 @@ sub print_generic {
 
   foreach my $section (@sections, @$late_sections) {
 
+    warn "$me adding section \n". Dumper($section)
+      if $DEBUG > 1;
+
     # begin some normalization
     $section->{'subtotal'} = $section->{'amount'}
       if $multisection
@@ -2739,6 +2844,9 @@ sub print_generic {
                  );
     }
 
+    warn "$me   setting options\n"
+      if $DEBUG > 1;
+
     my $multilocation = scalar($cust_main->cust_location); #too expensive?
     my %options = ();
     $options{'section'} = $section if $multisection;
@@ -2752,7 +2860,14 @@ sub print_generic {
     $options{'multilocation'} = $multilocation;
     $options{'multisection'} = $multisection;
 
+    warn "$me   searching for line items\n"
+      if $DEBUG > 1;
+
     foreach my $line_item ( $self->_items_pkg(%options) ) {
+
+      warn "$me     adding line item $line_item\n"
+        if $DEBUG > 1;
+
       my $detail = {
         ext_description => [],
       };
@@ -2798,6 +2913,9 @@ sub print_generic {
     unshift @sections, $previous_section if $pr_total;
   }
 
+  warn "$me adding taxes\n"
+    if $DEBUG > 1;
+
   foreach my $tax ( $self->_items_tax ) {
 
     $taxtotal += $tax->{'amount'};
@@ -3131,9 +3249,10 @@ I<notice_name>, if specified, overrides "Invoice" as the name of the sent docume
 sub print_ps {
   my $self = shift;
 
-  my ($file, $lfile) = $self->print_latex(@_);
+  my ($file, $logofile, $barcodefile) = $self->print_latex(@_);
   my $ps = generate_ps($file);
-  unlink($lfile);
+  unlink($logofile);
+  unlink($barcodefile);
 
   $ps;
 }
@@ -3159,9 +3278,10 @@ I<notice_name>, if specified, overrides "Invoice" as the name of the sent docume
 sub print_pdf {
   my $self = shift;
 
-  my ($file, $lfile) = $self->print_latex(@_);
+  my ($file, $logofile, $barcodefile) = $self->print_latex(@_);
   my $pdf = generate_pdf($file);
-  unlink($lfile);
+  unlink($logofile);
+  unlink($barcodefile);
 
   $pdf;
 }
@@ -3196,7 +3316,7 @@ sub print_html {
   }
 
   $params{'format'} = 'html';
-
+  
   $self->print_generic( %params );
 }
 
@@ -4111,9 +4231,21 @@ sub _items_previous {
 sub _items_pkg {
   my $self = shift;
   my %options = @_;
+
+  warn "$me _items_pkg searching for all package line items\n"
+    if $DEBUG > 1;
+
   my @cust_bill_pkg = grep { $_->pkgnum } $self->cust_bill_pkg;
+
+  warn "$me _items_pkg filtering line items\n"
+    if $DEBUG > 1;
   my @items = $self->_items_cust_bill_pkg(\@cust_bill_pkg, @_);
+
   if ($options{section} && $options{section}->{condensed}) {
+
+    warn "$me _items_pkg condensing section\n"
+      if $DEBUG > 1;
+
     my %itemshash = ();
     local $Storable::canonical = 1;
     foreach ( @items ) {
@@ -4133,6 +4265,10 @@ sub _items_pkg {
                  }
              keys %itemshash;
   }
+
+  warn "$me _items_pkg returning ". scalar(@items). " items\n"
+    if $DEBUG > 1;
+
   @items;
 }
 
@@ -4153,7 +4289,7 @@ sub _items_tax {
 
 sub _items_cust_bill_pkg {
   my $self = shift;
-  my $cust_bill_pkg = shift;
+  my $cust_bill_pkgs = shift;
   my %opt = @_;
 
   my $format = $opt{format} || '';
@@ -4168,11 +4304,14 @@ sub _items_cust_bill_pkg {
 
   my @b = ();
   my ($s, $r, $u) = ( undef, undef, undef );
-  foreach my $cust_bill_pkg ( @$cust_bill_pkg )
+  foreach my $cust_bill_pkg ( @$cust_bill_pkgs )
   {
 
+    warn "$me _items_cust_bill_pkg considering cust_bill_pkg $cust_bill_pkg\n"
+      if $DEBUG > 1;
+
     $discount_show_always = ($cust_bill_pkg->cust_bill_pkg_discount
-                               && $conf->exists('discount-show-always'));
+                               && $conf->exists('discount-show-always'));
 
     foreach ( $s, $r, ($opt{skip_usage} ? () : $u ) ) {
       if ( $_ && !$cust_bill_pkg->hidden ) {
@@ -4195,6 +4334,9 @@ sub _items_cust_bill_pkg {
                         )
     {
 
+      warn "$me _items_cust_bill_pkg considering display item $display\n"
+        if $DEBUG > 1;
+
       my $type = $display->type;
 
       my $desc = $cust_bill_pkg->desc;
@@ -4208,10 +4350,16 @@ sub _items_cust_bill_pkg {
 
       if ( $cust_bill_pkg->pkgnum > 0 ) {
 
+        warn "$me _items_cust_bill_pkg cust_bill_pkg is non-tax\n"
+          if $DEBUG > 1;
         my $cust_pkg = $cust_bill_pkg->cust_pkg;
 
         if ( $cust_bill_pkg->setup != 0 && (!$type || $type eq 'S') ) {
 
+          warn "$me _items_cust_bill_pkg adding setup\n"
+            if $DEBUG > 1;
+
           my $description = $desc;
           $description .= ' Setup' if $cust_bill_pkg->recur != 0;
 
@@ -4260,14 +4408,17 @@ sub _items_cust_bill_pkg {
            )
         {
 
+          warn "$me _items_cust_bill_pkg adding recur/usage\n"
+            if $DEBUG > 1;
+
           my $is_summary = $display->summary;
           my $description = ($is_summary && $type && $type eq 'U')
                             ? "Usage charges" : $desc;
 
-          unless ( $conf->exists('disable_line_item_date_ranges') ) {
-            $description .= " (" . time2str($date_format, $cust_bill_pkg->sdate).
-                            " - ". time2str($date_format, $cust_bill_pkg->edate). ")";
-          }
+          $description .= " (" . time2str($date_format, $cust_bill_pkg->sdate).
+                          " - ". time2str($date_format, $cust_bill_pkg->edate).
+                          ")"
+            unless $conf->exists('disable_line_item_date_ranges');
 
           my @d = ();
 
@@ -4284,12 +4435,18 @@ sub _items_cust_bill_pkg {
                 || $is_summary && $type && $type eq 'U' )
           {
 
+            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)
               unless $cust_bill_pkg->pkgpart_override; #don't redisplay services
 
+            warn "$me _items_cust_bill_pkg done adding service details\n"
+              if $DEBUG > 1;
+
             if ( $multilocation ) {
               my $loc = $cust_pkg->location_label;
               $loc = substr($loc, 0, 50). '...'
@@ -4299,8 +4456,14 @@ sub _items_cust_bill_pkg {
 
           }
 
+          warn "$me _items_cust_bill_pkg adding details\n"
+            if $DEBUG > 1;
+
           push @d, $cust_bill_pkg->details(%details_opt)
             unless ($is_summary || $type && $type eq 'R');
+
+          warn "$me _items_cust_bill_pkg calculating amount\n"
+            if $DEBUG > 1;
   
           my $amount = 0;
           if (!$type) {
@@ -4313,6 +4476,9 @@ sub _items_cust_bill_pkg {
   
           if ( !$type || $type eq 'R' ) {
 
+            warn "$me _items_cust_bill_pkg adding recur\n"
+              if $DEBUG > 1;
+
             if ( $cust_bill_pkg->hidden ) {
               $r->{amount}      += $amount;
               $r->{unit_amount} += $cust_bill_pkg->unitrecur;
@@ -4331,6 +4497,9 @@ sub _items_cust_bill_pkg {
 
           } else {  # $type eq 'U'
 
+            warn "$me _items_cust_bill_pkg adding usage\n"
+              if $DEBUG > 1;
+
             if ( $cust_bill_pkg->hidden ) {
               $u->{amount}      += $amount;
               $u->{unit_amount} += $cust_bill_pkg->unitrecur;
@@ -4353,6 +4522,9 @@ sub _items_cust_bill_pkg {
 
       } else { #pkgnum tax or one-shot line item (??)
 
+        warn "$me _items_cust_bill_pkg cust_bill_pkg is tax\n"
+          if $DEBUG > 1;
+
         if ( $cust_bill_pkg->setup != 0 ) {
           push @b, {
             'description' => $desc,
@@ -4374,13 +4546,16 @@ sub _items_cust_bill_pkg {
 
   }
 
+  warn "$me _items_cust_bill_pkg done considering cust_bill_pkgs\n"
+    if $DEBUG > 1;
+
   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, { %$_ }
-           unless ( $_->{amount} == 0 && !$discount_show_always );
+        unless ( $_->{amount} == 0 && !$discount_show_always );
     }
   }