add per-agent invoice templates, add per-package suspend invoice events, fix automati...
[freeside.git] / FS / FS / cust_main.pm
index 986fef3..070a888 100644 (file)
@@ -1,7 +1,7 @@
 package FS::cust_main;
 
 use strict;
-use vars qw( @ISA $conf $Debug $import );
+use vars qw( @ISA $conf $DEBUG $import );
 use Safe;
 use Carp;
 BEGIN {
@@ -38,8 +38,8 @@ use FS::Msgcat qw(gettext);
 
 @ISA = qw( FS::Record );
 
-$Debug = 0;
-#$Debug = 1;
+$DEBUG = 0;
+#$DEBUG = 1;
 
 $import = 0;
 
@@ -223,10 +223,16 @@ invoicing_list destination to the newly-created svc_acct.  Here's an example:
 
   $cust_main->insert( {}, [ $email, 'POST' ] );
 
-Currently available options are: I<noexport>
+Currently available options are: I<depend_jobnum> and I<noexport>.
 
-If I<noexport> is set true, no provisioning jobs (exports) are scheduled.
-(You can schedule them later with the B<reexport> method.)
+If I<depend_jobnum> is set, all provisioning jobs will have a dependancy
+on the supplied jobnum (they will not run until the specific job completes).
+This can be used to defer provisioning until some action completes (such
+as running the customer's credit card sucessfully).
+
+The I<noexport> option is deprecated.  If I<noexport> is set true, no
+provisioning jobs (exports) are scheduled.  (You can schedule them later with
+the B<reexport> method.)
 
 =cut
 
@@ -235,6 +241,9 @@ sub insert {
   my $cust_pkgs = @_ ? shift : {};
   my $invoicing_list = @_ ? shift : '';
   my %options = @_;
+  warn "FS::cust_main::insert called with options ".
+       join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
+    if $DEBUG;
 
   local $SIG{HUP} = 'IGNORE';
   local $SIG{INT} = 'IGNORE';
@@ -286,7 +295,6 @@ sub insert {
   }
 
   # packages
-  #local $FS::svc_Common::noexport_hack = 1 if $options{'noexport'};
   $error = $self->order_pkgs($cust_pkgs, \$seconds, %options);
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
@@ -321,7 +329,7 @@ sub insert {
 
 }
 
-=item order_pkgs HASHREF, [ , OPTION => VALUE ... ] ]
+=item order_pkgs HASHREF, [ SECONDSREF, [ , OPTION => VALUE ... ] ]
 
 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
@@ -334,14 +342,20 @@ be a better explanation of this, but until then, here's an example:
     $cust_pkg => [ $svc_acct ],
     ...
   );
-  $cust_main->order_pkgs( \%hash, 'noexport'=>1 );
+  $cust_main->order_pkgs( \%hash, \'0', 'noexport'=>1 );
+
+Currently available options are: I<depend_jobnum> and I<noexport>.
 
-Currently available options are: I<noexport>
+If I<depend_jobnum> is set, all provisioning jobs will have a dependancy
+on the supplied jobnum (they will not run until the specific job completes).
+This can be used to defer provisioning until some action completes (such
+as running the customer's credit card sucessfully).
 
-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.)
+The I<noexport> option is deprecated.  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
 
@@ -350,6 +364,12 @@ sub order_pkgs {
   my $cust_pkgs = shift;
   my $seconds = shift;
   my %options = @_;
+  my %svc_options = ();
+  $svc_options{'depend_jobnum'} = $options{'depend_jobnum'}
+    if exists $options{'depend_jobnum'};
+  warn "FS::cust_main::order_pkgs called with options ".
+       join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
+    if $DEBUG;
 
   local $SIG{HUP} = 'IGNORE';
   local $SIG{INT} = 'IGNORE';
@@ -377,7 +397,7 @@ sub order_pkgs {
         $svc_something->seconds( $svc_something->seconds + $$seconds );
         $$seconds = 0;
       }
-      $error = $svc_something->insert;
+      $error = $svc_something->insert(%svc_options);
       if ( $error ) {
         $dbh->rollback if $oldAutoCommit;
         #return "inserting svc_ (transaction rolled back): $error";
@@ -392,6 +412,9 @@ sub order_pkgs {
 
 =item reexport
 
+This method is deprecated.  See the I<depend_jobnum> option to the insert and
+order_pkgs methods for a better way to defer provisioning.
+
 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.
@@ -401,6 +424,9 @@ otherwise returns false.
 sub reexport {
   my $self = shift;
 
+  carp "warning: FS::cust_main::reexport is deprectated; ".
+       "use the depend_jobnum option to insert or order_pkgs to delay export";
+
   local $SIG{HUP} = 'IGNORE';
   local $SIG{INT} = 'IGNORE';
   local $SIG{QUIT} = 'IGNORE';
@@ -986,6 +1012,38 @@ sub suspend {
   grep { $_->suspend } $self->unsuspended_pkgs;
 }
 
+=item suspend_if_pkgpart PKGPART [ , PKGPART ... ]
+
+Suspends all unsuspended packages (see L<FS::cust_pkg>) matching the listed
+PKGPARTs (see L<FS::part_pkg>).  Always returns a list: an empty list on
+success or a list of errors.
+
+=cut
+
+sub suspend_if_pkgpart {
+  my $self = shift;
+  my @pkgparts = @_;
+  grep { $_->suspend }
+    grep { my $pkgpart = $_->pkgpart; grep { $pkgpart eq $_ } @pkgparts }
+      $self->unsuspended_pkgs;
+}
+
+=item suspend_unless_pkgpart PKGPART [ , PKGPART ... ]
+
+Suspends all unsuspended packages (see L<FS::cust_pkg>) unless they match the
+listed PKGPARTs (see L<FS::part_pkg>).  Always returns a list: an empty list
+on success or a list of errors.
+
+=cut
+
+sub suspend_unless_pkgpart {
+  my $self = shift;
+  my @pkgparts = @_;
+  grep { $_->suspend }
+    grep { my $pkgpart = $_->pkgpart; ! grep { $pkgpart eq $_ } @pkgparts }
+      $self->unsuspended_pkgs;
+}
+
 =item cancel [ OPTION => VALUE ... ]
 
 Cancels all uncancelled packages (see L<FS::cust_pkg>) for this customer.
@@ -1056,6 +1114,8 @@ sub bill {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
+  $self->select_for_update; #mutex
+
   # find the packages which are due for billing, find out how much they are
   # & generate invoice database.
  
@@ -1453,8 +1513,10 @@ sub collect {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
+  $self->select_for_update; #mutex
+
   my $balance = $self->balance;
-  warn "collect customer". $self->custnum. ": balance $balance" if $Debug;
+  warn "collect customer". $self->custnum. ": balance $balance" if $DEBUG;
   unless ( $balance > 0 ) { #redundant?????
     $dbh->rollback if $oldAutoCommit; #hmm
     return '';
@@ -1480,14 +1542,14 @@ sub collect {
     last if $self->balance <= 0;
 
     warn "invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ")"
-      if $Debug;
+      if $DEBUG;
 
     foreach my $part_bill_event (
       sort {    $a->seconds   <=> $b->seconds
              || $a->weight    <=> $b->weight
              || $a->eventpart <=> $b->eventpart }
         grep { $_->seconds <= ( $invoice_time - $cust_bill->_date )
-               && ! qsearchs( 'cust_bill_event', {
+               && ! qsearch( 'cust_bill_event', {
                                 'invnum'    => $cust_bill->invnum,
                                 'eventpart' => $_->eventpart,
                                 'status'    => 'done',
@@ -1501,7 +1563,7 @@ sub collect {
            || $self->balance   <= 0; # or if balance<=0
 
       warn "calling invoice event (". $part_bill_event->eventcode. ")\n"
-        if $Debug;
+        if $DEBUG;
       my $cust_main = $self; #for callback
 
       my $error;
@@ -2146,6 +2208,18 @@ sub cust_refund {
     qsearch( 'cust_refund', { 'custnum' => $self->custnum } )
 }
 
+=item select_for_update
+
+Selects this record with the SQL "FOR UPDATE" command.  This can be useful as
+a mutex.
+
+=cut
+
+sub select_for_update {
+  my $self = shift;
+  qsearch('cust_main', { 'custnum' => $self->custnum }, '*', 'FOR UPDATE' );
+}
+
 =back
 
 =head1 SUBROUTINES
@@ -2336,7 +2410,7 @@ sub batch_import {
     my %cust_main = (
       agentnum => $agentnum,
       refnum   => $refnum,
-      country  => 'US', #default
+      country  => $conf->config('countrydefault') || 'US',
       payby    => 'BILL', #default
       paydate  => '12/2037', #default
     );