service refactor!
[freeside.git] / FS / FS / svc_Common.pm
index f2e1b9a..f60a7d9 100644 (file)
@@ -1,8 +1,8 @@
 package FS::svc_Common;
 
 use strict;
-use vars qw( @ISA $noexport_hack $DEBUG );
-use Carp;
+use vars qw( @ISA $noexport_hack $DEBUG $me );
+use Carp qw( cluck carp croak ); #specify cluck have to specify them all..
 use FS::Record qw( qsearch qsearchs fields dbh );
 use FS::cust_main_Mixin;
 use FS::cust_svc;
@@ -14,7 +14,8 @@ use FS::inventory_class;
 
 @ISA = qw( FS::cust_main_Mixin FS::Record );
 
-$DEBUG = 1;
+$me = '[FS::svc_Common]';
+$DEBUG = 0;
 
 =head1 NAME
 
@@ -35,6 +36,27 @@ inherit from, i.e. FS::svc_acct.  FS::svc_Common inherits from FS::Record.
 
 =over 4
 
+=item search_sql_field FIELD STRING
+
+Class method which returns an SQL fragment to search for STRING in FIELD.
+
+=cut
+
+sub search_sql_field {
+  my( $class, $field, $string ) = @_;
+  my $table = $class->table;
+  my $q_string = dbh->quote($string);
+  "$table.$field = $q_string";
+}
+
+#fallback for services that don't provide a search... 
+sub search_sql {
+  #my( $class, $string ) = @_;
+  '1 = 0'; #false
+}
+
+=item new
+
 =cut
 
 sub new {
@@ -51,7 +73,10 @@ sub new {
   #$self->{'Hash'} = shift;
   my $newhash = shift;
   $self->{'Hash'} = { map { $_ => $newhash->{$_} } qw(svcnum svcpart) };
-  $self->setdefault;
+
+  $self->setdefault( $self->_fieldhandlers )
+    unless $self->svcnum;
+
   $self->{'Hash'}{$_} = $newhash->{$_}
     foreach grep { defined($newhash->{$_}) && length($newhash->{$_}) }
                  keys %$newhash;
@@ -69,6 +94,9 @@ sub new {
   $self;
 }
 
+#empty default
+sub _fieldhandlers { {}; }
+
 sub virtual_fields {
 
   # This restricts the fields based on part_svc_column and the svcpart of 
@@ -107,6 +135,19 @@ sub virtual_fields {
   return ();
 }
 
+=item label
+
+svc_Common provides a fallback label subroutine that just returns the svcnum.
+
+=cut
+
+sub label {
+  my $self = shift;
+  cluck "warning: ". ref($self). " not loaded or missing label method; ".
+        "using svcnum";
+  $self->svcnum;
+}
+
 =item check
 
 Checks the validity of fields in this record.
@@ -151,13 +192,13 @@ jobnum(s) (they will not run until the specific job(s) complete(s)).
 sub insert {
   my $self = shift;
   my %options = @_;
-  warn "FS::svc_Common::insert called with options ".
-     join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
-  if $DEBUG;
+  warn "[$me] insert called with options ".
+       join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
+    if $DEBUG;
 
   my @jobnums = ();
   local $FS::queue::jobnums = \@jobnums;
-  warn "FS::svc_Common::insert: set \$FS::queue::jobnums to $FS::queue::jobnums"
+  warn "[$me] insert: set \$FS::queue::jobnums to $FS::queue::jobnums\n"
     if $DEBUG;
   my $objects = $options{'child_objects'} || [];
   my $depend_jobnums = $options{'depend_jobnum'} || [];
@@ -235,7 +276,7 @@ sub insert {
   #new-style exports!
   unless ( $noexport_hack ) {
 
-    warn "FS::svc_Common::insert: \$FS::queue::jobnums is $FS::queue::jobnums"
+    warn "[$me] insert: \$FS::queue::jobnums is $FS::queue::jobnums\n"
       if $DEBUG;
 
     foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
@@ -248,11 +289,11 @@ sub insert {
     }
 
     foreach my $depend_jobnum ( @$depend_jobnums ) {
-      warn "inserting dependancies on supplied job $depend_jobnum\n"
+      warn "[$me] inserting dependancies on supplied job $depend_jobnum\n"
         if $DEBUG;
       foreach my $jobnum ( @jobnums ) {
         my $queue = qsearchs('queue', { 'jobnum' => $jobnum } );
-        warn "inserting dependancy for job $jobnum on $depend_jobnum\n"
+        warn "[$me] inserting dependancy for job $jobnum on $depend_jobnum\n"
           if $DEBUG;
         my $error = $queue->depend_insert($depend_jobnum);
         if ( $error ) {
@@ -293,35 +334,15 @@ sub delete {
   local $SIG{TSTP} = 'IGNORE';
   local $SIG{PIPE} = 'IGNORE';
 
-  my $svcnum = $self->svcnum;
-
   my $oldAutoCommit = $FS::UID::AutoCommit;
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
-  $error = $self->SUPER::delete;
-  return $error if $error;
-
-  #new-style exports!
-  unless ( $noexport_hack ) {
-    foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
-      $error = $part_export->export_delete($self);
-      if ( $error ) {
-        $dbh->rollback if $oldAutoCommit;
-        return "exporting to ". $part_export->exporttype.
-               " (transaction rolled back): $error";
-      }
-    }
-  }
-
-  $error = $self->return_inventory;
-  if ( $error ) {
-    $dbh->rollback if $oldAutoCommit;
-    return "error returning inventory: $error";
-  }
-
-  my $cust_svc = $self->cust_svc;
-  $error = $cust_svc->delete;
+  $error =    $self->SUPER::delete
+           || $self->export('delete')
+          || $self->return_inventory
+          || $self->cust_svc->delete
+  ;
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
     return $error;
@@ -353,6 +374,9 @@ sub replace {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
+  # We absolutely have to have an old vs. new record to make this work.
+  $old = $new->replace_old unless defined($old);
+
   my $error = $new->set_auto_inventory;
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
@@ -470,7 +494,7 @@ sub setx {
   my $self = shift;
   my $x = shift;
   my @x = ref($x) ? @$x : ($x);
-  my %coderef = @_ ? shift : {};
+  my $coderef = scalar(@_) ? shift : $self->_fieldhandlers;
 
   my $error =
     $self->ut_numbern('svcnum')
@@ -490,11 +514,9 @@ sub setx {
     my $columnname  = $part_svc_column->columnname;
     my $columnvalue = $part_svc_column->columnvalue;
 
-    if ( exists( $coderef{columnname} ) ) {
-      &{ $coderef{$columnname} }( $self, $columnvalue);
-    } else {
-      $self->setfield( $columnname, $columnvalue );
-    }
+    $columnvalue = &{ $coderef->{$columnname} }( $self, $columnvalue )
+      if exists( $coderef->{$columnname} );
+    $self->setfield( $columnname, $columnvalue );
 
   }
 
@@ -659,33 +681,7 @@ Runs export_suspend callbacks.
 
 sub suspend {
   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;
-
-  #new-style exports!
-  unless ( $noexport_hack ) {
-    foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
-      my $error = $part_export->export_suspend($self);
-      if ( $error ) {
-        $dbh->rollback if $oldAutoCommit;
-        return "error exporting to ". $part_export->exporttype.
-               " (transaction rolled back): $error";
-      }
-    }
-  }
-
-  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-  '';
-
+  $self->export('suspend');
 }
 
 =item unsuspend
@@ -696,6 +692,19 @@ Runs export_unsuspend callbacks.
 
 sub unsuspend {
   my $self = shift;
+  $self->export('unsuspend');
+}
+
+=item export HOOK [ EXPORT_ARGS ]
+
+Runs the provided export hook (i.e. "suspend", "unsuspend") for this service.
+
+=cut
+
+sub export {
+  my( $self, $method ) = ( shift, shift );
+
+  $method = "export_$method" unless $method =~ /^export_/;
 
   local $SIG{HUP} = 'IGNORE';
   local $SIG{INT} = 'IGNORE';
@@ -711,10 +720,11 @@ sub unsuspend {
   #new-style exports!
   unless ( $noexport_hack ) {
     foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
-      my $error = $part_export->export_unsuspend($self);
+      next unless $part_export->can($method);
+      my $error = $part_export->$method($self, @_);
       if ( $error ) {
         $dbh->rollback if $oldAutoCommit;
-        return "error exporting to ". $part_export->exporttype.
+        return "error exporting $method event to ". $part_export->exporttype.
                " (transaction rolled back): $error";
       }
     }
@@ -727,9 +737,13 @@ sub unsuspend {
 
 =item cancel
 
-Stub - returns false (no error) so derived classes don't need to define these
+Stub - returns false (no error) so derived classes don't need to define this
 methods.  Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
 
+This method is called *before* the deletion step which actually deletes the
+services.  This method should therefore only be used for "pre-deletion"
+cancellation steps, if necessary.
+
 =cut
 
 sub cancel { ''; }
@@ -764,6 +778,8 @@ sub clone_kludge_unsuspend {
 
 The setfixed method return value.
 
+B<export> method isn't used by insert and replace methods yet.
+
 =head1 SEE ALSO
 
 L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>, schema.html