don't require payname for DCHK either
[freeside.git] / FS / FS / cust_main.pm
index 1d2e9ed..86962ce 100644 (file)
@@ -22,6 +22,7 @@ use FS::cust_bill;
 use FS::cust_bill_pkg;
 use FS::cust_pay;
 use FS::cust_credit;
+use FS::cust_refund;
 use FS::part_referral;
 use FS::cust_main_county;
 use FS::agent;
@@ -41,7 +42,7 @@ use FS::Msgcat qw(gettext);
 
 $realtime_bop_decline_quiet = 0;
 
-$Debug = 1;
+$Debug = 0;
 #$Debug = 1;
 
 $import = 0;
@@ -294,8 +295,8 @@ sub insert {
   }
 
   # packages
-  local $FS::svc_Common::noexport_hack = 1 if $options{'noexport'};
-  $error = $self->order_pkgs($cust_pkgs, \$seconds);
+  #local $FS::svc_Common::noexport_hack = 1 if $options{'noexport'};
+  $error = $self->order_pkgs($cust_pkgs, \$seconds, %options);
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
     return $error;
@@ -329,9 +330,27 @@ sub insert {
 
 }
 
-=item order_pkgs
+=item order_pkgs HASHREF, [ , OPTION => VALUE ... ] ]
 
-document me.  like ->insert(%cust_pkg) on an existing record
+Like the insert method on an existing record, this method orders a package
+and included services atomicaly.  Pass a Tie::RefHash data structure to this
+method containing FS::cust_pkg and FS::svc_I<tablename> objects.  There should
+be a better explanation of this, but until then, here's an example:
+
+  use Tie::RefHash;
+  tie %hash, 'Tie::RefHash'; #this part is important
+  %hash = (
+    $cust_pkg => [ $svc_acct ],
+    ...
+  );
+  $cust_main->order_pkgs( \%hash, 'noexport'=>1 );
+
+Currently available options are: I<noexport>
+
+If I<noexport> is set true, no provisioning jobs (exports) are scheduled.
+(You can schedule them later with the B<reexport> method for each
+cust_pkg object.  Using the B<reexport> method on the cust_main object is not
+recommended, as existing services will also be reexported.)
 
 =cut
 
