modular price plans!
[freeside.git] / FS / FS / part_export.pm
index 3f184be..1e0b905 100644 (file)
@@ -1,12 +1,18 @@
 package FS::part_export;
 
 use strict;
-use vars qw( @ISA );
+use vars qw( @ISA @EXPORT_OK $DEBUG %exports );
+use Exporter;
+use Tie::IxHash;
 use FS::Record qw( qsearch qsearchs dbh );
 use FS::part_svc;
 use FS::part_export_option;
+use FS::export_svc;
 
 @ISA = qw(FS::Record);
+@EXPORT_OK = qw(export_info);
+
+$DEBUG = 0;
 
 =head1 NAME
 
@@ -19,7 +25,7 @@ FS::part_export - Object methods for part_export records
   $record = new FS::part_export \%hash;
   $record = new FS::part_export { 'column' => 'value' };
 
-  ($new_record, $options) = $template_recored->clone( $svcpart );
+  #($new_record, $options) = $template_recored->clone( $svcpart );
 
   $error = $record->insert( { 'option' => 'value' } );
   $error = $record->insert( \%options );
@@ -40,8 +46,6 @@ fields are currently supported:
 
 =item exportnum - primary key
 
-=item svcpart - Service definition (see L<FS::part_svc>) to which this export applies
-
 =item machine - Machine name 
 
 =item exporttype - Export type
@@ -67,27 +71,29 @@ points to.  You can ask the object for a copy with the I<hash> method.
 
 sub table { 'part_export'; }
 
-=item clone SVCPART
-
-An alternate constructor.  Creates a new export by duplicating an existing
-export.  The given svcpart is assigned to the new export.
-
-Returns a list consisting of the new export object and a hashref of options.
-
 =cut
 
-sub clone {
-  my $self = shift;
-  my $class = ref($self);
-  my %hash = $self->hash;
-  $hash{'exportnum'} = '';
-  $hash{'svcpart'} = shift;
-  ( $class->new( \%hash ),
-    { map { $_->optionname => $_->optionvalue }
-        qsearch('part_export_option', { 'exportnum' => $self->exportnum } )
-    }
-  );
-}
+#=item clone SVCPART
+#
+#An alternate constructor.  Creates a new export by duplicating an existing
+#export.  The given svcpart is assigned to the new export.
+#
+#Returns a list consisting of the new export object and a hashref of options.
+#
+#=cut
+#
+#sub clone {
+#  my $self = shift;
+#  my $class = ref($self);
+#  my %hash = $self->hash;
+#  $hash{'exportnum'} = '';
+#  $hash{'svcpart'} = shift;
+#  ( $class->new( \%hash ),
+#    { map { $_->optionname => $_->optionvalue }
+#        qsearch('part_export_option', { 'exportnum' => $self->exportnum } )
+#    }
+#  );
+#}
 
 =item insert HASHREF
 
@@ -137,7 +143,7 @@ sub insert {
 
   '';
 
-};
+}
 
 =item delete
 
@@ -173,6 +179,14 @@ sub delete {
     }
   }
 
