parse paydate correctly for cybercash
[freeside.git] / site_perl / cust_main.pm
index 83a7d78..ad2ff02 100644 (file)
@@ -5,29 +5,35 @@ use vars qw($paymentserversecret $paymentserverport $paymentserverhost);
 package FS::cust_main;
 
 use strict;
 package FS::cust_main;
 
 use strict;
-use vars qw(@ISA @EXPORT_OK $conf $lpr $processor $xaction $E_NoErr);
+use vars qw(@ISA $conf $lpr $processor $xaction $E_NoErr $invoice_from);
 use Safe;
 use Safe;
-use Exporter;
 use Carp;
 use Time::Local;
 use Date::Format;
 use Date::Manip;
 use Carp;
 use Time::Local;
 use Date::Format;
 use Date::Manip;
+use Mail::Internet;
+use Mail::Header;
 use Business::CreditCard;
 use Business::CreditCard;
-use FS::UID qw(getotaker);
-use FS::Record qw(fields hfields qsearchs qsearch);
+use FS::UID qw( getotaker );
+use FS::Record qw( qsearchs qsearch );
 use FS::cust_pkg;
 use FS::cust_bill;
 use FS::cust_bill_pkg;
 use FS::cust_pay;
 use FS::cust_pkg;
 use FS::cust_bill;
 use FS::cust_bill_pkg;
 use FS::cust_pay;
