optimize customer list, RT#20173
[freeside.git] / FS / FS / cust_svc.pm
index a7aeada..d432747 100644 (file)
@@ -102,6 +102,37 @@ sub table { 'cust_svc'; }
 Adds this service to the database.  If there is an error, returns the error,
 otherwise returns false.
 
 Adds this service to the database.  If there is an error, returns the error,
 otherwise returns false.
 
+=cut
+
+sub insert {
+  my $self = shift;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $error = $self->SUPER::insert;
+
+  #check if this releases a hold (see FS::pkg_svc provision_hold)
+  $error ||= $self->_check_provision_hold;
+
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error if $error
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  ''; #no error
+
+}
+
 =item delete
 
 Deletes this service from the database.  If there is an error, returns the
 =item delete
 
 Deletes this service from the database.  If there is an error, returns the
@@ -118,8 +149,42 @@ sub delete {
   my $cust_pkg = $self->cust_pkg;
   my $custnum = $cust_pkg->custnum if $cust_pkg;
 
   my $cust_pkg = $self->cust_pkg;
   my $custnum = $cust_pkg->custnum if $cust_pkg;
 
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
   my $error = $self->SUPER::delete;
   my $error = $self->SUPER::delete;
-  return $error if $error;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  foreach my $part_svc_link ( $self->part_svc_link(
+                                link_type   => 'cust_svc_unprovision_cascade',
+                              )
+  ) {
+    foreach my $cust_svc ( qsearch( 'cust_svc', {
+                             'pkgnum'  => $self->pkgnum,
+                             'svcpart' => $part_svc_link->dst_svcpart,
+                           })
+    ) {
+      my $error = $cust_svc->svc_x->delete;
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return $error;
+      }
+    }
+
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
 
   if ( $ticket_system eq 'RT_Internal' ) {
     unless ( $rt_session ) {
 
   if ( $ticket_system eq 'RT_Internal' ) {
     unless ( $rt_session ) {
@@ -144,6 +209,40 @@ sub delete {
       warn "error unlinking ticket $svcnum: $msg\n" if !$val;
     }
   }
       warn "error unlinking ticket $svcnum: $msg\n" if !$val;
     }
   }
+
+  '';
+
+}
+
+=item suspend
+
+Suspends the relevant service by calling the B<suspend> method of the associated
+FS::svc_XXX object (i.e. an FS::svc_acct object or FS::svc_domain object).
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub suspend {
+  my( $self, %opt ) = @_;
+
+  $self->part_svc->svcdb =~ /^([\w\-]+)$/ or return 'Illegal part_svc.svcdb';
+  my $svcdb = $1;
+  require "FS/$svcdb.pm";
+
+  my $svc = qsearchs( $svcdb, { 'svcnum' => $self->svcnum } )
+    or return '';
+
+  my $error = $svc->suspend;
+  return $error if $error;
+
+  if ( $opt{labels_arryref} ) {
+    my( $label, $value ) = $self->label;
+    push @{ $opt{labels_arrayref} }, "$label: $value";
+  }
+
+  '';
+
 }
 
 =item cancel
 }
 
 =item cancel
@@ -360,6 +459,9 @@ sub replace {
     } # if ($svc_x->locationnum)
   } # if this is a location change
 
     } # if ($svc_x->locationnum)
   } # if this is a location change
 
+  #check if this releases a hold (see FS::pkg_svc provision_hold)
+  $error ||= $new->_check_provision_hold;
+
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
     return $error if $error
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
     return $error if $error
@@ -456,6 +558,35 @@ sub check {
   $self->SUPER::check;
 }
 
   $self->SUPER::check;
 }
 
+=item check_part_svc_link_unprovision
+
+Checks service dependency unprovision rules for this service.
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub check_part_svc_link_unprovision {
+  my $self = shift;
+
+  foreach my $part_svc_link ( $self->part_svc_link(
+                                link_type   => 'cust_svc_unprovision_restrict',
+                              )
+  ) {
+    return $part_svc_link->dst_svc. ' must be unprovisioned before '.
+           $part_svc_link->src_svc
+      if qsearchs({
+        'table'    => 'cust_svc',
+        'hashref'  => { 'pkgnum'  => $self->pkgnum,
+                        'svcpart' => $part_svc_link->dst_svcpart,
+                      },
+        'order_by' => 'LIMIT 1',
+      });
+  }
+
+  '';
+}
+
 =item part_svc_link
 
 Returns the service dependencies (see L<FS::part_svc_link>) for the given
 =item part_svc_link
 
 Returns the service dependencies (see L<FS::part_svc_link>) for the given