+  foreach my $export_svc ( $self->export_svc ) {
+    my $error = $export_svc->delete;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
 
   '';
@@ -258,35 +272,70 @@ sub check {
   my $error = 
     $self->ut_numbern('exportnum')
     || $self->ut_domain('machine')
-    || $self->ut_number('svcpart')
     || $self->ut_alpha('exporttype')
   ;
   return $error if $error;
 
-  return "Unknown svcpart: ". $self->svcpart
-    unless qsearchs( 'part_svc', { 'svcpart' => $self->svcpart } );
-
-  $self->machine =~ /^([\w\-\.]*)$/
-    or return "Illegal machine: ". $self->machine;
-  $self->machine($1);
-
   $self->nodomain =~ /^(Y?)$/ or return "Illegal nodomain: ". $self->nodomain;
   $self->nodomain($1);
 
+  $self->deprecated(1); #BLAH
+
   #check exporttype?
 
-  ''; #no error
+  $self->SUPER::check;
+}
+
+#=item part_svc
+#
+#Returns the service definition (see L<FS::part_svc>) for this export.
+#
+#=cut
+#
+#sub part_svc {
+#  my $self = shift;
+#  qsearchs('part_svc', { svcpart => $self->svcpart } );
+#}
+
+sub part_svc {
+  use Carp;
+  croak "FS::part_export::part_svc deprecated";
+  #confess "FS::part_export::part_svc deprecated";
 }
 
-=item part_svc
+=item svc_x
 
-Returns the service definition (see L<FS::part_svc>) for this export.
+Returns a list of associated FS::svc_* records.
 
 =cut
 
-sub part_svc {
+sub svc_x {
+  my $self = shift;
+  map { $_->svc_x } $self->cust_svc;
+}
+
+=item cust_svc
+
+Returns a list of associated FS::cust_svc records.
+
+=cut
+
+sub cust_svc {
+  my $self = shift;
+  map { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) }
+    grep { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) }
+      $self->export_svc;
+}
+
+=item export_svc
+
+Returns a list of associated FS::export_svc records.
+
+=cut
+
+sub export_svc {
   my $self = shift;
-  qsearchs('part_svc', { svcpart => $self->svcpart } );
+  qsearch('export_svc', { 'exportnum' => $self->exportnum } );
 }
 
 =item part_export_option
@@ -328,29 +377,32 @@ sub option {
   $part_export_option ? $part_export_option->optionvalue : '';
 }
 
-=item rebless
+=item _rebless
 
 Reblesses the object into the FS::part_export::EXPORTTYPE class, where
 EXPORTTYPE is the object's I<exporttype> field.  There should be better docs
-on how to create new exports (and they should live in their own files and be
-autoloaded-on-demand), but until then, see L</NEW EXPORT CLASSES>.
+on how to create new exports, but until then, see L</NEW EXPORT CLASSES>.
 
 =cut
 