-#use FS::cust_pay_batch;
+use FS::cust_credit;
+use FS::cust_pay_batch;
+use FS::part_referral;
+use FS::cust_main_county;
+use FS::agent;
+use FS::cust_main_invoice;
 
 
-@ISA = qw(FS::Record Exporter);
-@EXPORT_OK = qw(hfields);
+@ISA = qw( FS::Record );
 
 #ask FS::UID to run this stuff for us later
 $FS::UID::callback{'FS::cust_main'} = sub { 
   $conf = new FS::Conf;
   $lpr = $conf->config('lpr');
 
 #ask FS::UID to run this stuff for us later
 $FS::UID::callback{'FS::cust_main'} = sub { 
   $conf = new FS::Conf;
   $lpr = $conf->config('lpr');
+  $invoice_from = $conf->config('invoice_from');
 
   if ( $conf->exists('cybercash3.2') ) {
     require CCMckLib3_2;
 
   if ( $conf->exists('cybercash3.2') ) {
     require CCMckLib3_2;
@@ -70,8 +76,8 @@ FS::cust_main - Object methods for cust_main records
 
   use FS::cust_main;
 
 
   use FS::cust_main;
 
-  $record = create FS::cust_main \%hash;
-  $record = create FS::cust_main { 'column' => 'value' };
+  $record = new FS::cust_main \%hash;
+  $record = new FS::cust_main { 'column' => 'value' };
 
   $error = $record->insert;
 
 
   $error = $record->insert;
 
@@ -153,7 +159,7 @@ FS::Record.  The following fields are currently supported:
 
 =over 4
 
 
 =over 4
 
-=item create HASHREF
+=item new HASHREF
 
 Creates a new customer.  To add the customer to the database, see L<"insert">.
 
 
 Creates a new customer.  To add the customer to the database, see L<"insert">.
 
@@ -162,39 +168,13 @@ points to.  You can ask the object for a copy with the I<hash> method.
 
 =cut
 
 
 =cut
 
-sub create {
-  my($proto,$hashref)=@_;
-
-  #now in FS::Record::new
-  #my $field;
-  #foreach $field (fields('cust_main')) {
-  #  $hashref->{$field}='' unless defined $hashref->{$field};
-  #}
-
-  $proto->new('cust_main',$hashref);
-}
+sub table { 'cust_main'; }
 
 =item insert
 
 Adds this customer to the database.  If there is an error, returns the error,
 otherwise returns false.
 
 
 =item insert
 
 Adds this customer to the database.  If there is an error, returns the error,
 otherwise returns false.
 
-=cut
-
-sub insert {
-  my($self)=@_;
-
-  #no callbacks in check, only data checks
-  #local $SIG{HUP} = 'IGNORE';
-  #local $SIG{INT} = 'IGNORE';
-  #local $SIG{QUIT} = 'IGNORE';
-  #local $SIG{TERM} = 'IGNORE';
-  #local $SIG{TSTP} = 'IGNORE';
-
-  $self->check or
-  $self->add;
-}
-
 =item delete
 
 Currently unimplemented.  Maybe cancel all of this customer's
 =item delete
 
 Currently unimplemented.  Maybe cancel all of this customer's
@@ -205,12 +185,8 @@ be no record the customer ever existed (which is bad, no?)
 
 =cut
 
 
 =cut
 
-# Usage: $error = $record -> delete;
 sub delete {
    return "Can't (yet?) delete customers.";
 sub delete {
    return "Can't (yet?) delete customers.";
-#  my($self)=@_;
-#
-#  $self->del;
 }
 
 =item replace OLD_RECORD
 }
 
 =item replace OLD_RECORD
@@ -218,17 +194,6 @@ sub delete {
 Replaces the OLD_RECORD with this one in the database.  If there is an error,
 returns the error, otherwise returns false.
 
 Replaces the OLD_RECORD with this one in the database.  If there is an error,
 returns the error, otherwise returns false.
 
-=cut
-
-sub replace {
-  my($new,$old)=@_;
-  return "(Old) Not a cust_main record!" unless $old->table eq "cust_main";
-  return "Can't change custnum!"
-    unless $old->getfield('custnum') eq $new->getfield('custnum');
-  $new->check or
-  $new->rep($old);
-}
-
 =item check
 
 Checks all fields to make sure this is a valid customer record.  If there is
 =item check
 
 Checks all fields to make sure this is a valid customer record.  If there is
@@ -238,12 +203,11 @@ and repalce methods.
 =cut
 
 sub check {
 =cut
 
 sub check {
-  my($self)=@_;
-
-  return "Not a cust_main record!" unless $self->table eq "cust_main";
+  my $self = shift;
 
   my $error =
 
   my $error =
-    $self->ut_number('agentnum')
+    $self->ut_numbern('custnum')
+    || $self->ut_number('agentnum')
     || $self->ut_number('refnum')
     || $self->ut_textn('company')
     || $self->ut_text('address1')
     || $self->ut_number('refnum')
     || $self->ut_textn('company')
     || $self->ut_text('address1')
@@ -258,10 +222,10 @@ sub check {
   return $error if $error;
 
   return "Unknown agent"
   return $error if $error;
 
   return "Unknown agent"
-    unless qsearchs('agent',{'agentnum'=>$self->agentnum});
+    unless qsearchs( 'agent', { 'agentnum' => $self->agentnum } );
 
   return "Unknown referral"
 
   return "Unknown referral"
-    unless qsearchs('part_referral',{'refnum'=>$self->refnum});
+    unless qsearchs( 'part_referral', { 'refnum' => $self->refnum } );
 
   $self->getfield('last') =~ /^([\w \,\.\-\']+)$/ or return "Illegal last name";
   $self->setfield('last',$1);
 
   $self->getfield('last') =~ /^([\w \,\.\-\']+)$/ or return "Illegal last name";
   $self->setfield('last',$1);
@@ -279,20 +243,24 @@ sub check {
     $self->ss("$1-$2-$3");
   }
 
     $self->ss("$1-$2-$3");
   }
 
-  return "Unknown state/county/country"
-    unless qsearchs('cust_main_county',{
-      'state'  => $self->state,
-      'county' => $self->county,
-    } );
+  $self->country =~ /^(\w\w)$/ or return "Illegal country";
+  $self->country($1);
+  unless ( qsearchs('cust_main_county', {
+    'country' => $self->country,
+    'state'   => '',
+   } ) ) {
+    return "Unknown state/county/country"
+      #" state ". $self->state. " county ". $self->county. " country ". $self->country
+      unless qsearchs('cust_main_county',{
+        'state'   => $self->state,
+        'county'  => $self->county,
+        'country' => $self->country,
+      } );
+  }
 
 
-  #int'l zips?
-  $self->zip =~ /^(\d{5}(-\d{4})?)$/ or return "Illegal zip";
+  $self->zip =~ /^([\w\-]{5,10})$/ or return "Illegal zip";
   $self->zip($1);
 
   $self->zip($1);
 
-  #int'l countries!
-  $self->country =~ /^(US)$/ or return "Illegal country";
-  $self->country($1);
-
   $self->payby =~ /^(CARD|BILL|COMP)$/ or return "Illegal payby";
   $self->payby($1);
 
   $self->payby =~ /^(CARD|BILL|COMP)$/ or return "Illegal payby";
   $self->payby($1);
 
@@ -305,22 +273,17 @@ sub check {
     $payinfo = $1;
     $self->payinfo($payinfo);
     validate($payinfo) or return "Illegal credit card number";
     $payinfo = $1;
     $self->payinfo($payinfo);
     validate($payinfo) or return "Illegal credit card number";
-    my $type = cardtype($payinfo);
-    return "Unknown credit card type"
-      unless ( $type =~ /^VISA/ ||
-               $type =~ /^MasterCard/ ||
-               $type =~ /^American Express/ ||
-               $type =~ /^Discover/ );
+    return "Unknown card type" if cardtype($self->payinfo) eq "Unknown";
 
   } elsif ( $self->payby eq 'BILL' ) {
 
 
   } elsif ( $self->payby eq 'BILL' ) {
 
-    $self->payinfo =~ /^([\w \-]*)$/ or return "Illegal P.O. number";
-    $self->payinfo($1);
+    $error = $self->ut_textn('payinfo');
+    return "Illegal P.O. number" if $error;
 
   } elsif ( $self->payby eq 'COMP' ) {
 
 
   } elsif ( $self->payby eq 'COMP' ) {
 
-    $self->payinfo =~ /^(\w{2,8})$/ or return "Illegal comp account issuer";
-    $self->payinfo($1);
+    $error = $self->ut_textn('payinfo');
+    return "Illegal comp account issuer" if $error;
 
   }
 
 
   }
 
@@ -362,7 +325,7 @@ Returns all packages (see L<FS::cust_pkg>) for this customer.
 =cut
 
 sub all_pkgs {
 =cut
 
 sub all_pkgs {
-  my($self)=@_;
+  my $self = shift;
   qsearch( 'cust_pkg', { 'custnum' => $self->custnum });
 }
 
   qsearch( 'cust_pkg', { 'custnum' => $self->custnum });
 }
 
@@ -373,7 +336,7 @@ Returns all non-cancelled packages (see L<FS::cust_pkg>) for this customer.
 =cut
 
 sub ncancelled_pkgs {
 =cut
 
 sub ncancelled_pkgs {
-  my($self)=@_;
+  my $self = shift;
   qsearch( 'cust_pkg', {
     'custnum' => $self->custnum,
     'cancel'  => '',
   qsearch( 'cust_pkg', {
     'custnum' => $self->custnum,
     'cancel'  => '',
@@ -395,10 +358,10 @@ If there is an error, returns the error, otherwise returns false.
 =cut
 
 sub bill {
 =cut
 
 sub bill {
-  my($self,%options)=@_;
-  my($time) = $options{'time'} || $^T;
+  my( $self, %options ) = @_;
+  my $time = $options{'time'} || time;
 
 
-  my($error);
+  my $error;
 
   #put below somehow?
   local $SIG{HUP} = 'IGNORE';
 
   #put below somehow?
   local $SIG{HUP} = 'IGNORE';
@@ -406,42 +369,38 @@ sub bill {
   local $SIG{QUIT} = 'IGNORE';
   local $SIG{TERM} = 'IGNORE';
   local $SIG{TSTP} = 'IGNORE';
   local $SIG{QUIT} = 'IGNORE';
   local $SIG{TERM} = 'IGNORE';
   local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
 
   # find the packages which are due for billing, find out how much they are
   # & generate invoice database.
  
 
   # find the packages which are due for billing, find out how much they are
   # & generate invoice database.
  
-  my($total_setup,$total_recur)=(0,0);
+  my( $total_setup, $total_recur ) = ( 0, 0 );
+  my @cust_bill_pkg;
 
 
-  my(@cust_bill_pkg);
-
-  my($cust_pkg);
-  foreach $cust_pkg (
+  foreach my $cust_pkg (
     qsearch('cust_pkg',{'custnum'=> $self->getfield('custnum') } )
   ) {
 
     qsearch('cust_pkg',{'custnum'=> $self->getfield('custnum') } )
   ) {
 
-    bless($cust_pkg,"FS::cust_pkg");
-    next if ( $cust_pkg->getfield('cancel') );  
+    next if $cust_pkg->getfield('cancel');  
 
     #? to avoid use of uninitialized value errors... ?
     $cust_pkg->setfield('bill', '')
       unless defined($cust_pkg->bill);
  
 
     #? to avoid use of uninitialized value errors... ?
     $cust_pkg->setfield('bill', '')
       unless defined($cust_pkg->bill);
  
-    my($part_pkg)=
-      qsearchs('part_pkg',{'pkgpart'=> $cust_pkg->pkgpart } );
+    my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $cust_pkg->pkgpart } );
 
     #so we don't modify cust_pkg record unnecessarily
 
     #so we don't modify cust_pkg record unnecessarily
-    my($cust_pkg_mod_flag)=0;
-    my(%hash)=$cust_pkg->hash;
-    my($old_cust_pkg)=create FS::cust_pkg(\%hash);
+    my $cust_pkg_mod_flag = 0;
+    my %hash = $cust_pkg->hash;
+    my $old_cust_pkg = new FS::cust_pkg \%hash;
 
     # bill setup
 
     # bill setup
-    my($setup)=0;
+    my $setup = 0;
     unless ( $cust_pkg->setup ) {
     unless ( $cust_pkg->setup ) {
-      my($setup_prog)=$part_pkg->getfield('setup');
-      my($cpt) = new Safe;
+      my $setup_prog = $part_pkg->getfield('setup');
+      my $cpt = new Safe;
       #$cpt->permit(); #what is necessary?
       #$cpt->permit(); #what is necessary?
-      $cpt->share(qw($cust_pkg)); #can $cpt now use $cust_pkg methods?
+      $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
       $setup = $cpt->reval($setup_prog);
       unless ( defined($setup) ) {
         warn "Error reval-ing part_pkg->setup pkgpart ", 
       $setup = $cpt->reval($setup_prog);
       unless ( defined($setup) ) {
         warn "Error reval-ing part_pkg->setup pkgpart ", 
@@ -453,16 +412,16 @@ sub bill {
     }
 
     #bill recurring fee
     }
 
     #bill recurring fee
-    my($recur)=0;
-    my($sdate);
+    my $recur = 0;
+    my $sdate;
     if ( $part_pkg->getfield('freq') > 0 &&
          ! $cust_pkg->getfield('susp') &&
          ( $cust_pkg->getfield('bill') || 0 ) < $time
     ) {
     if ( $part_pkg->getfield('freq') > 0 &&
          ! $cust_pkg->getfield('susp') &&
          ( $cust_pkg->getfield('bill') || 0 ) < $time
     ) {
-      my($recur_prog)=$part_pkg->getfield('recur');
-      my($cpt) = new Safe;
+      my $recur_prog = $part_pkg->getfield('recur');
+      my $cpt = new Safe;
       #$cpt->permit(); #what is necessary?
       #$cpt->permit(); #what is necessary?
-      $cpt->share(qw($cust_pkg)); #can $cpt now use $cust_pkg methods?
+      $cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
       $recur = $cpt->reval($recur_prog);
       unless ( defined($recur) ) {
         warn "Error reval-ing part_pkg->recur pkgpart ",
       $recur = $cpt->reval($recur_prog);
       unless ( defined($recur) ) {
         warn "Error reval-ing part_pkg->recur pkgpart ",
@@ -471,13 +430,14 @@ sub bill {
         #change this bit to use Date::Manip?
         #$sdate=$cust_pkg->bill || time;
         #$sdate=$cust_pkg->bill || $time;
         #change this bit to use Date::Manip?
         #$sdate=$cust_pkg->bill || time;
         #$sdate=$cust_pkg->bill || $time;
-        $sdate=$cust_pkg->bill || $cust_pkg->setup || $time;
-        my($sec,$min,$hour,$mday,$mon,$year)=
+        $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
+        my ($sec,$min,$hour,$mday,$mon,$year) =
           (localtime($sdate) )[0,1,2,3,4,5];
         $mon += $part_pkg->getfield('freq');
         until ( $mon < 12 ) { $mon -= 12; $year++; }
           (localtime($sdate) )[0,1,2,3,4,5];
         $mon += $part_pkg->getfield('freq');
         until ( $mon < 12 ) { $mon -= 12; $year++; }
-        $cust_pkg->setfield('bill',timelocal($sec,$min,$hour,$mday,$mon,$year));
-        $cust_pkg_mod_flag=1; 
+        $cust_pkg->setfield('bill',
+          timelocal($sec,$min,$hour,$mday,$mon,$year));
+        $cust_pkg_mod_flag = 1; 
       }
     }
 
       }
     }
 
@@ -485,15 +445,14 @@ sub bill {
     warn "recur is undefinded" unless defined($recur);
     warn "cust_pkg bill is undefinded" unless defined($cust_pkg->bill);
 
     warn "recur is undefinded" unless defined($recur);
     warn "cust_pkg bill is undefinded" unless defined($cust_pkg->bill);
 
-    if ($cust_pkg_mod_flag) {
+    if ( $cust_pkg_mod_flag ) {
       $error=$cust_pkg->replace($old_cust_pkg);
       $error=$cust_pkg->replace($old_cust_pkg);
-      if ( $error ) {
+      if ( $error ) { #just in case
         warn "Error modifying pkgnum ", $cust_pkg->pkgnum, ": $error";
       } else {
         warn "Error modifying pkgnum ", $cust_pkg->pkgnum, ": $error";
       } else {
-        #just in case
-        $setup=sprintf("%.2f",$setup);
-        $recur=sprintf("%.2f",$recur);
-        my($cust_bill_pkg)=create FS::cust_bill_pkg ({
+        $setup = sprintf( "%.2f", $setup );
+        $recur = sprintf( "%.2f", $recur );
+        my $cust_bill_pkg = new FS::cust_bill_pkg ({
           'pkgnum' => $cust_pkg->pkgnum,
           'setup'  => $setup,
           'recur'  => $recur,
           'pkgnum' => $cust_pkg->pkgnum,
           'setup'  => $setup,
           'recur'  => $recur,
@@ -508,24 +467,24 @@ sub bill {
 
   }
 
 
   }
 
-  my($charged)=sprintf("%.2f",$total_setup + $total_recur);
+  my $charged = sprintf( "%.2f", $total_setup + $total_recur );
 
   return '' if scalar(@cust_bill_pkg) == 0;
 
 
   return '' if scalar(@cust_bill_pkg) == 0;
 
-  unless ( $self->getfield('tax') eq 'Y' ||
-           $self->getfield('tax') eq 'y' ||
-           $self->getfield('payby') eq 'COMP'
+  unless ( $self->getfield('tax') =~ /Y/i
+           || $self->getfield('payby') eq 'COMP'
   ) {
   ) {
-    my($cust_main_county) = qsearchs('cust_main_county',{
-      'county' => $self->getfield('county'),
-      'state'  => $self->getfield('state'),
+    my $cust_main_county = qsearchs('cust_main_county',{
+        'state'   => $self->state,
+        'county'  => $self->county,
+        'country' => $self->country,
     } );
     } );
-    my($tax) = sprintf("%.2f",
+    my $tax = sprintf( "%.2f",
       $charged * ( $cust_main_county->getfield('tax') / 100 )
     );
       $charged * ( $cust_main_county->getfield('tax') / 100 )
     );
-    $charged = sprintf("%.2f",$charged+$tax);
+    $charged = sprintf( "%.2f", $charged+$tax );
 
 
-    my($cust_bill_pkg)=create FS::cust_bill_pkg ({
+    my $cust_bill_pkg = new FS::cust_bill_pkg ({
       'pkgnum' => 0,
       'setup'  => $tax,
       'recur'  => 0,
       'pkgnum' => 0,
       'setup'  => $tax,
       'recur'  => 0,
@@ -535,23 +494,23 @@ sub bill {
     push @cust_bill_pkg, $cust_bill_pkg;
   }
 
     push @cust_bill_pkg, $cust_bill_pkg;
   }
 
-  my($cust_bill) = create FS::cust_bill ( {
+  my $cust_bill = new FS::cust_bill ( {
     'custnum' => $self->getfield('custnum'),
     '_date' => $time,
     'charged' => $charged,
   } );
     'custnum' => $self->getfield('custnum'),
     '_date' => $time,
     'charged' => $charged,
   } );
-  $error=$cust_bill->insert;
+  $error = $cust_bill->insert;
   #shouldn't happen, but how else to handle this? (wrap me in eval, to catch 
   # fatal errors)
   die "Error creating cust_bill record: $error!\n",
       "Check updated but unbilled packages for customer", $self->custnum, "\n"
     if $error;
 
   #shouldn't happen, but how else to handle this? (wrap me in eval, to catch 
   # fatal errors)
   die "Error creating cust_bill record: $error!\n",
       "Check updated but unbilled packages for customer", $self->custnum, "\n"
     if $error;
 
-  my($invnum)=$cust_bill->invnum;
-  my($cust_bill_pkg);
+  my $invnum = $cust_bill->invnum;
+  my $cust_bill_pkg;
   foreach $cust_bill_pkg ( @cust_bill_pkg ) {
   foreach $cust_bill_pkg ( @cust_bill_pkg ) {
-    $cust_bill_pkg->setfield('invnum',$invnum);
-    $error=$cust_bill_pkg->insert;
+    $cust_bill_pkg->setfield( 'invnum', $invnum );
+    $error = $cust_bill_pkg->insert;
     #shouldn't happen, but how else tohandle this?
     die "Error creating cust_bill_pkg record: $error!\n",
         "Check incomplete invoice ", $invnum, "\n"
     #shouldn't happen, but how else tohandle this?
     die "Error creating cust_bill_pkg record: $error!\n",
         "Check incomplete invoice ", $invnum, "\n"
@@ -587,10 +546,10 @@ return an error.  By default, they don't.
 =cut
 
 sub collect {
 =cut
 
 sub collect {
-  my($self,%options)=@_;
-  my($invoice_time) = $options{'invoice_time'} || $^T;
+  my( $self, %options ) = @_;
+  my $invoice_time = $options{'invoice_time'} || time;
 
 
-  my($total_owed) = $self->balance;
+  my $total_owed = $self->balance;
   return '' unless $total_owed > 0; #redundant?????
 
   #put below somehow?
   return '' unless $total_owed > 0; #redundant?????
 
   #put below somehow?
@@ -599,91 +558,107 @@ sub collect {
   local $SIG{QUIT} = 'IGNORE';
   local $SIG{TERM} = 'IGNORE';
   local $SIG{TSTP} = 'IGNORE';
   local $SIG{QUIT} = 'IGNORE';
   local $SIG{TERM} = 'IGNORE';
   local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
 
 
-  foreach my $cust_bill ( qsearch('cust_bill', {
-    'custnum' => $self->getfield('custnum'),
-  } ) ) {
-
-    bless($cust_bill,"FS::cust_bill");
+  foreach my $cust_bill (
+    qsearch('cust_bill', { 'custnum' => $self->custnum, } )
+  ) {
 
     #this has to be before next's
 
     #this has to be before next's
-    my($amount) = sprintf("%.2f", $total_owed < $cust_bill->owed
+    my $amount = sprintf( "%.2f", $total_owed < $cust_bill->owed
                                   ? $total_owed
                                   : $cust_bill->owed
     );
                                   ? $total_owed
                                   : $cust_bill->owed
     );
-    $total_owed = sprintf("%.2f",$total_owed-$amount);
+    $total_owed = sprintf( "%.2f", $total_owed - $amount );
 
     next unless $cust_bill->owed > 0;
 
 
     next unless $cust_bill->owed > 0;
 
-    next if qsearchs('cust_pay_batch',{'invnum'=> $cust_bill->invnum });
+    next if qsearchs( 'cust_pay_batch', { 'invnum' => $cust_bill->invnum } );
 
     #warn "invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ", amount $amount, total_owed $total_owed)";
 
     next unless $amount > 0;
 
 
     #warn "invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ", amount $amount, total_owed $total_owed)";
 
     next unless $amount > 0;
 
-    if ( $self->getfield('payby') eq 'BILL' ) {
+    if ( $self->payby eq 'BILL' ) {
 
       #30 days 2592000
 
       #30 days 2592000
-      my($since)=$invoice_time - ( $cust_bill->_date || 0 );
+      my $since = $invoice_time - ( $cust_bill->_date || 0 );
       #warn "$invoice_time ", $cust_bill->_date, " $since";
       if ( $since >= 0 #don't print future invoices
            && ( $cust_bill->printed * 2592000 ) <= $since
       ) {
 
       #warn "$invoice_time ", $cust_bill->_date, " $since";
       if ( $since >= 0 #don't print future invoices
            && ( $cust_bill->printed * 2592000 ) <= $since
       ) {
 
-        open(LPR,"|$lpr") or die "Can't open $lpr: $!";
-        print LPR $cust_bill->print_text; #( date )
-        close LPR
-          or die $! ? "Error closing $lpr: $!"
-                       : "Exit status $? from $lpr";
+        #my @print_text = $cust_bill->print_text; #( date )
+        my @invoicing_list = $self->invoicing_list;
+        if ( grep { $_ ne 'POST' } @invoicing_list ) { #email invoice
+          my $header = new Mail::Header ( [
+            "From: $invoice_from",
+            "To: ". join(', ', grep { $_ ne 'POST' } @invoicing_list ),
+            "Sender: $invoice_from",
+            "Reply-To: $invoice_from",
+            "Date: ". time2str("%a, %d %b %Y %X %z", time),
+            "Subject: Invoice",
+          ] );
+          my $message = new Mail::Internet (
+            'Header' => $header,
+            'Body' => [ $cust_bill->print_text ], #( date)
+          );
+          $message->smtpsend or die "Can't send invoice email!"; #die?  warn?
+
+        } elsif ( ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list ) {
+          open(LPR, "|$lpr") or die "Can't open pipe to $lpr: $!";
+          print LPR $cust_bill->print_text; #( date )
+          close LPR
+            or die $! ? "Error closing $lpr: $!"
+                         : "Exit status $? from $lpr";
+        }
 
 
-        my(%hash)=$cust_bill->hash;
+        my %hash = $cust_bill->hash;
         $hash{'printed'}++;
         $hash{'printed'}++;
-        my($new_cust_bill)=create FS::cust_bill(\%hash);
-        my($error)=$new_cust_bill->replace($cust_bill);
-        if ( $error ) {
-          warn "Error updating $cust_bill->printed: $error";
-        }
+        my $new_cust_bill = new FS::cust_bill(\%hash);
+        my $error = $new_cust_bill->replace($cust_bill);
+        warn "Error updating $cust_bill->printed: $error" if $error;
 
       }
 
 
       }
 
-    } elsif ( $self->getfield('payby') eq 'COMP' ) {
-      my($cust_pay) = create FS::cust_pay ( {
-         'invnum' => $cust_bill->getfield('invnum'),
+    } elsif ( $self->payby eq 'COMP' ) {
+      my $cust_pay = new FS::cust_pay ( {
+         'invnum' => $cust_bill->invnum,
          'paid' => $amount,
          '_date' => '',
          'payby' => 'COMP',
          'paid' => $amount,
          '_date' => '',
          'payby' => 'COMP',
-         'payinfo' => $self->getfield('payinfo'),
+         'payinfo' => $self->payinfo,
          'paybatch' => ''
       } );
          'paybatch' => ''
       } );
-      my($error)=$cust_pay->insert;
-      return 'Error COMPing invnum #' . $cust_bill->getfield('invnum') .
+      my $error = $cust_pay->insert;
+      return 'Error COMPing invnum #' . $cust_bill->invnum .
              ':' . $error if $error;
              ':' . $error if $error;
-    } elsif ( $self->getfield('payby') eq 'CARD' ) {
+
+    } elsif ( $self->payby eq 'CARD' ) {
 
       if ( $options{'batch_card'} ne 'yes' ) {
 
         return "Real time card processing not enabled!" unless $processor;
 
 
       if ( $options{'batch_card'} ne 'yes' ) {
 
         return "Real time card processing not enabled!" unless $processor;
 
-        if ( $processor =~ /cybercash/ ) {
+        if ( $processor =~ /^cybercash/ ) {
 
           #fix exp. date for cybercash
 
           #fix exp. date for cybercash
-          $self->getfield('paydate') =~ /^(\d+)\/\d*(\d{2})$/;
-          my($exp)="$1/$2";
+          #$self->paydate =~ /^(\d+)\/\d*(\d{2})$/;
+          $self->paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+          my $exp = "$2/$1";
 
 
-          my($paybatch)= $cust_bill->getfield('invnum') 
-                         '-' . time2str("%y%m%d%H%M%S",time);
+          my $paybatch = $cust_bill->invnum
+                         '-' . time2str("%y%m%d%H%M%S", time);
 
 
-          my($payname)= $self->getfield('payname') ||
-                        $self->getfield('first') . ' ' .$self->getfield('last');
+          my $payname = $self->payname ||
+                        $self->getfield('first'). ' '. $self->getfield('last');
 
 
-          my($address)= $self->getfield('address1');
-          $address .= ", " . $self->getfield('address2')
-            if $self->getfield('address2');
+          my $address = $self->address1;
+          $address .= ", ". $self->address2 if $self->address2;
 
 
-          my($country) = $self->getfield('country') eq 'US' ?
-                         'USA' : $self->getfield('country');
+          my $country = 'USA' if $self->country eq 'US';
 
 
-          my(@full_xaction)=($xaction,
+          my @full_xaction = ( $xaction,
             'Order-ID'     => $paybatch,
             'Amount'       => "usd $amount",
             'Card-Number'  => $self->getfield('payinfo'),
             'Order-ID'     => $paybatch,
             'Amount'       => "usd $amount",
             'Card-Number'  => $self->getfield('payinfo'),
@@ -696,7 +671,7 @@ sub collect {
             'Card-Exp'     => $exp,
           );
 
             'Card-Exp'     => $exp,
           );
 
-          my(%result);
+          my %result;
           if ( $processor eq 'cybercash2' ) {
             $^W=0; #CCLib isn't -w safe, ugh!
             %result = &CCLib::sendmserver(@full_xaction);
           if ( $processor eq 'cybercash2' ) {
             $^W=0; #CCLib isn't -w safe, ugh!
             %result = &CCLib::sendmserver(@full_xaction);
@@ -710,21 +685,21 @@ sub collect {
           #if ( $result{'MActionCode'} == 7 ) { #cybercash smps v.1.1.3
           #if ( $result{'action-code'} == 7 ) { #cybercash smps v.2.1
           if ( $result{'MStatus'} eq 'success' ) { #cybercash smps v.2 or 3
           #if ( $result{'MActionCode'} == 7 ) { #cybercash smps v.1.1.3
           #if ( $result{'action-code'} == 7 ) { #cybercash smps v.2.1
           if ( $result{'MStatus'} eq 'success' ) { #cybercash smps v.2 or 3
-            my($cust_pay) = create FS::cust_pay ( {
-               'invnum'   => $cust_bill->getfield('invnum'),
+            my $cust_pay = new FS::cust_pay ( {
+               'invnum'   => $cust_bill->invnum,
                'paid'     => $amount,
                '_date'     => '',
                'payby'    => 'CARD',
                'paid'     => $amount,
                '_date'     => '',
                'payby'    => 'CARD',
-               'payinfo'  => $self->getfield('payinfo'),
+               'payinfo'  => $self->payinfo,
                'paybatch' => "$processor:$paybatch",
             } );
                'paybatch' => "$processor:$paybatch",
             } );
-            my($error)=$cust_pay->insert;
+            my $error = $cust_pay->insert;
             return 'Error applying payment, invnum #' . 
             return 'Error applying payment, invnum #' . 
-              $cust_bill->getfield('invnum') . ':' . $error if $error;
+              $cust_bill->invnum. ':'. $error if $error;
           } elsif ( $result{'Mstatus'} ne 'failure-bad-money'
                  || $options{'report_badcard'} ) {
              return 'Cybercash error, invnum #' . 
           } elsif ( $result{'Mstatus'} ne 'failure-bad-money'
                  || $options{'report_badcard'} ) {
              return 'Cybercash error, invnum #' . 
-               $cust_bill->getfield('invnum') . ':' . $result{'MErrMsg'};
+               $cust_bill->invnum. ':'. $result{'MErrMsg'};
           } else {
             return '';
           }
           } else {
             return '';
           }
@@ -735,8 +710,7 @@ sub collect {
 
       } else { #batch card
 
 
       } else { #batch card
 
-#       my($cust_pay_batch) = create FS::cust_pay_batch ( {
-       my($cust_pay_batch) = new FS::Record ('cust_pay_batch', {
+       my $cust_pay_batch = new FS::Record ('cust_pay_batch', {
          'invnum'   => $cust_bill->getfield('invnum'),
          'custnum'  => $self->getfield('custnum'),
          'last'     => $self->getfield('last'),
          'invnum'   => $cust_bill->getfield('invnum'),
          'custnum'  => $self->getfield('custnum'),
          'last'     => $self->getfield('last'),
@@ -753,16 +727,19 @@ sub collect {
          'payname'  => $self->getfield('payname'),
          'amount'   => $amount,
        } );
          'payname'  => $self->getfield('payname'),
          'amount'   => $amount,
        } );
-#       my($error)=$cust_pay_batch->insert;
-       my($error)=$cust_pay_batch->add;
+       my $error = $cust_pay_batch->insert;
        return "Error adding to cust_pay_batch: $error" if $error;
 
       }
 
     } else {
        return "Error adding to cust_pay_batch: $error" if $error;
 
       }
 
     } else {
-      return "Unknown payment type ".$self->getfield('payby');
+      return "Unknown payment type ". $self->payby;
     }
 
     }
 
+
+
+
+
   }
   '';
 
   }
   '';
 
@@ -776,15 +753,14 @@ Returns the total owed for this customer on all invoices
 =cut
 
 sub total_owed {
 =cut
 
 sub total_owed {
-  my($self) = @_;
-  my($total_bill) = 0;
-  my($cust_bill);
-  foreach $cust_bill ( qsearch('cust_bill', {
-    'custnum' => $self->getfield('custnum'),
+  my $self = shift;
+  my $total_bill = 0;
+  foreach my $cust_bill ( qsearch('cust_bill', {
+    'custnum' => $self->custnum,
   } ) ) {
   } ) ) {
-    $total_bill += $cust_bill->getfield('owed');
+    $total_bill += $cust_bill->owed;
   }
   }
-  sprintf("%.2f",$total_bill);
+  sprintf( "%.2f", $total_bill );
 }
 
 =item total_credited
 }
 
 =item total_credited
@@ -794,15 +770,14 @@ Returns the total credits (see L<FS::cust_credit>) for this customer.
 =cut
 
 sub total_credited {
 =cut
 
 sub total_credited {
-  my($self) = @_;
-  my($total_credit) = 0;
-  my($cust_credit);
-  foreach $cust_credit ( qsearch('cust_credit', {
-    'custnum' => $self->getfield('custnum'),
+  my $self = shift;
+  my $total_credit = 0;
+  foreach my $cust_credit ( qsearch('cust_credit', {
+    'custnum' => $self->custnum,
   } ) ) {
   } ) ) {
-    $total_credit += $cust_credit->getfield('credited');
+    $total_credit += $cust_credit->credited;
   }
   }
-  sprintf("%.2f",$total_credit);
+  sprintf( "%.2f", $total_credit );
 }
 
 =item balance
 }
 
 =item balance
@@ -812,30 +787,106 @@ Returns the balance for this customer (total owed minus total credited).
 =cut
 
 sub balance {
 =cut
 
 sub balance {
-  my($self) = @_;
-  sprintf("%.2f",$self->total_owed - $self->total_credited);
+  my $self = shift;
+  sprintf( "%.2f", $self->total_owed - $self->total_credited );
+}
+
+=item invoicing_list [ ARRAYREF ]
+
+If an arguement is given, sets these email addresses as invoice recipients
+(see L<FS::cust_main_invoice>).  Errors are not fatal and are not reported
+(except as warnings), so use check_invoicing_list first.
+
+Returns a list of email addresses (with svcnum entries expanded).
+
+Note: You can clear the invoicing list by passing an empty ARRAYREF.  You can
+check it without disturbing anything by passing nothing.
+
+This interface may change in the future.
+
+=cut
+
+sub invoicing_list {
+  my( $self, $arrayref ) = @_;
+  if ( $arrayref ) {
+    my @cust_main_invoice = 
+      qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
+    foreach my $cust_main_invoice ( @cust_main_invoice ) {
+      #warn $cust_main_invoice->destnum;
+      unless ( grep { $cust_main_invoice->address eq $_ } @{$arrayref} ) {
+        #warn $cust_main_invoice->destnum;
+        my $error = $cust_main_invoice->delete;
+        warn $error if $error;
+      }
+    }
+    @cust_main_invoice =
+      qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
+    foreach my $address ( @{$arrayref} ) {
+      unless ( grep { $address eq $_->address } @cust_main_invoice ) {
+        my $cust_main_invoice = new FS::cust_main_invoice ( {
+          'custnum' => $self->custnum,
+          'dest'    => $address,
+        } );
+        my $error = $cust_main_invoice->insert;
+        warn $error if $error;
+      } 
+    }
+  }
+  map { $_->address }
+    qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
+}
+
+=item check_invoicing_list ARRAYREF
+
+Checks these arguements as valid input for the invoicing_list method.  If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub check_invoicing_list {
+  my( $self, $arrayref ) = @_;
+  foreach my $address ( @{$arrayref} ) {
+    my $cust_main_invoice = new FS::cust_main_invoice ( {
+      'custnum' => $self->custnum,
+      'dest'    => $address,
+    } );
+    my $error = $self->custnum
+                ? $cust_main_invoice->check
+                : $cust_main_invoice->checkdest
+    ;
+    return $error if $error;
+  }
+  '';
 }
 
 =back
 
 }
 
 =back
 
-=head1 BUGS
+=head1 VERSION
 
 
-The delete method.
+$Id: cust_main.pm,v 1.12 1999-02-27 21:24:22 ivan Exp $
 
 
-It doesn't properly override FS::Record yet.
+=head1 BUGS
 
 
-hfields should be removed.
+The delete method.
 
 Bill and collect options should probably be passed as references instead of a
 list.
 
 CyberCash v2 forces us to define some variables in package main.
 
 
 Bill and collect options should probably be passed as references instead of a
 list.
 
 CyberCash v2 forces us to define some variables in package main.
 
+There should probably be a configuration file with a list of allowed credit
+card types.
+
+CyberCash is the only processor.
+
+No multiple currency support (probably a larger project than just this module).
+
 =head1 SEE ALSO
 
 L<FS::Record>, L<FS::cust_pkg>, L<FS::cust_bill>, L<FS::cust_credit>
 L<FS::cust_pay_batch>, L<FS::agent>, L<FS::part_referral>,
 =head1 SEE ALSO
 
 L<FS::Record>, L<FS::cust_pkg>, L<FS::cust_bill>, L<FS::cust_credit>
 L<FS::cust_pay_batch>, L<FS::agent>, L<FS::part_referral>,
-L<FS::cust_main_county>, L<FS::UID>, schema.html from the base documentation.
+L<FS::cust_main_county>, L<FS::cust_main_invoice>,
+L<FS::UID>, schema.html from the base documentation.
 
 =head1 HISTORY
 
 
 =head1 HISTORY
 
@@ -868,7 +919,35 @@ enable cybercash, cybercash v3 support, don't need to import
 FS::UID::{datasrc,checkruid} ivan@sisd.com 98-sep-19-21
 
 $Log: cust_main.pm,v $
 FS::UID::{datasrc,checkruid} ivan@sisd.com 98-sep-19-21
 
 $Log: cust_main.pm,v $
-Revision 1.3  1998-11-13 09:56:54  ivan
+Revision 1.12  1999-02-27 21:24:22  ivan
+parse paydate correctly for cybercash
+
+Revision 1.11  1999/02/23 08:09:27  ivan
+beginnings of one-screen new customer entry and some other miscellania
+
+Revision 1.10  1999/01/25 12:26:09  ivan
+yet more mod_perl stuff
+
+Revision 1.9  1999/01/18 09:22:41  ivan
+changes to track email addresses for email invoicing
+
+Revision 1.8  1998/12/29 11:59:39  ivan
+mostly properly OO, some work still to be done with svc_ stuff
+
+Revision 1.7  1998/12/16 09:58:52  ivan
+library support for editing email invoice destinations (not in sub collect yet)
+
+Revision 1.6  1998/11/18 09:01:42  ivan
+i18n! i18n!
+
+Revision 1.5  1998/11/15 11:23:14  ivan
+use FS::table_name for all searches to eliminate warnings,
+emit state/county when they don't match
+
+Revision 1.4  1998/11/15 05:30:48  ivan
+bugfix for new config layout
+
+Revision 1.3  1998/11/13 09:56:54  ivan
 change configuration file layout to support multiple distinct databases (with
 own set of config files, export, etc.)
 
 change configuration file layout to support multiple distinct databases (with
 own set of config files, export, etc.)