when refunding against a specific card payment, use the card details from the payment...
[freeside.git] / FS / FS / cust_bill.pm
index 3b76dd2..ecc6d5f 100644 (file)
@@ -6,6 +6,7 @@ use vars qw( $invoice_lines @buf ); #yuck
 use Date::Format;
 use Text::Template;
 use File::Temp 0.14;
 use Date::Format;
 use Text::Template;
 use File::Temp 0.14;
+use String::ShellQuote;
 use FS::UID qw( datasrc );
 use FS::Record qw( qsearch qsearchs );
 use FS::Misc qw( send_email );
 use FS::UID qw( datasrc );
 use FS::Record qw( qsearch qsearchs );
 use FS::Misc qw( send_email );
@@ -317,7 +318,7 @@ sub owed {
   $balance;
 }
 
   $balance;
 }
 
-=item send [ TEMPLATENAME [ , AGENTNUM ] ]
+=item send [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ]
 
 Sends this invoice to the destinations configured for this customer: send
 emails or print.  See L<FS::cust_main_invoice>.
 
 Sends this invoice to the destinations configured for this customer: send
 emails or print.  See L<FS::cust_main_invoice>.
@@ -327,12 +328,18 @@ TEMPLATENAME, if specified, is the name of a suffix for alternate invoices.
 AGENTNUM, if specified, means that this invoice will only be sent for customers
 of the specified agent.
 
 AGENTNUM, if specified, means that this invoice will only be sent for customers
 of the specified agent.
 