-sub rebless {
+sub _rebless {
   my $self = shift;
   my $exporttype = $self->exporttype;
-  my $class = ref($self);
-  bless($self, $class."::$exporttype");
+  my $class = ref($self). "::$exporttype";
+  eval "use $class;";
+  die $@ if $@;
+  bless($self, $class);
 }
 
+#these should probably all go away, just let the subclasses define em
+
 =item export_insert SVC_OBJECT
 
 =cut
 
 sub export_insert {
   my $self = shift;
-  $self->rebless;
+  #$self->rebless;
   $self->_export_insert(@_);
 }
 
@@ -369,7 +421,7 @@ sub export_insert {
 
 sub export_replace {
   my $self = shift;
-  $self->rebless;
+  #$self->rebless;
   $self->_export_replace(@_);
 }
 
@@ -379,279 +431,158 @@ sub export_replace {
 
 sub export_delete {
   my $self = shift;
-  $self->rebless;
+  #$self->rebless;
   $self->_export_delete(@_);
 }
 
-=back
+=item export_suspend
 
 =cut
 
-#infostreet
+sub export_suspend {
+  my $self = shift;
+  #$self->rebless;
+  $self->_export_suspend(@_);
+}
 
-package FS::part_export::infostreet;
-use vars qw(@ISA);
-@ISA = qw(FS::part_export);
+=item export_unsuspend
 
+=cut
+
+sub export_unsuspend {
+  my $self = shift;
+  #$self->rebless;
+  $self->_export_unsuspend(@_);
+}
+
+#fallbacks providing useful error messages intead of infinite loops
 sub _export_insert {
-  my( $self, $svc_acct ) = (shift, shift);
-  $self->infostreet_queue( $svc_acct->svcnum,
-    'createUser', $svc_acct->username, $svc_acct->password );
+  my $self = shift;
+  return "_export_insert: unknown export type ". $self->exporttype;
 }
 
 sub _export_replace {
-  my( $self, $new, $old ) = (shift, shift, shift);
-  return "can't change username with InfoStreet"
-    if $old->username ne $new->username;
-  return '' unless $old->_password ne $new->_password;
-  $self->infostreet_queue( $new->svcnum,
-    'passwd', $new->username, $new->password );
+  my $self = shift;
+  return "_export_replace: unknown export type ". $self->exporttype;
 }
 
 sub _export_delete {
-  my( $self, $svc_acct ) = (shift, shift);
-  $self->infostreet_queue( $svc_acct->svcnum,
-    'purgeAccount,releaseUsername', $svc_acct->username );
-}
-
-sub infostreet_queue {
-  my( $self, $svcnum, $method ) = (shift, shift, shift);
-  my $queue = new FS::queue {
-    'svcnum' => $svcnum,
-    'job'    => 'FS::part_export::infostreet::infostreet_command',
-  };
-  $queue->insert(
-    $self->option('url'),
-    $self->option('login'),
-    $self->option('password'),
-    $self->option('groupID'),
-    $method,
-    @_,
-  );
+  my $self = shift;
+  return "_export_delete: unknown export type ". $self->exporttype;
 }
 
-sub infostreet_command { #subroutine, not method
-  my($url, $username, $password, $groupID, $method, @args) = @_;
-
-  #quelle hack
-  if ( $method =~ /,/ ) {
-    foreach my $part ( split(/,\s*/, $method) ) {
-      infostreet_command($url, $username, $password, $groupID, $part, @args);
-    }
-    return;
-  }
-
-  eval "use Frontier::Client;";
+#call svcdb-specific fallbacks
 
-  my $conn = Frontier::Client->new( url => $url );
-  my $key_result = $conn->call( 'authenticate', $username, $password, $groupID);
-  my %key_result = _infostreet_parse($key_result);
-  die $key_result{error} unless $key_result{success};
-  my $key = $key_result{data};
-
-  my $result = $conn->call($method, $key, @args);
-  my %result = _infostreet_parse($result);
-  die $result{error} unless $result{success};
-
-}
-
-sub _infostreet_parse { #subroutine, not method
-  my $arg = shift;
-  map {
-    my $value = $arg->{$_};
-    #warn ref($value);
-    $value = $value->value()
-      if ref($value) && $value->isa('Frontier::RPC2::DataType');
-    $_=>$value;
-  } keys %$arg;
+sub _export_suspend {
+  my $self = shift;
+  #warn "warning: _export_suspened unimplemented for". ref($self);
+  my $svc_x = shift;
+  my $new = $svc_x->clone_suspended;
+  $self->_export_replace( $new, $svc_x );
 }
 
-#sqlradius
-
-package FS::part_export::sqlradius;
-use vars qw(@ISA);
-@ISA = qw(FS::part_export);
-
-sub _export_insert {
-  my($self, $svc_acct) = (shift, shift);
-  $self->sqlradius_queue( $svc_acct->svcnum, 'insert',
-    'reply', $svc_acct->username, $svc_acct->radius_reply );
-  $self->sqlradius_queue( $svc_acct->svcnum, 'insert',
-    'check', $svc_acct->username, $svc_acct->radius_check );
+sub _export_unsuspend {
+  my $self = shift;
+  #warn "warning: _export_unsuspend unimplemented for ". ref($self);
+  my $svc_x = shift;
+  my $old = $svc_x->clone_kludge_unsuspend;
+  $self->_export_replace( $svc_x, $old );
 }
 
-sub _export_replace {
-  my( $self, $new, $old ) = (shift, shift, shift);
-
-  #return "can't (yet) change username with sqlradius"
-  #  if $old->username ne $new->username;
-  if ( $old->username ne $new->username ) {
-    my $error = $self->sqlradius_queue( $new->svcnum, 'rename',
-      $new->username, $old->username );
-    return $error if $error;
-  }
-
-  foreach my $table (qw(reply check)) {
-    my $method = "radius_$table";
-    my %new = $new->$method;
-    my %old = $old->$method;
-    if ( grep { !exists $old{$_} #new attributes
-                || $new{$_} ne $old{$_} #changed
-              } keys %new
-    ) {
-      my $error = $self->sqlradius_queue( $new->svcnum, 'insert',
-        $table, $new->username, %new );
-      return $error if $error;
-    }
-
-    my @del = grep { !exists $new{$_} } keys %old;
-    my $error = $self->sqlradius_queue( $new->svcnum, 'sqlradius_attrib_delete',
-      $table, $new->username, @del );
-    return $error if $error;
-  }
+=back
 
-  '';
-}
+=head1 SUBROUTINES
 
-sub _export_delete {
-  my( $self, $svc_acct ) = (shift, shift);
-  $self->sqlradius_queue( $svc_acct->svcnum, 'delete',
-    $svc_acct->username );
-}
+=over 4
 
-sub sqlradius_queue {
-  my( $self, $svcnum, $method ) = (shift, shift, shift);
-  my $queue = new FS::queue {
-    'svcnum' => $svcnum,
-    'job'    => "FS::part_export::sqlradius::sqlradius_$method",
-  };
-  $queue->insert(
-    $self->option('datasrc'),
-    $self->option('username'),
-    $self->option('password'),
-    @_,
-  );
-}
+=item export_info [ SVCDB ]
 
-sub sqlradius_insert { #subroutine, not method
-  my $dbh = sqlradius_connect(shift, shift, shift);
-  my( $replycheck, $username, %attributes ) = @_;
-
-  foreach my $attribute ( keys %attributes ) {
-    my $u_sth = $dbh->prepare(
-      "UPDATE rad$replycheck SET Value = ? WHERE UserName = ? AND Attribute = ?"    ) or die $dbh->errstr;
-    my $i_sth = $dbh->prepare(
-      "INSERT INTO rad$replycheck ( id, UserName, Attribute, Value ) ".
-        "VALUES ( ?, ?, ?, ? )" )
-      or die $dbh->errstr;
-    $u_sth->execute($attributes{$attribute}, $username, $attribute) > 0
-      or $i_sth->execute( '', $username, $attribute, $attributes{$attribute} )
-        or die "can't insert into rad$replycheck table: ". $i_sth->errstr;
-  }
-  $dbh->disconnect;
-}
+Returns a hash reference of the exports for the given I<svcdb>, or if no
+I<svcdb> is specified, for all exports.  The keys of the hash are
+I<exporttype>s and the values are again hash references containing information
+on the export:
 
-sub sqlradius_rename { #subroutine, not method
-  my $dbh = sqlradius_connect(shift, shift, shift);
-  my($new_username, $old_username) = @_;
-  foreach my $table (qw(radreply radcheck)) {
-    my $sth = $dbh->prepare("UPDATE $table SET Username = ? WHERE UserName = ?")
-      or die $dbh->errstr;
-    $sth->execute($new_username, $old_username)
-      or die "can't update $table: ". $sth->errstr;
-  }
-  $dbh->disconnect;
-}
+  'desc'     => 'Description',
+  'options'  => {
+                  'option'  => { label=>'Option Label' },
+                  'option2' => { label=>'Another label' },
+                },
+  'nodomain' => 'Y', #or ''
+  'notes'    => 'Additional notes',
 
-sub sqlradius_attrib_delete { #subroutine, not method
-  my $dbh = sqlradius_connect(shift, shift, shift);
-  my( $replycheck, $username, @attrib ) = @_;
+=cut
 
-  foreach my $attribute ( @attrib ) {
-    my $sth = $dbh->prepare(
-        "DELETE FROM rad$replycheck WHERE UserName = ? AND Attribute = ?" )
-      or die $dbh->errstr;
-    $sth->execute($username,$attribute)
-      or die "can't delete from rad$replycheck table: ". $sth->errstr;
-  }
-  $dbh->disconnect;
+sub export_info {
+  #warn $_[0];
+  return $exports{$_[0]} || {} if @_;
+  #{ map { %{$exports{$_}} } keys %exports };
+  my $r = { map { %{$exports{$_}} } keys %exports };
 }
 
-sub sqlradius_delete { #subroutine, not method
-  my $dbh = sqlradius_connect(shift, shift, shift);
-  my $username = shift;
+#=item exporttype2svcdb EXPORTTYPE
+#
+#Returns the applicable I<svcdb> for an I<exporttype>.
+#
+#=cut
+#
+#sub exporttype2svcdb {
+#  my $exporttype = $_[0];
+#  foreach my $svcdb ( keys %exports ) {
+#    return $svcdb if grep { $exporttype eq $_ } keys %{$exports{$svcdb}};
+#  }
+#  '';
+#}
 
-  foreach my $table (qw( radcheck radreply )) {
-    my $sth = $dbh->prepare( "DELETE FROM $table WHERE UserName = ?" );
-    $sth->execute($username)
-      or die "can't delete from $table table: ". $sth->errstr;
+foreach my $INC ( @INC ) {
+  foreach my $file ( glob("$INC/FS/part_export/*.pm") ) {
+    warn "attempting to load export info from $file\n" if $DEBUG;
+    $file =~ /\/(\w+)\.pm$/ or do {
+      warn "unrecognized file in $INC/FS/part_export/: $file\n";
+      next;
+    };
+    my $mod = $1;
+    my $info = eval "use FS::part_export::$mod; ".
+                    "\\%FS::part_export::$mod\::info;";
+    if ( $@ ) {
+      die "error using FS::part_export::$mod (skipping): $@\n" if $@;
+      next;
+    }
+    unless ( keys %$info ) {
+      warn "no %info hash found in FS::part_export::$mod, skipping\n"
+        unless $mod =~ /^(passwdfile|null)$/; #hack but what the heck
+      next;
+    }
+    warn "got export info from FS::part_export::$mod: $info\n" if $DEBUG;
+    no strict 'refs';
+    foreach my $svc (
+      ref($info->{'svc'}) ? @{$info->{'svc'}} : $info->{'svc'}
+    ) {
+      unless ( $svc ) {
+        warn "blank svc for FS::part_export::$mod (skipping)\n";
+        next;
+      }
+      $exports{$svc}->{$mod} = $info;
+    }
   }
-  $dbh->disconnect;
 }
 
-sub sqlradius_connect {
-  #my($datasrc, $username, $password) = @_;
-  #DBI->connect($datasrc, $username, $password) or die $DBI::errstr;
-  DBI->connect(@_) or die $DBI::errstr;
-}
+=back
 
 =head1 NEW EXPORT CLASSES
 
-  #myexport
-  
-  package FS::part_export::myexport;
-  use vars qw(@ISA);
-  @ISA = qw(FS::part_export);
-  
-  sub _export_insert {
-    my($self, $svc_something) = (shift, shift);
-    $self->myexport_queue( $svc_acct->svcnum, 'insert',
-      $svc_something->username, $svc_something->password );
-  }
-  
-  sub _export_replace {
-    my( $self, $new, $old ) = (shift, shift, shift);
-    #return "can't change username with myexport"
-    #  if $old->username ne $new->username;
-    #return '' unless $old->_password ne $new->_password;
-    $self->myexport_queue( $new->svcnum,
-      'replace', $new->username, $new->password );
-  }
-  
-  sub _export_delete {
-    my( $self, $svc_something ) = (shift, shift);
-    $self->myexport_queue( $svc_acct->svcnum,
-      'delete', $svc_something->username );
-  }
-  
-  #a good idea to queue anything that could fail or take any time
-  sub myexport_queue {
-    my( $self, $svcnum, $method ) = (shift, shift, shift);
-    my $queue = new FS::queue {
-      'svcnum' => $svcnum,
-      'job'    => "FS::part_export::myexport::myexport_$method",
-    };
-    $queue->insert( @_ );
-  }
-  
-  sub myexport_insert { #subroutine, not method
-  }
-  sub myexport_replace { #subroutine, not method
-  }
-  sub myexport_delete { #subroutine, not method
-  }
+A module should be added in FS/FS/part_export/ (an example may be found in
+eg/export_template.pm)
 
 =head1 BUGS
 
-Probably.
+Hmm... cust_export class (not necessarily a database table...) ... ?
 
-Hmm, export code has wound up in here.  Move those sub-classes out into their
-own files, at least.  Also hmm... cust_export class (not necessarily a
-database table...) ... ?
+deprecated column...
 
 =head1 SEE ALSO
 
-L<FS::part_export_option>, L<FS::part_svc>, L<FS::svc_acct>, L<FS::svc_domain>,
+L<FS::part_export_option>, L<FS::export_svc>, L<FS::svc_acct>,
+L<FS::svc_domain>,
 L<FS::svc_forward>, L<FS::Record>, schema.html from the base documentation.
 
 =cut