@@ -339,6 +358,7 @@ sub order_pkgs {
   my $self = shift;
   my $cust_pkgs = shift;
   my $seconds = shift;
+  my %options = @_;
 
   local $SIG{HUP} = 'IGNORE';
   local $SIG{INT} = 'IGNORE';
@@ -351,6 +371,8 @@ sub order_pkgs {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
+  local $FS::svc_Common::noexport_hack = 1 if $options{'noexport'};
+
   foreach my $cust_pkg ( keys %$cust_pkgs ) {
     $cust_pkg->custnum( $self->custnum );
     my $error = $cust_pkg->insert;
@@ -379,9 +401,9 @@ sub order_pkgs {
 
 =item reexport
 
-document me.  Re-schedules all exports by calling the B<reexport> method
-of all associated packages (see L<FS::cust_pkg>).  If there is an error,
-returns the error; otherwise returns false.
+Re-schedules all exports by calling the B<reexport> method of all associated
+packages (see L<FS::cust_pkg>).  If there is an error, returns the error;
+otherwise returns false.
 
 =cut
 
@@ -446,19 +468,19 @@ sub delete {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
-  if ( qsearch( 'cust_bill', { 'custnum' => $self->custnum } ) ) {
+  if ( $self->cust_bill ) {
     $dbh->rollback if $oldAutoCommit;
     return "Can't delete a customer with invoices";
   }
-  if ( qsearch( 'cust_credit', { 'custnum' => $self->custnum } ) ) {
+  if ( $self->cust_credit ) {
     $dbh->rollback if $oldAutoCommit;
     return "Can't delete a customer with credits";
   }
-  if ( qsearch( 'cust_pay', { 'custnum' => $self->custnum } ) ) {
+  if ( $self->cust_pay ) {
     $dbh->rollback if $oldAutoCommit;
     return "Can't delete a customer with payments";
   }
-  if ( qsearch( 'cust_refund', { 'custnum' => $self->custnum } ) ) {
+  if ( $self->cust_refund ) {
     $dbh->rollback if $oldAutoCommit;
     return "Can't delete a customer with refunds";
   }
@@ -858,7 +880,7 @@ sub check {
       if !$import && ( $y<$nowy || ( $y==$nowy && $1<$nowm ) );
   }
 
-  if ( $self->payname eq '' && $self->payby ne 'CHEK' &&
+  if ( $self->payname eq '' && $self->payby !~ /^(CHEK|DCHK)$/ &&
        ( ! $conf->exists('require_cardname')
          || $self->payby !~ /^(CARD|DCRD)$/  ) 
   ) {
@@ -872,7 +894,7 @@ sub check {
   $self->tax =~ /^(Y?)$/ or return "Illegal tax: ". $self->tax;
   $self->tax($1);
 
-  $self->otaker(getotaker);
+  $self->otaker(getotaker) unless $self->otaker;
 
   #warn "AFTER: \n". $self->_dump;
 
@@ -1179,15 +1201,15 @@ sub bill {
       }
       $setup = sprintf( "%.2f", $setup );
       $recur = sprintf( "%.2f", $recur );
-      if ( $setup < 0 ) {
+      if ( $setup < 0 && ! $conf->exists('allow_negative_charges') ) {
         $dbh->rollback if $oldAutoCommit;
         return "negative setup $setup for pkgnum ". $cust_pkg->pkgnum;
       }
-      if ( $recur < 0 ) {
+      if ( $recur < 0 && ! $conf->exists('allow_negative_charges') ) {
         $dbh->rollback if $oldAutoCommit;
         return "negative recur $recur for pkgnum ". $cust_pkg->pkgnum;
       }
-      if ( $setup > 0 || $recur > 0 ) {
+      if ( $setup != 0 || $recur != 0 ) {
         my $cust_bill_pkg = new FS::cust_bill_pkg ({
           'pkgnum'  => $cust_pkg->pkgnum,
           'setup'   => $setup,
@@ -1217,6 +1239,16 @@ sub bill {
                                                                       } );
           }
 
+          #one more try at a whole-country tax rate
+          unless ( @taxes ) {
+            @taxes =  qsearch( 'cust_main_county', {
+                                  'state'    => '',
+                                  'county'   => '',
+                                  'country'  => $self->country,
+                                  'taxclass' => '',
+                                                                      } );
+          }
+
           # maybe eliminate this entirely, along with all the 0% records
           unless ( @taxes ) {
             $dbh->rollback if $oldAutoCommit;
@@ -1295,7 +1327,7 @@ sub bill {
 
         } #unless $self->tax =~ /Y/i || $self->payby eq 'COMP'
 
-      } #if $setup > 0 || $recur > 0
+      } #if $setup != 0 || $recur != 0
       
     } #if $cust_pkg_mod_flag
 
@@ -1460,24 +1492,15 @@ sub collect {
     }
   }
 
-  foreach my $cust_bill ( $self->cust_bill ) {
-
-    #this has to be before next's
-    my $amount = sprintf( "%.2f", $balance < $cust_bill->owed
-                                  ? $balance
-                                  : $cust_bill->owed
-    );
-    $balance = sprintf( "%.2f", $balance - $amount );
-
-    next unless $cust_bill->owed > 0;
+  foreach my $cust_bill ( $self->open_cust_bill ) {
 
     # don't try to charge for the same invoice if it's already in a batch
     #next if qsearchs( 'cust_pay_batch', { 'invnum' => $cust_bill->invnum } );
 
-    warn "invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ", amount $amount, balance $balance)" if $Debug;
-
-    next unless $amount > 0;
+    last if $self->balance <= 0;
 
+    warn "invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ")"
+      if $Debug;
 
     foreach my $part_bill_event (
       sort {    $a->seconds   <=> $b->seconds
@@ -1494,7 +1517,8 @@ sub collect {
                                        'disabled' => '',           } )
     ) {
 
-      last unless $cust_bill->owed > 0; #don't run subsequent events if owed=0
+      last if $cust_bill->owed <= 0  # don't run subsequent events if owed<=0
+           || $self->balance   <= 0; # or if balance<=0
 
       warn "calling invoice event (". $part_bill_event->eventcode. ")\n"
         if $Debug;
@@ -2365,6 +2389,42 @@ sub open_cust_bill {
   grep { $_->owed > 0 } $self->cust_bill;
 }
 
+=item cust_credit
+
+Returns all the credits (see L<FS::cust_credit>) for this customer.
+
+=cut
+
+sub cust_credit {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_credit', { 'custnum' => $self->custnum } )
+}
+
+=item cust_pay
+
+Returns all the payments (see L<FS::cust_pay>) for this customer.
+
+=cut
+
+sub cust_pay {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_pay', { 'custnum' => $self->custnum } )
+}
+
+=item cust_refund
+
+Returns all the refunds (see L<FS::cust_refund>) for this customer.
+
+=cut
+
+sub cust_refund {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_refund', { 'custnum' => $self->custnum } )
+}
+
 =back
 
 =head1 SUBROUTINES