+INVOICE_FROM, if specified, overrides the default email invoice From: address.
+
 =cut
 
 sub send {
   my $self = shift;
   my $template = scalar(@_) ? shift : '';
 =cut
 
 sub send {
   my $self = shift;
   my $template = scalar(@_) ? shift : '';
-  return '' if scalar(@_) && $_[0] && $self->cust_main->agentnum ne shift;
+  return 'N/A' if scalar(@_) && $_[0] && $self->cust_main->agentnum != shift;
+  my $invoice_from =
+    scalar(@_)
+      ? shift
+      : ( $self->_agent_invoice_from || $conf->config('invoice_from') );
 
   my @print_text = $self->print_text('', $template);
   my @invoicing_list = $self->cust_main->invoicing_list;
 
   my @print_text = $self->print_text('', $template);
   my @invoicing_list = $self->cust_main->invoicing_list;
@@ -340,23 +347,22 @@ sub send {
   if ( grep { $_ ne 'POST' } @invoicing_list or !@invoicing_list  ) { #email
 
     #better to notify this person than silence
   if ( grep { $_ ne 'POST' } @invoicing_list or !@invoicing_list  ) { #email
 
     #better to notify this person than silence
-    @invoicing_list = ($conf->config('invoice_from')) unless @invoicing_list;
+    @invoicing_list = ($invoice_from) unless @invoicing_list;
 
     my $error = send_email(
 
     my $error = send_email(
-      'from'    => $conf->config('invoice_from'),
+      'from'    => $invoice_from,
       'to'      => [ grep { $_ ne 'POST' } @invoicing_list ],
       'subject' => 'Invoice',
       'body'    => \@print_text,
     );
     die "can't email invoice: $error\n" if $error;
       'to'      => [ grep { $_ ne 'POST' } @invoicing_list ],
       'subject' => 'Invoice',
       'body'    => \@print_text,
     );
     die "can't email invoice: $error\n" if $error;
+    #die "$error\n" if $error;
 
   }
 
 
   }
 
-  if ( $conf->config('invoice_latex') ) {
-    @print_text = $self->print_ps('', $template);
-  }
-
   if ( grep { $_ eq 'POST' } @invoicing_list ) { #postal
   if ( grep { $_ eq 'POST' } @invoicing_list ) { #postal
+    @print_text = $self->print_ps('', $template)
+      if $conf->config('invoice_latex');
     my $lpr = $conf->config('lpr');
     open(LPR, "|$lpr")
       or die "Can't open pipe to $lpr: $!\n";
     my $lpr = $conf->config('lpr');
     open(LPR, "|$lpr")
       or die "Can't open pipe to $lpr: $!\n";
@@ -370,6 +376,29 @@ sub send {
 
 }
 
 
 }
 
+=item send_if_newest [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ]
+
+Like B<send>, but only sends the invoice if it is the newest open invoice for
+this customer.
+
+=cut
+
+sub send_if_newest {
+  my $self = shift;
+
+  return ''
+    if scalar(
+               grep { $_->owed > 0 } 
+                    qsearch('cust_bill', {
+                      'custnum' => $self->custnum,
+                      #'_date'   => { op=>'>', value=>$self->_date },
+                      'invnum'  => { op=>'>', value=>$self->invnum },
+                    } )
+             );
+    
+  $self->send(@_);
+}
+
 =item send_csv OPTIONS
 
 Sends invoice as a CSV data-file to a remote host with the specified protocol.
 =item send_csv OPTIONS
 
 Sends invoice as a CSV data-file to a remote host with the specified protocol.
@@ -665,24 +694,38 @@ sub batch_card {
 
 sub _agent_template {
   my $self = shift;
 
 sub _agent_template {
   my $self = shift;
+  $self->_agent_plandata('agent_templatename');
+}
 
 
-  my $cust_bill_event = qsearchs( 'part_bill_event',
+sub _agent_invoice_from {
+  my $self = shift;
+  $self->_agent_plandata('agent_invoice_from');
+}
+
+sub _agent_plandata {
+  my( $self, $option ) = @_;
+
+  my $part_bill_event = qsearchs( 'part_bill_event',
     {
       'payby'     => $self->cust_main->payby,
       'plan'      => 'send_agent',
     {
       'payby'     => $self->cust_main->payby,
       'plan'      => 'send_agent',
-      'eventcode' => { 'op'    => 'LIKE',
-                       'value' => '_%, '. $self->cust_main->agentnum. ');' },
+      'plandata'  => { 'op'    => '~',
+                       'value' => "(^|\n)agentnum ".
+                                  $self->cust_main->agentnum.
+                                  "(\n|\$)",
+                     },
     },
     '',
     'ORDER BY seconds LIMIT 1'
   );
 
     },
     '',
     'ORDER BY seconds LIMIT 1'
   );
 
-  return '' unless $cust_bill_event;
+  return '' unless $part_bill_event;
 
 
-  if ( $cust_bill_event->eventcode =~ /\(\s*'(.*)'\s*,\s*(\d+)\s*\)\;$/ ) {
+  if ( $part_bill_event->plandata =~ /^$option (.*)$/m ) {
     return $1;
   } else {
     return $1;
   } else {
-    warn "can't parse eventcode for agent-specific invoice template";
+    warn "can't parse part_bill_event eventpart#". $part_bill_event->eventpart.
+         " plandata for $option";
     return '';
   }
 
     return '';
   }
 
@@ -699,14 +742,16 @@ L<Time::Local> and L<Date::Parse> for conversion functions.
 
 =cut
 
 
 =cut
 
+#still some false laziness w/print_text
 sub print_text {
 
   my( $self, $today, $template ) = @_;
   $today ||= time;
 sub print_text {
 
   my( $self, $today, $template ) = @_;
   $today ||= time;
+
 #  my $invnum = $self->invnum;
 #  my $invnum = $self->invnum;
-  my $cust_main = qsearchs('cust_main', { 'custnum', $self->custnum } );
+  my $cust_main = $self->cust_main;
   $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
   $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
-    unless $cust_main->payname && $cust_main->payby ne 'CHEK';
+    unless $cust_main->payname && $cust_main->payby !~ /^(CHEK|DCHK)$/;
 
   my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
 #  my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
 
   my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
 #  my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
@@ -750,7 +795,8 @@ sub print_text {
         push @buf, [ $description,
                      $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ];
         push @buf,
         push @buf, [ $description,
                      $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ];
         push @buf,
-          map { [ "  ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
+          map { [ "  ". $_->[0]. ": ". $_->[1], '' ] }
+              $cust_pkg->h_labels($self->_date);
       }
 
       if ( $cust_bill_pkg->recur != 0 ) {
       }
 
       if ( $cust_bill_pkg->recur != 0 ) {
@@ -760,7 +806,8 @@ sub print_text {
           $money_char. sprintf("%10.2f", $cust_bill_pkg->recur)
         ];
         push @buf,
           $money_char. sprintf("%10.2f", $cust_bill_pkg->recur)
         ];
         push @buf,
-          map { [ "  ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
+          map { [ "  ". $_->[0]. ": ". $_->[1], '' ] }
+              $cust_pkg->h_labels($cust_bill_pkg->edate, $cust_bill_pkg->sdate);
       }
 
       push @buf, map { [ "  $_", '' ] } $cust_bill_pkg->details;
       }
 
       push @buf, map { [ "  $_", '' ] } $cust_bill_pkg->details;
@@ -946,7 +993,7 @@ sub print_latex {
 #  my $invnum = $self->invnum;
   my $cust_main = $self->cust_main;
   $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
 #  my $invnum = $self->invnum;
   my $cust_main = $self->cust_main;
   $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
-    unless $cust_main->payname && $cust_main->payby ne 'CHEK';
+    unless $cust_main->payname && $cust_main->payby !~ /^(CHEK|DCHK)$/;
 
   my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
 #  my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
 
   my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
 #  my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
@@ -1142,13 +1189,15 @@ sub print_ps {
   my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
   chdir($dir);
 
   my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
   chdir($dir);
 
-  system("pslatex $file.tex >/dev/null 2>&1") == 0
-    or die "pslatex $file.tex failed: $!";
-  system("pslatex $file.tex >/dev/null 2>&1") == 0
-    or die "pslatex $file.tex failed: $!";
+  my $sfile = shell_quote $file;
+
+  system("pslatex $sfile.tex >/dev/null 2>&1") == 0
+    or die "pslatex $file.tex failed; see $file.log for details?\n";
+  system("pslatex $sfile.tex >/dev/null 2>&1") == 0
+    or die "pslatex $file.tex failed; see $file.log for details?\n";
 
   system('dvips', '-q', '-t', 'letter', "$file.dvi", '-o', "$file.ps" ) == 0
 
   system('dvips', '-q', '-t', 'letter', "$file.dvi", '-o', "$file.ps" ) == 0
-    or die "dbips failed: $!";
+    or die "dvips failed";
 
   open(POSTSCRIPT, "<$file.ps")
     or die "can't open $file.ps: $! (error in LaTeX template?)\n";
 
   open(POSTSCRIPT, "<$file.ps")
     or die "can't open $file.ps: $! (error in LaTeX template?)\n";
@@ -1189,15 +1238,17 @@ sub print_pdf {
   #system('pdflatex', "$file.tex");
   #! LaTeX Error: Unknown graphics extension: .eps.
 
   #system('pdflatex', "$file.tex");
   #! LaTeX Error: Unknown graphics extension: .eps.
 
-  system("pslatex $file.tex >/dev/null 2>&1") == 0
+  my $sfile = shell_quote $file;
+
+  system("pslatex $sfile.tex >/dev/null 2>&1") == 0
     or die "pslatex $file.tex failed: $!";
     or die "pslatex $file.tex failed: $!";
-  system("pslatex $file.tex >/dev/null 2>&1") == 0
+  system("pslatex $sfile.tex >/dev/null 2>&1") == 0
     or die "pslatex $file.tex failed: $!";
 
   #system('dvipdf', "$file.dvi", "$file.pdf" );
   system(
     or die "pslatex $file.tex failed: $!";
 
   #system('dvipdf', "$file.dvi", "$file.pdf" );
   system(
-    "dvips -q -t letter -f $file.dvi ".
-    "| gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=$file.pdf ".
+    "dvips -q -t letter -f $sfile.dvi ".
+    "| gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=$sfile.pdf ".
     "     -c save pop -"
   ) == 0
     or die "dvips | gs failed: $!";
     "     -c save pop -"
   ) == 0
     or die "dvips | gs failed: $!";
@@ -1229,7 +1280,7 @@ sub print_pdf {
 
 sub _latex_escape {
   my $value = shift;
 
 sub _latex_escape {
   my $value = shift;
-  $value =~ s/([#\$%&~_\^{}])( )?/"\\$1". ( length($2) ? "\\$2" : '' )/ge;
+  $value =~ s/([#\$%&~_\^{}])( )?/"\\$1". ( ( defined($2) && length($2) ) ? "\\$2" : '' )/ge;
   $value;
 }
 
   $value;
 }
 
@@ -1316,45 +1367,32 @@ sub _items_cust_bill_pkg {
       my $part_pkg = qsearchs('part_pkg', { pkgpart=>$cust_pkg->pkgpart } );
       my $pkg = $part_pkg->pkg;
 
       my $part_pkg = qsearchs('part_pkg', { pkgpart=>$cust_pkg->pkgpart } );
       my $pkg = $part_pkg->pkg;
 
-      my %labels;
-      #tie %labels, 'Tie::IxHash';
-      push @{ $labels{$_->[0]} }, $_->[1] foreach $cust_pkg->labels;
-      my @ext_description;
-      foreach my $label ( keys %labels ) {
-        my @values = @{ $labels{$label} };
-        my $num = scalar(@values);
-        if ( $num > 5 ) {
-          push @ext_description, "$label ($num)";
-        } else {
-          push @ext_description, map { "$label: $_" } @values;
-        }
-      }
-
       if ( $cust_bill_pkg->setup != 0 ) {
         my $description = $pkg;
         $description .= ' Setup' if $cust_bill_pkg->recur != 0;
       if ( $cust_bill_pkg->setup != 0 ) {
         my $description = $pkg;
         $description .= ' Setup' if $cust_bill_pkg->recur != 0;
-        my @d = @ext_description;
+        my @d = $cust_pkg->h_labels_short($self->_date);
         push @d, $cust_bill_pkg->details if $cust_bill_pkg->recur == 0;
         push @b, {
         push @d, $cust_bill_pkg->details if $cust_bill_pkg->recur == 0;
         push @b, {
-          'description'     => $description,
-          #'pkgpart'         => $part_pkg->pkgpart,
-          'pkgnum'          => $cust_pkg->pkgnum,
-          'amount'          => sprintf("%10.2f", $cust_bill_pkg->setup),
-          'ext_description' => \@d,
+          description     => $description,
+          #pkgpart         => $part_pkg->pkgpart,
+          pkgnum          => $cust_pkg->pkgnum,
+          amount          => sprintf("%10.2f", $cust_bill_pkg->setup),
+          ext_description => \@d,
         };
       }
 
       if ( $cust_bill_pkg->recur != 0 ) {
         push @b, {
         };
       }
 
       if ( $cust_bill_pkg->recur != 0 ) {
         push @b, {
-          'description'     => "$pkg (" .
+          description     => "$pkg (" .
                                time2str('%x', $cust_bill_pkg->sdate). ' - '.
                                time2str('%x', $cust_bill_pkg->edate). ')',
                                time2str('%x', $cust_bill_pkg->sdate). ' - '.
                                time2str('%x', $cust_bill_pkg->edate). ')',
-          #'pkgpart'         => $part_pkg->pkgpart,
-          'pkgnum'          => $cust_pkg->pkgnum,
-          'amount'          => sprintf("%10.2f", $cust_bill_pkg->recur),
-          'ext_description' => [ @ext_description,
-                                 $cust_bill_pkg->details,
-                               ],
+          #pkgpart         => $part_pkg->pkgpart,
+          pkgnum          => $cust_pkg->pkgnum,
+          amount          => sprintf("%10.2f", $cust_bill_pkg->recur),
+          ext_description => [ $cust_pkg->h_labels_short($cust_bill_pkg->edate,
+                                                         $cust_bill_pkg->sdate),
+                               $cust_bill_pkg->details,
+                             ],
         };
       }
 
         };
       }
 
@@ -1403,7 +1441,7 @@ sub _items_credits {
       #'description' => 'Credit ref\#'. $_->crednum.
       #                 " (". time2str("%x",$_->cust_credit->_date) .")".
       #                 $reason,
       #'description' => 'Credit ref\#'. $_->crednum.
       #                 " (". time2str("%x",$_->cust_credit->_date) .")".
       #                 $reason,
-      'description' => 'Credit applied'.
+      'description' => 'Credit applied '.
                        time2str("%x",$_->cust_credit->_date). $reason,
       'amount'      => sprintf("%10.2f",$_->amount),
     };
                        time2str("%x",$_->cust_credit->_date). $reason,
       'amount'      => sprintf("%10.2f",$_->amount),
     };