@@ -470,7 +601,11 @@ link_type.
 sub part_svc_link {
   my $self = shift;
   my $agentnum = $self->pkgnum ? $self->cust_pkg->cust_main->agentnum : '';
 sub part_svc_link {
   my $self = shift;
   my $agentnum = $self->pkgnum ? $self->cust_pkg->cust_main->agentnum : '';
-  FS::part_svc_link->by_agentnum($agentnum, src_svcpart=>$self->svcpart, @_);
+  FS::part_svc_link->by_agentnum($agentnum,
+    src_svcpart=>$self->svcpart,
+    disabled   => '',
+    @_
+  );
 }
 
 =item display_svcnum 
 }
 
 =item display_svcnum 
@@ -1074,9 +1209,10 @@ sub smart_search_param {
   my @or = 
       map { my $table = $_;
             my $search_sql = "FS::$table"->search_sql($string);
   my @or = 
       map { my $table = $_;
             my $search_sql = "FS::$table"->search_sql($string);
+            my $addl_from = "FS::$table"->search_sql_addl_from();
 
             "SELECT $table.svcnum AS svcnum, '$table' AS svcdb ".
 
             "SELECT $table.svcnum AS svcnum, '$table' AS svcdb ".
-            "FROM $table WHERE $search_sql";
+            "FROM $table $addl_from WHERE $search_sql";
           }
       FS::part_svc->svc_tables;
 
           }
       FS::part_svc->svc_tables;
 
@@ -1109,6 +1245,35 @@ sub smart_search_param {
   );
 }
 
   );
 }
 
+# If the associated cust_pkg is 'on hold'
+# and the associated pkg_svc has the provision_hold flag
+# and there are no more available_part_svcs on the cust_pkg similarly flagged,
+# then removes hold from pkg
+# returns $error or '' on success,
+# does not indicate if pkg status was changed
+sub _check_provision_hold {
+  my $self = shift;
+
+  # check status of cust_pkg
+  my $cust_pkg = $self->cust_pkg;
+  return '' unless $cust_pkg && $cust_pkg->status eq 'on hold';
+
+  # check flag on this svc
+  # small false laziness with $self->pkg_svc
+  # to avoid looking up cust_pkg twice
+  my $pkg_svc  = qsearchs( 'pkg_svc', {
+    'svcpart' => $self->svcpart,
+    'pkgpart' => $cust_pkg->pkgpart,
+  });
+  return '' unless $pkg_svc->provision_hold;
+
+  # check for any others available with that flag
+  return '' if $cust_pkg->available_part_svc( 'provision_hold' => 1 );
+
+  # conditions met, remove hold
+  return $cust_pkg->unsuspend;
+}
+
 sub _upgrade_data {
   my $class = shift;
 
 sub _upgrade_data {
   my $class = shift;
 
@@ -1139,13 +1304,19 @@ sub _upgrade_data {
     my $svcdb = $cust_svc->part_svc->svcdb;
     $h_search{'hashref'}{'svcnum'} = $svcnum;
     $h_search{'table'} = "h_$svcdb";
     my $svcdb = $cust_svc->part_svc->svcdb;
     $h_search{'hashref'}{'svcnum'} = $svcnum;
     $h_search{'table'} = "h_$svcdb";
-    my $h_svc_x = qsearchs(\%h_search)
-      or next;
-    my $class = "FS::$svcdb";
-    my $new_svc_x = $class->new({ $h_svc_x->hash });
-    my $error = $new_svc_x->insert;
-    warn "error repairing svcnum $svcnum ($svcdb) from history:\n$error\n"
-      if $error;
+    my $h_svc_x = qsearchs(\%h_search);
+    if ( $h_svc_x ) {
+      my $class = "FS::$svcdb";
+      my $new_svc_x = $class->new({ $h_svc_x->hash });
+      my $error = $new_svc_x->insert;
+      warn "error repairing svcnum $svcnum ($svcdb) from history:\n$error\n"
+        if $error;
+    } else {
+      # can't be fixed, so remove the dangling cust_svc to avoid breaking
+      # stuff
+      my $error = $cust_svc->delete;
+      warn "error cleaning up missing svcnum $svcnum ($svcdb):\n$error\n";
+    }
   }
 
   '';
   }
 
   '';