infrastructure for easier schema changes, and: add payment_gateway, payment_gateway_o...
authorivan <ivan>
Wed, 17 Aug 2005 22:23:46 +0000 (22:23 +0000)
committerivan <ivan>
Wed, 17 Aug 2005 22:23:46 +0000 (22:23 +0000)
27 files changed:
FS/FS.pm
FS/FS/Record.pm
FS/FS/Schema.pm [new file with mode: 0644]
FS/FS/cust_main.pm
FS/FS/option_Common.pm [new file with mode: 0644]
FS/FS/part_export.pm
FS/FS/part_export_option.pm
FS/FS/part_pkg.pm
FS/FS/payment_gateway.pm [new file with mode: 0644]
FS/FS/payment_gateway_option.pm [new file with mode: 0644]
FS/MANIFEST
FS/bin/freeside-setup
FS/bin/freeside-upgrade [new file with mode: 0755]
FS/t/option_Common.t [new file with mode: 0644]
FS/t/payment_gateway.t [new file with mode: 0644]
FS/t/payment_gateway_option.t [new file with mode: 0644]
README.1.5.8 [new file with mode: 0644]
SCHEMA_CHANGE
bin/generate-table-module [new file with mode: 0755]
htetc/global.asa
htetc/handler.pl
httemplate/browse/agent.cgi
httemplate/browse/payment_gateway.html [new file with mode: 0644]
httemplate/docs/upgrade10.html
httemplate/edit/agent_payment_gateway.html [new file with mode: 0644]
httemplate/edit/payment_gateway.html [new file with mode: 0644]
httemplate/index.html

index f5b2b96..f41245e 100644 (file)
--- a/FS/FS.pm
+++ b/FS/FS.pm
@@ -237,11 +237,11 @@ The main documentation is in httemplate/docs.
 =head1 SUPPORT
 
 A mailing list for users is available.  Send a blank message to
-<ivan-freeside-subscribe@sisd.com> to subscribe.
+<freeside-users-subscribe@sisd.com> to subscribe.
 
 A mailing list for developers is available.  It is intended to be lower volume
 and higher SNR than the users list.  Send a blank message to
-<ivan-freeside-devel-subscribe@sisd.com> to subscribe.
+<freeside-devel-subscribe@sisd.com> to subscribe.
 
 Commercial support is available; see
 <http://www.sisd.com/freeside/commercial.html>.
index f806e4f..9cff579 100644 (file)
@@ -1,9 +1,8 @@
 package FS::Record;
 
 use strict;
-use vars qw( $dbdef_file $dbdef $setup_hack $AUTOLOAD @ISA @EXPORT_OK $DEBUG
-             $me %dbdef_cache %virtual_fields_cache $nowarn_identical );
-use subs qw(reload_dbdef);
+use vars qw( $AUTOLOAD @ISA @EXPORT_OK $DEBUG
+             $me %virtual_fields_cache $nowarn_identical );
 use Exporter;
 use Carp qw(carp cluck croak confess);
 use File::CounterFile;
@@ -11,6 +10,7 @@ use Locale::Country;
 use DBI qw(:sql_types);
 use DBIx::DBSchema 0.25;
 use FS::UID qw(dbh getotaker datasrc driver_name);
+use FS::Schema qw(dbdef);
 use FS::SearchCache;
 use FS::Msgcat qw(gettext);
 use FS::Conf;
@@ -20,6 +20,8 @@ use FS::part_virtual_field;
 use Tie::IxHash;
 
 @ISA = qw(Exporter);
+
+#export dbdef for now... everything else expects to find it here
 @EXPORT_OK = qw(dbh fields hfields qsearch qsearchs dbdef jsearch);
 
 $DEBUG = 0;
@@ -33,13 +35,10 @@ my $rsa_loaded;
 my $rsa_encrypt;
 my $rsa_decrypt;
 
-#ask FS::UID to run this stuff for us later
-$FS::UID::callback{'FS::Record'} = sub { 
+FS::UID->install_callback( sub {
   $conf = new FS::Conf; 
   $File::CounterFile::DEFAULT_DIR = "/usr/local/etc/freeside/counters.". datasrc;
-  $dbdef_file = "/usr/local/etc/freeside/dbdef.". datasrc;
-  &reload_dbdef unless $setup_hack; #$setup_hack needed now?
-};
+} );
 
 =head1 NAME
 
@@ -48,7 +47,7 @@ FS::Record - Database record objects
 =head1 SYNOPSIS
 
     use FS::Record;
-    use FS::Record qw(dbh fields qsearch qsearchs dbdef);
+    use FS::Record qw(dbh fields qsearch qsearchs);
 
     $record = new FS::Record 'table', \%hash;
     $record = new FS::Record 'table', { 'column' => 'value', ... };
@@ -94,10 +93,6 @@ FS::Record - Database record objects
     $error = $record->ut_anything('column');
     $error = $record->ut_name('column');
 
-    $dbdef = reload_dbdef;
-    $dbdef = reload_dbdef "/non/standard/filename";
-    $dbdef = dbdef;
-
     $quoted_value = _quote($value,'table','field');
 
     #deprecated
@@ -218,7 +213,7 @@ sub qsearch {
   my $dbh = dbh;
 
   my $table = $cache ? $cache->table : $stable;
-  my $dbdef_table = $dbdef->table($table)
+  my $dbdef_table = dbdef->table($table)
     or die "No schema for table $table found - ".
            "do you need to create it or run dbdef-create?";
   my $pkey = $dbdef_table->primary_key;
@@ -254,7 +249,7 @@ sub qsearch {
       if ( ! defined( $record->{$_} ) || $record->{$_} eq '' ) {
         if ( $op eq '=' ) {
           if ( driver_name eq 'Pg' ) {
-            my $type = $dbdef->table($table)->column($column)->type;
+            my $type = dbdef->table($table)->column($column)->type;
             if ( $type =~ /(int|serial)/i ) {
               qq-( $column IS NULL )-;
             } else {
@@ -265,7 +260,7 @@ sub qsearch {
           }
         } elsif ( $op eq '!=' ) {
           if ( driver_name eq 'Pg' ) {
-            my $type = $dbdef->table($table)->column($column)->type;
+            my $type = dbdef->table($table)->column($column)->type;
             if ( $type =~ /(int|serial)/i ) {
               qq-( $column IS NOT NULL )-;
             } else {
@@ -335,7 +330,7 @@ sub qsearch {
     grep defined( $record->{$_} ) && $record->{$_} ne '', @real_fields
   ) {
     if ( $record->{$field} =~ /^\d+(\.\d+)?$/
-         && $dbdef->table($table)->column($field)->type =~ /(int|serial)/i
+         && dbdef->table($table)->column($field)->type =~ /(int|serial)/i
     ) {
       $sth->bind_param($bind++, $record->{$field}, { TYPE => SQL_INTEGER } );
     } else {
@@ -446,7 +441,7 @@ sub by_key {
   my $table = $class->table
     or croak "No table for $class found";
 
-  my $dbdef_table = $dbdef->table($table)
+  my $dbdef_table = dbdef->table($table)
     or die "No schema for table $table found - ".
            "do you need to create it or run dbdef-create?";
   my $pkey = $dbdef_table->primary_key
@@ -520,7 +515,7 @@ Returns the DBIx::DBSchema::Table object for the table.
 sub dbdef_table {
   my($self)=@_;
   my($table)=$self->table;
-  $dbdef->table($table);
+  dbdef->table($table);
 }
 
 =item get, getfield COLUMN
@@ -787,7 +782,7 @@ sub insert {
 
 
   my $h_sth;
-  if ( defined $dbdef->table('h_'. $table) ) {
+  if ( defined dbdef->table('h_'. $table) ) {
     my $h_statement = $self->_h_statement('insert');
     warn "[debug]$me $h_statement\n" if $DEBUG > 2;
     $h_sth = dbh->prepare($h_statement) or do {
@@ -848,7 +843,7 @@ sub delete {
   my $sth = dbh->prepare($statement) or return dbh->errstr;
 
   my $h_sth;
-  if ( defined $dbdef->table('h_'. $self->table) ) {
+  if ( defined dbdef->table('h_'. $self->table) ) {
     my $h_statement = $self->_h_statement('delete');
     warn "[debug]$me $h_statement\n" if $DEBUG > 2;
     $h_sth = dbh->prepare($h_statement) or return dbh->errstr;
@@ -992,7 +987,7 @@ sub replace {
   my $sth = dbh->prepare($statement) or return dbh->errstr;
 
   my $h_old_sth;
-  if ( defined $dbdef->table('h_'. $old->table) ) {
+  if ( defined dbdef->table('h_'. $old->table) ) {
     my $h_old_statement = $old->_h_statement('replace_old');
     warn "[debug]$me $h_old_statement\n" if $DEBUG > 2;
     $h_old_sth = dbh->prepare($h_old_statement) or return dbh->errstr;
@@ -1001,7 +996,7 @@ sub replace {
   }
 
   my $h_new_sth;
-  if ( defined $dbdef->table('h_'. $new->table) ) {
+  if ( defined dbdef->table('h_'. $new->table) ) {
     my $h_new_statement = $new->_h_statement('replace_new');
     warn "[debug]$me $h_new_statement\n" if $DEBUG > 2;
     $h_new_sth = dbh->prepare($h_new_statement) or return dbh->errstr;
@@ -1552,9 +1547,9 @@ sub virtual_fields {
   my $table;
   $table = $self->table or confess "virtual_fields called on non-table";
 
-  confess "Unknown table $table" unless $dbdef->table($table);
+  confess "Unknown table $table" unless dbdef->table($table);
 
-  return () unless $self->dbdef->table('part_virtual_field');
+  return () unless dbdef->table('part_virtual_field');
 
   unless ( $virtual_fields_cache{$table} ) {
     my $query = 'SELECT name from part_virtual_field ' .
@@ -1622,40 +1617,11 @@ fields() and other subroutines elsewhere in FS::Record.
 sub real_fields {
   my $table = shift;
 
-  my($table_obj) = $dbdef->table($table);
+  my($table_obj) = dbdef->table($table);
   confess "Unknown table $table" unless $table_obj;
   $table_obj->columns;
 }
 
-=item reload_dbdef([FILENAME])
-
-Load a database definition (see L<DBIx::DBSchema>), optionally from a
-non-default filename.  This command is executed at startup unless
-I<$FS::Record::setup_hack> is true.  Returns a DBIx::DBSchema object.
-
-=cut
-
-sub reload_dbdef {
-  my $file = shift || $dbdef_file;
-
-  unless ( exists $dbdef_cache{$file} ) {
-    warn "[debug]$me loading dbdef for $file\n" if $DEBUG;
-    $dbdef_cache{$file} = DBIx::DBSchema->load( $file )
-                            or die "can't load database schema from $file";
-  } else {
-    warn "[debug]$me re-using cached dbdef for $file\n" if $DEBUG;
-  }
-  $dbdef = $dbdef_cache{$file};
-}
-
-=item dbdef
-
-Returns the current database definition.  See L<DBIx::DBSchema>.
-
-=cut
-
-sub dbdef { $dbdef; }
-
 =item _quote VALUE, TABLE, COLUMN
 
 This is an internal function used to construct SQL statements.  It returns
@@ -1666,7 +1632,7 @@ type (see L<DBIx::DBSchema::Column>) does not end in `char' or `binary'.
 
 sub _quote {
   my($value, $table, $column) = @_;
-  my $column_obj = $dbdef->table($table)->column($column);
+  my $column_obj = dbdef->table($table)->column($column);
   my $column_type = $column_obj->type;
   my $nullable = $column_obj->null;
 
@@ -1701,7 +1667,7 @@ sub vfieldpart_hashref {
   my $self = shift;
   my $table = $self->table;
 
-  return {} unless $self->dbdef->table('part_virtual_field');
+  return {} unless dbdef->table('part_virtual_field');
 
   my $dbh = dbh;
   my $statement = "SELECT vfieldpart, name FROM part_virtual_field WHERE ".
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
new file mode 100644 (file)
index 0000000..09e16fe
--- /dev/null
@@ -0,0 +1,1247 @@
+package FS::Schema;
+
+use vars qw(@ISA @EXPORT_OK $DEBUG $setup_hack %dbdef_cache);
+use subs qw(reload_dbdef);
+use Exporter;
+use DBIx::DBSchema 0.25;
+use DBIx::DBSchema::Table;
+use DBIx::DBSchema::Column;
+use DBIx::DBSchema::ColGroup::Unique;
+use DBIx::DBSchema::ColGroup::Index;
+use FS::UID qw(datasrc);
+
+@ISA = qw(Exporter);
+@EXPORT_OK = qw( dbdef dbdef_dist reload_dbdef );
+
+$DEBUG = 0;
+$me = '[FS::Schema]';
+
+#ask FS::UID to run this stuff for us later
+FS::UID->install_callback( sub {
+  #$conf = new FS::Conf; 
+  &reload_dbdef("/usr/local/etc/freeside/dbdef.". datasrc)
+    unless $setup_hack; #$setup_hack needed now?
+} );
+
+=head1 NAME
+
+FS::Schema - Freeside database schema
+
+=head1 SYNOPSYS
+
+    use FS::Schema qw(dbdef dbdef_dist reload_dbdef);
+
+    $dbdef = reload_dbdef;
+    $dbdef = reload_dbdef "/non/standard/filename";
+    $dbdef = dbdef;
+    $dbdef_dist = dbdef_dist;
+
+=head1 DESCRIPTION
+
+This class represents the database schema.
+
+=head1 METHODS
+
+=over 4
+
+=item reload_dbdef([FILENAME])
+
+Load a database definition (see L<DBIx::DBSchema>), optionally from a
+non-default filename.  This command is executed at startup unless
+I<$FS::Schema::setup_hack> is true.  Returns a DBIx::DBSchema object.
+
+=cut
+
+sub reload_dbdef {
+  my $file = shift;
+
+  unless ( exists $dbdef_cache{$file} ) {
+    warn "[debug]$me loading dbdef for $file\n" if $DEBUG;
+    $dbdef_cache{$file} = DBIx::DBSchema->load( $file )
+                            or die "can't load database schema from $file";
+  } else {
+    warn "[debug]$me re-using cached dbdef for $file\n" if $DEBUG;
+  }
+  $dbdef = $dbdef_cache{$file};
+}
+
+=item dbdef
+
+Returns the current database definition (represents the current database,
+assuming it is up-to-date).  See L<DBIx::DBSchema>.
+
+=cut
+
+sub dbdef { $dbdef; }
+
+=item dbdef_dist [ OPTION => VALUE ... ]
+
+Returns the current canoical database definition as defined in this file.
+
+=cut
+
+sub dbdef_dist {
+
+  ###
+  # create a dbdef object from the old data structure
+  ###
+
+  my $tables_hashref = tables_hashref();
+
+  #turn it into objects
+  my $dbdef = new DBIx::DBSchema map {  
+    my @columns;
+    while (@{$tables_hashref->{$_}{'columns'}}) {
+      my($name, $type, $null, $length) =
+        splice @{$tables_hashref->{$_}{'columns'}}, 0, 4;
+      push @columns, new DBIx::DBSchema::Column ( $name,$type,$null,$length );
+    }
+    DBIx::DBSchema::Table->new(
+      $_,
+      $tables_hashref->{$_}{'primary_key'},
+      DBIx::DBSchema::ColGroup::Unique->new($tables_hashref->{$_}{'unique'}),
+      DBIx::DBSchema::ColGroup::Index->new($tables_hashref->{$_}{'index'}),
+      @columns,
+    );
+  } keys %$tables_hashref;
+
+  if ( $DEBUG ) {
+    warn "[debug]$me initial dbdef_dist created ($dbdef) with tables:\n";
+    warn "[debug]$me   $_\n" foreach $dbdef->tables;
+  }
+  
+  my $cust_main = $dbdef->table('cust_main');
+  #unless ($ship) { #remove ship_ from cust_main
+  #  $cust_main->delcolumn($_) foreach ( grep /^ship_/, $cust_main->columns );
+  #} else { #add indices
+    push @{$cust_main->index->lol_ref},
+      map { [ "ship_$_" ] } qw( last company daytime night fax );
+  #}
+  
+  #add radius attributes to svc_acct
+  #
+  #my($svc_acct)=$dbdef->table('svc_acct');
+  # 
+  #my($attribute);
+  #foreach $attribute (@attributes) {
+  #  $svc_acct->addcolumn ( new DBIx::DBSchema::Column (
+  #    'radius_'. $attribute,
+  #    'varchar',
+  #    'NULL',
+  #    $char_d,
+  #  ));
+  #}
+  # 
+  #foreach $attribute (@check_attributes) {
+  #  $svc_acct->addcolumn( new DBIx::DBSchema::Column (
+  #    'rc_'. $attribute,
+  #    'varchar',
+  #    'NULL',
+  #    $char_d,
+  #  ));
+  #}
+
+  #create history tables (false laziness w/create-history-tables)
+  foreach my $table (
+    grep { ! /^clientapi_session/ }
+    grep { ! /^h_/ }
+    $dbdef->tables
+  ) {
+    my $tableobj = $dbdef->table($table)
+      or die "unknown table $table";
+  
+    die "unique->lol_ref undefined for $table"
+      unless defined $tableobj->unique->lol_ref;
+    die "index->lol_ref undefined for $table"
+      unless defined $tableobj->index->lol_ref;
+  
+    my $h_tableobj = DBIx::DBSchema::Table->new( {
+      name        => "h_$table",
+      primary_key => 'historynum',
+      unique      => DBIx::DBSchema::ColGroup::Unique->new( [] ),
+      'index'     => DBIx::DBSchema::ColGroup::Index->new( [
+                       @{$tableobj->unique->lol_ref},
+                       @{$tableobj->index->lol_ref}
+                     ] ),
+      columns     => [
+                       DBIx::DBSchema::Column->new( {
+                         'name'    => 'historynum',
+                         'type'    => 'serial',
+                         'null'    => 'NOT NULL',
+                         'length'  => '',
+                         'default' => '',
+                         'local'   => '',
+                       } ),
+                       DBIx::DBSchema::Column->new( {
+                         'name'    => 'history_date',
+                         'type'    => 'int',
+                         'null'    => 'NULL',
+                         'length'  => '',
+                         'default' => '',
+                         'local'   => '',
+                       } ),
+                       DBIx::DBSchema::Column->new( {
+                         'name'    => 'history_user',
+                         'type'    => 'varchar',
+                         'null'    => 'NOT NULL',
+                         'length'  => '80',
+                         'default' => '',
+                         'local'   => '',
+                       } ),
+                       DBIx::DBSchema::Column->new( {
+                         'name'    => 'history_action',
+                         'type'    => 'varchar',
+                         'null'    => 'NOT NULL',
+                         'length'  => '80',
+                         'default' => '',
+                         'local'   => '',
+                       } ),
+                       map {
+                         my $column = $tableobj->column($_);
+  
+                         #clone so as to not disturb the original
+                         $column = DBIx::DBSchema::Column->new( {
+                           map { $_ => $column->$_() }
+                             qw( name type null length default local )
+                         } );
+  
+                         if ( $column->type eq 'serial' ) {
+                           $column->type('int');
+                           $column->null('NULL');
+                         }
+                         #$column->default('')
+                         #  if $column->default =~ /^nextval\(/i;
+                         #( my $local = $column->local ) =~ s/AUTO_INCREMENT//i;
+                         #$column->local($local);
+                         $column;
+                       } $tableobj->columns
+                     ],
+    } );
+    $dbdef->addtable($h_tableobj);
+  }
+
+  $dbdef;
+
+}
+
+sub tables_hashref {
+
+  my($char_d) = 80; #default maxlength for text fields
+
+  #my(@date_type)  = ( 'timestamp', '', ''     );
+  my(@date_type)  = ( 'int', 'NULL', ''     );
+  my(@perl_type) = ( 'text', 'NULL', ''  ); 
+  my @money_type = ( 'decimal',   '', '10,2' );
+
+  return {
+
+    'agent' => {
+      'columns' => [
+        'agentnum', 'serial',            '',     '',
+        'agent',    'varchar',           '',     $char_d,
+        'typenum',  'int',            '',     '',
+        'freq',     'int',       'NULL', '',
+        'prog',     @perl_type,
+        'disabled',     'char', 'NULL', 1,
+        'username', 'varchar',       'NULL',     $char_d,
+        '_password','varchar',       'NULL',     $char_d,
+      ],
+      'primary_key' => 'agentnum',
+      'unique' => [],
+      'index' => [ ['typenum'], ['disabled'] ],
+    },
+
+    'agent_type' => {
+      'columns' => [
+        'typenum',   'serial',  '', '',
+        'atype',     'varchar', '', $char_d,
+      ],
+      'primary_key' => 'typenum',
+      'unique' => [],
+      'index' => [],
+    },
+
+    'type_pkgs' => {
+      'columns' => [
+        'typepkgnum', 'serial', '', '',
+        'typenum',   'int',  '', '',
+        'pkgpart',   'int',  '', '',
+      ],
+      'primary_key' => 'typepkgnum',
+      'unique' => [ ['typenum', 'pkgpart'] ],
+      'index' => [ ['typenum'] ],
+    },
+
+    'cust_bill' => {
+      'columns' => [
+        'invnum',    'serial',  '', '',
+        'custnum',   'int',  '', '',
+        '_date',     @date_type,
+        'charged',   @money_type,
+        'printed',   'int',  '', '',
+        'closed',    'char', 'NULL', 1,
+      ],
+      'primary_key' => 'invnum',
+      'unique' => [],
+      'index' => [ ['custnum'], ['_date'] ],
+    },
+
+    'cust_bill_event' => {
+      'columns' => [
+        'eventnum',    'serial',  '', '',
+        'invnum',   'int',  '', '',
+        'eventpart',   'int',  '', '',
+        '_date',     @date_type,
+        'status', 'varchar', '', $char_d,
+        'statustext', 'text', 'NULL', '',
+      ],
+      'primary_key' => 'eventnum',
+      #no... there are retries now #'unique' => [ [ 'eventpart', 'invnum' ] ],
+      'unique' => [],
+      'index' => [ ['invnum'], ['status'] ],
+    },
+
+    'part_bill_event' => {
+      'columns' => [
+        'eventpart',    'serial',  '', '',
+        'payby',       'char',  '', 4,
+        'event',       'varchar',           '',     $char_d,
+        'eventcode',    @perl_type,
+        'seconds',     'int', 'NULL', '',
+        'weight',      'int', '', '',
+        'plan',       'varchar', 'NULL', $char_d,
+        'plandata',   'text', 'NULL', '',
+        'disabled',     'char', 'NULL', 1,
+      ],
+      'primary_key' => 'eventpart',
+      'unique' => [],
+      'index' => [ ['payby'], ['disabled'], ],
+    },
+
+    'cust_bill_pkg' => {
+      'columns' => [
+        'billpkgnum', 'serial', '', '',
+        'pkgnum',  'int', '', '',
+        'invnum',  'int', '', '',
+        'setup',   @money_type,
+        'recur',   @money_type,
+        'sdate',   @date_type,
+        'edate',   @date_type,
+        'itemdesc', 'varchar', 'NULL', $char_d,
+      ],
+      'primary_key' => 'billpkgnum',
+      'unique' => [],
+      'index' => [ ['invnum'] ],
+    },
+
+    'cust_bill_pkg_detail' => {
+      'columns' => [
+        'detailnum', 'serial', '', '',
+        'pkgnum',  'int', '', '',
+        'invnum',  'int', '', '',
+        'detail',  'varchar', '', $char_d,
+      ],
+      'primary_key' => 'detailnum',
+      'unique' => [],
+      'index' => [ [ 'pkgnum', 'invnum' ] ],
+    },
+
+    'cust_credit' => {
+      'columns' => [
+        'crednum',  'serial', '', '',
+        'custnum',  'int', '', '',
+        '_date',    @date_type,
+        'amount',   @money_type,
+        'otaker',   'varchar', '', 32,
+        'reason',   'text', 'NULL', '',
+        'closed',    'char', 'NULL', 1,
+      ],
+      'primary_key' => 'crednum',
+      'unique' => [],
+      'index' => [ ['custnum'] ],
+    },
+
+    'cust_credit_bill' => {
+      'columns' => [
+        'creditbillnum', 'serial', '', '',
+        'crednum',  'int', '', '',
+        'invnum',  'int', '', '',
+        '_date',    @date_type,
+        'amount',   @money_type,
+      ],
+      'primary_key' => 'creditbillnum',
+      'unique' => [],
+      'index' => [ ['crednum'], ['invnum'] ],
+    },
+
+    'cust_main' => {
+      'columns' => [
+        'custnum',  'serial',  '',     '',
+        'agentnum', 'int',  '',     '',
+#        'titlenum', 'int',  'NULL',   '',
+        'last',     'varchar', '',     $char_d,
+#        'middle',   'varchar', 'NULL', $char_d,
+        'first',    'varchar', '',     $char_d,
+        'ss',       'varchar', 'NULL', 11,
+        'company',  'varchar', 'NULL', $char_d,
+        'address1', 'varchar', '',     $char_d,
+        'address2', 'varchar', 'NULL', $char_d,
+        'city',     'varchar', '',     $char_d,
+        'county',   'varchar', 'NULL', $char_d,
+        'state',    'varchar', 'NULL', $char_d,
+        'zip',      'varchar', 'NULL', 10,
+        'country',  'char', '',     2,
+        'daytime',  'varchar', 'NULL', 20,
+        'night',    'varchar', 'NULL', 20,
+        'fax',      'varchar', 'NULL', 12,
+        'ship_last',     'varchar', 'NULL', $char_d,
+#        'ship_middle',   'varchar', 'NULL', $char_d,
+        'ship_first',    'varchar', 'NULL', $char_d,
+        'ship_company',  'varchar', 'NULL', $char_d,
+        'ship_address1', 'varchar', 'NULL', $char_d,
+        'ship_address2', 'varchar', 'NULL', $char_d,
+        'ship_city',     'varchar', 'NULL', $char_d,
+        'ship_county',   'varchar', 'NULL', $char_d,
+        'ship_state',    'varchar', 'NULL', $char_d,
+        'ship_zip',      'varchar', 'NULL', 10,
+        'ship_country',  'char', 'NULL', 2,
+        'ship_daytime',  'varchar', 'NULL', 20,
+        'ship_night',    'varchar', 'NULL', 20,
+        'ship_fax',      'varchar', 'NULL', 12,
+        'payby',    'char', '',     4,
+        'payinfo',  'varchar', 'NULL', 512,
+        'paycvv',   'varchar', 'NULL', 512,
+       'paymask', 'varchar', 'NULL', $char_d,
+        #'paydate',  @date_type,
+        'paydate',  'varchar', 'NULL', 10,
+        'paystart_month', 'int', 'NULL', '',
+        'paystart_year',  'int', 'NULL', '',
+        'payissue', 'varchar', 'NULL', 2,
+        'payname',  'varchar', 'NULL', $char_d,
+        'payip',    'varchar', 'NULL', 15,
+        'tax',      'char', 'NULL', 1,
+        'otaker',   'varchar', '',    32,
+        'refnum',   'int',  '',     '',
+        'referral_custnum', 'int',  'NULL', '',
+        'comments', 'text', 'NULL', '',
+      ],
+      'primary_key' => 'custnum',
+      'unique' => [],
+      #'index' => [ ['last'], ['company'] ],
+      'index' => [ ['last'], [ 'company' ], [ 'referral_custnum' ],
+                   [ 'daytime' ], [ 'night' ], [ 'fax' ], [ 'refnum' ],
+                 ],
+    },
+
+    'cust_main_invoice' => {
+      'columns' => [
+        'destnum',  'serial',  '',     '',
+        'custnum',  'int',  '',     '',
+        'dest',     'varchar', '',  $char_d,
+      ],
+      'primary_key' => 'destnum',
+      'unique' => [],
+      'index' => [ ['custnum'], ],
+    },
+
+    'cust_main_county' => { #county+state+country are checked off the
+                            #cust_main_county for validation and to provide
+                            # a tax rate.
+      'columns' => [
+        'taxnum',   'serial',   '',    '',
+        'state',    'varchar',  'NULL',    $char_d,
+        'county',   'varchar',  'NULL',    $char_d,
+        'country',  'char',  '', 2, 
+        'taxclass',   'varchar', 'NULL', $char_d,
+        'exempt_amount', @money_type,
+        'tax',      'real',  '',    '', #tax %
+        'taxname',  'varchar',  'NULL',    $char_d,
+        'setuptax',  'char', 'NULL', 1, # Y = setup tax exempt
+        'recurtax',  'char', 'NULL', 1, # Y = recur tax exempt
+      ],
+      'primary_key' => 'taxnum',
+      'unique' => [],
+  #    'unique' => [ ['taxnum'], ['state', 'county'] ],
+      'index' => [],
+    },
+
+    'cust_pay' => {
+      'columns' => [
+        'paynum',   'serial',    '',   '',
+        #now cust_bill_pay #'invnum',   'int',    '',   '',
+        'custnum',  'int',    '',   '',
+        'paid',     @money_type,
+        '_date',    @date_type,
+        'payby',    'char',   '',     4, # CARD/BILL/COMP, should be index into
+                                         # payment type table.
+        'payinfo',  'varchar',   'NULL', $char_d,  #see cust_main above
+        'paybatch', 'varchar',   'NULL', $char_d, #for auditing purposes.
+        'closed',    'char', 'NULL', 1,
+      ],
+      'primary_key' => 'paynum',
+      'unique' => [],
+      'index' => [ [ 'custnum' ], [ 'paybatch' ], [ 'payby' ], [ '_date' ] ],
+    },
+
+    'cust_pay_void' => {
+      'columns' => [
+        'paynum',    'int',    '',   '',
+        'custnum',   'int',    '',   '',
+        'paid',      @money_type,
+        '_date',     @date_type,
+        'payby',     'char',   '',     4, # CARD/BILL/COMP, should be index into
+                                          # payment type table.
+        'payinfo',   'varchar',   'NULL', $char_d,  #see cust_main above
+        'paybatch',  'varchar',   'NULL', $char_d, #for auditing purposes.
+        'closed',    'char', 'NULL', 1,
+        'void_date', @date_type,
+        'reason',    'varchar',   'NULL', $char_d,
+        'otaker',   'varchar', '', 32,
+      ],
+      'primary_key' => 'paynum',
+      'unique' => [],
+      'index' => [ [ 'custnum' ] ],
+    },
+
+    'cust_bill_pay' => {
+      'columns' => [
+        'billpaynum', 'serial',     '',   '',
+        'invnum',  'int',     '',   '',
+        'paynum',  'int',     '',   '',
+        'amount',  @money_type,
+        '_date',   @date_type
+      ],
+      'primary_key' => 'billpaynum',
+      'unique' => [],
+      'index' => [ [ 'paynum' ], [ 'invnum' ] ],
+    },
+
+    'cust_pay_batch' => { #what's this used for again?  list of customers
+                          #in current CARD batch? (necessarily CARD?)
+      'columns' => [
+        'paybatchnum',   'serial',    '',   '',
+        'invnum',   'int',    '',   '',
+        'custnum',   'int',    '',   '',
+        'last',     'varchar', '',     $char_d,
+        'first',    'varchar', '',     $char_d,
+        'address1', 'varchar', '',     $char_d,
+        'address2', 'varchar', 'NULL', $char_d,
+        'city',     'varchar', '',     $char_d,
+        'state',    'varchar', 'NULL', $char_d,
+        'zip',      'varchar', 'NULL', 10,
+        'country',  'char', '',     2,
+#        'trancode', 'int', '', '',
+        'cardnum',  'varchar', '',     16,
+        #'exp',      @date_type,
+        'exp',      'varchar', '',     11,
+        'payname',  'varchar', 'NULL', $char_d,
+        'amount',   @money_type,
+      ],
+      'primary_key' => 'paybatchnum',
+      'unique' => [],
+      'index' => [ ['invnum'], ['custnum'] ],
+    },
+
+    'cust_pkg' => {
+      'columns' => [
+        'pkgnum',    'serial',    '',   '',
+        'custnum',   'int',    '',   '',
+        'pkgpart',   'int',    '',   '',
+        'otaker',    'varchar', '', 32,
+        'setup',     @date_type,
+        'bill',      @date_type,
+        'last_bill', @date_type,
+        'susp',      @date_type,
+        'cancel',    @date_type,
+        'expire',    @date_type,
+        'manual_flag', 'char', 'NULL', 1,
+      ],
+      'primary_key' => 'pkgnum',
+      'unique' => [],
+      'index' => [ ['custnum'] ],
+    },
+
+    'cust_refund' => {
+      'columns' => [
+        'refundnum',    'serial',    '',   '',
+        #now cust_credit_refund #'crednum',      'int',    '',   '',
+        'custnum',  'int',    '',   '',
+        '_date',        @date_type,
+        'refund',       @money_type,
+        'otaker',       'varchar',   '',   32,
+        'reason',       'varchar',   '',   $char_d,
+        'payby',        'char',   '',     4, # CARD/BILL/COMP, should be index
+                                             # into payment type table.
+        'payinfo',      'varchar',   'NULL', $char_d,  #see cust_main above
+        'paybatch',     'varchar',   'NULL', $char_d,
+        'closed',    'char', 'NULL', 1,
+      ],
+      'primary_key' => 'refundnum',
+      'unique' => [],
+      'index' => [],
+    },
+
+    'cust_credit_refund' => {
+      'columns' => [
+        'creditrefundnum', 'serial',     '',   '',
+        'crednum',  'int',     '',   '',
+        'refundnum',  'int',     '',   '',
+        'amount',  @money_type,
+        '_date',   @date_type
+      ],
+      'primary_key' => 'creditrefundnum',
+      'unique' => [],
+      'index' => [ [ 'crednum', 'refundnum' ] ],
+    },
+
+
+    'cust_svc' => {
+      'columns' => [
+        'svcnum',    'serial',    '',   '',
+        'pkgnum',    'int',    'NULL',   '',
+        'svcpart',   'int',    '',   '',
+      ],
+      'primary_key' => 'svcnum',
+      'unique' => [],
+      'index' => [ ['svcnum'], ['pkgnum'], ['svcpart'] ],
+    },
+
+    'part_pkg' => {
+      'columns' => [
+        'pkgpart',    'serial',    '',   '',
+        'pkg',        'varchar',   '',   $char_d,
+        'comment',    'varchar',   '',   $char_d,
+        'promo_code', 'varchar', 'NULL', $char_d,
+        'setup',      @perl_type,
+        'freq',       'varchar',   '',   $char_d,  #billing frequency
+        'recur',      @perl_type,
+        'setuptax',  'char', 'NULL', 1,
+        'recurtax',  'char', 'NULL', 1,
+        'plan',       'varchar', 'NULL', $char_d,
+        'plandata',   'text', 'NULL', '',
+        'disabled',   'char', 'NULL', 1,
+        'taxclass',   'varchar', 'NULL', $char_d,
+      ],
+      'primary_key' => 'pkgpart',
+      'unique' => [],
+      'index' => [ [ 'promo_code' ], [ 'disabled' ] ],
+    },
+
+#    'part_title' => {
+#      'columns' => [
+#        'titlenum',   'int',    '',   '',
+#        'title',      'varchar',   '',   $char_d,
+#      ],
+#      'primary_key' => 'titlenum',
+#      'unique' => [ [] ],
+#      'index' => [ [] ],
+#    },
+
+    'pkg_svc' => {
+      'columns' => [
+        'pkgsvcnum',  'serial', '',  '',
+        'pkgpart',    'int',    '',   '',
+        'svcpart',    'int',    '',   '',
+        'quantity',   'int',    '',   '',
+        'primary_svc','char', 'NULL',  1,
+      ],
+      'primary_key' => 'pkgsvcnum',
+      'unique' => [ ['pkgpart', 'svcpart'] ],
+      'index' => [ ['pkgpart'] ],
+    },
+
+    'part_referral' => {
+      'columns' => [
+        'refnum',   'serial',    '',   '',
+        'referral', 'varchar',   '',   $char_d,
+        'disabled',     'char', 'NULL', 1,
+      ],
+      'primary_key' => 'refnum',
+      'unique' => [],
+      'index' => [ ['disabled'] ],
+    },
+
+    'part_svc' => {
+      'columns' => [
+        'svcpart',    'serial',    '',   '',
+        'svc',        'varchar',   '',   $char_d,
+        'svcdb',      'varchar',   '',   $char_d,
+        'disabled',   'char',  'NULL',   1,
+      ],
+      'primary_key' => 'svcpart',
+      'unique' => [],
+      'index' => [ [ 'disabled' ] ],
+    },
+
+    'part_svc_column' => {
+      'columns' => [
+        'columnnum',   'serial',         '', '',
+        'svcpart',     'int',         '', '',
+        'columnname',  'varchar',     '', 64,
+        'columnvalue', 'varchar', 'NULL', $char_d,
+        'columnflag',  'char',    'NULL', 1, 
+      ],
+      'primary_key' => 'columnnum',
+      'unique' => [ [ 'svcpart', 'columnname' ] ],
+      'index' => [ [ 'svcpart' ] ],
+    },
+
+    #(this should be renamed to part_pop)
+    'svc_acct_pop' => {
+      'columns' => [
+        'popnum',    'serial',    '',   '',
+        'city',      'varchar',   '',   $char_d,
+        'state',     'varchar',   '',   $char_d,
+        'ac',        'char',   '',   3,
+        'exch',      'char',   '',   3,
+        'loc',       'char',   'NULL',   4, #NULL for legacy purposes
+      ],
+      'primary_key' => 'popnum',
+      'unique' => [],
+      'index' => [ [ 'state' ] ],
+    },
+
+    'part_pop_local' => {
+      'columns' => [
+        'localnum',  'serial',     '',     '',
+        'popnum',    'int',     '',     '',
+        'city',      'varchar', 'NULL', $char_d,
+        'state',     'char',    'NULL', 2,
+        'npa',       'char',    '',     3,
+        'nxx',       'char',    '',     3,
+      ],
+      'primary_key' => 'localnum',
+      'unique' => [],
+      'index' => [ [ 'npa', 'nxx' ], [ 'popnum' ] ],
+    },
+
+    'svc_acct' => {
+      'columns' => [
+        'svcnum',    'int',    '',   '',
+        'username',  'varchar',   '',   $username_len, #unique (& remove dup code)
+        '_password', 'varchar',   '',   72, #13 for encryped pw's plus ' *SUSPENDED* (md5 passwords can be 34, blowfish 60)
+        'sec_phrase', 'varchar',  'NULL',   $char_d,
+        'popnum',    'int',    'NULL',   '',
+        'uid',       'int', 'NULL',   '',
+        'gid',       'int', 'NULL',   '',
+        'finger',    'varchar',   'NULL',   $char_d,
+        'dir',       'varchar',   'NULL',   $char_d,
+        'shell',     'varchar',   'NULL',   $char_d,
+        'quota',     'varchar',   'NULL',   $char_d,
+        'slipip',    'varchar',   'NULL',   15, #four TINYINTs, bah.
+        'seconds',   'int', 'NULL',   '', #uhhhh
+        'domsvc',    'int', '',   '',
+      ],
+      'primary_key' => 'svcnum',
+      #'unique' => [ [ 'username', 'domsvc' ] ],
+      'unique' => [],
+      'index' => [ ['username'], ['domsvc'] ],
+    },
+
+    #'svc_charge' => {
+    #  'columns' => [
+    #    'svcnum',    'int',    '',   '',
+    #    'amount',    @money_type,
+    #  ],
+    #  'primary_key' => 'svcnum',
+    #  'unique' => [ [] ],
+    #  'index' => [ [] ],
+    #},
+
+    'svc_domain' => {
+      'columns' => [
+        'svcnum',    'int',    '',   '',
+        'domain',    'varchar',    '',   $char_d,
+        'catchall',  'int', 'NULL',    '',
+      ],
+      'primary_key' => 'svcnum',
+      'unique' => [ ['domain'] ],
+      'index' => [],
+    },
+
+    'domain_record' => {
+      'columns' => [
+        'recnum',    'serial',     '',  '',
+        'svcnum',    'int',     '',  '',
+        #'reczone',   'varchar', '',  $char_d,
+        'reczone',   'varchar', '',  255,
+        'recaf',     'char',    '',  2,
+        'rectype',   'varchar',    '',  5,
+        #'recdata',   'varchar', '',  $char_d,
+        'recdata',   'varchar', '',  255,
+      ],
+      'primary_key' => 'recnum',
+      'unique'      => [],
+      'index'       => [ ['svcnum'] ],
+    },
+
+    'svc_forward' => {
+      'columns' => [
+        'svcnum',   'int',            '',   '',
+        'srcsvc',   'int',        'NULL',   '',
+        'src',      'varchar',    'NULL',  255,
+        'dstsvc',   'int',        'NULL',   '',
+        'dst',      'varchar',    'NULL',  255,
+      ],
+      'primary_key' => 'svcnum',
+      'unique'      => [],
+      'index'       => [ ['srcsvc'], ['dstsvc'] ],
+    },
+
+    'svc_www' => {
+      'columns' => [
+        'svcnum',   'int',    '',  '',
+        'recnum',   'int',    '',  '',
+        'usersvc',  'int',    '',  '',
+      ],
+      'primary_key' => 'svcnum',
+      'unique'      => [],
+      'index'       => [],
+    },
+
+    #'svc_wo' => {
+    #  'columns' => [
+    #    'svcnum',    'int',    '',   '',
+    #    'svcnum',    'int',    '',   '',
+    #    'svcnum',    'int',    '',   '',
+    #    'worker',    'varchar',   '',   $char_d,
+    #    '_date',     @date_type,
+    #  ],
+    #  'primary_key' => 'svcnum',
+    #  'unique' => [ [] ],
+    #  'index' => [ [] ],
+    #},
+
+    'prepay_credit' => {
+      'columns' => [
+        'prepaynum',   'serial',     '',   '',
+        'identifier',  'varchar', '', $char_d,
+        'amount',      @money_type,
+        'seconds',     'int',     'NULL', '',
+        'agentnum',    'int',     'NULL', '',
+      ],
+      'primary_key' => 'prepaynum',
+      'unique'      => [ ['identifier'] ],
+      'index'       => [],
+    },
+
+    'port' => {
+      'columns' => [
+        'portnum',  'serial',     '',   '',
+        'ip',       'varchar', 'NULL', 15,
+        'nasport',  'int',     'NULL', '',
+        'nasnum',   'int',     '',   '',
+      ],
+      'primary_key' => 'portnum',
+      'unique'      => [],
+      'index'       => [],
+    },
+
+    'nas' => {
+      'columns' => [
+        'nasnum',   'serial',     '',    '',
+        'nas',      'varchar', '',    $char_d,
+        'nasip',    'varchar', '',    15,
+        'nasfqdn',  'varchar', '',    $char_d,
+        'last',     'int',     '',    '',
+      ],
+      'primary_key' => 'nasnum',
+      'unique'      => [ [ 'nas' ], [ 'nasip' ] ],
+      'index'       => [ [ 'last' ] ],
+    },
+
+    'session' => {
+      'columns' => [
+        'sessionnum', 'serial',       '',   '',
+        'portnum',    'int',       '',   '',
+        'svcnum',     'int',       '',   '',
+        'login',      @date_type,
+        'logout',     @date_type,
+      ],
+      'primary_key' => 'sessionnum',
+      'unique'      => [],
+      'index'       => [ [ 'portnum' ] ],
+    },
+
+    'queue' => {
+      'columns' => [
+        'jobnum', 'serial', '', '',
+        'job', 'text', '', '',
+        '_date', 'int', '', '',
+        'status', 'varchar', '', $char_d,
+        'statustext', 'text', 'NULL', '',
+        'svcnum', 'int', 'NULL', '',
+      ],
+      'primary_key' => 'jobnum',
+      'unique'      => [],
+      'index'       => [ [ 'svcnum' ], [ 'status' ] ],
+    },
+
+    'queue_arg' => {
+      'columns' => [
+        'argnum', 'serial', '', '',
+        'jobnum', 'int', '', '',
+        'arg', 'text', 'NULL', '',
+      ],
+      'primary_key' => 'argnum',
+      'unique'      => [],
+      'index'       => [ [ 'jobnum' ] ],
+    },
+
+    'queue_depend' => {
+      'columns' => [
+        'dependnum', 'serial', '', '',
+        'jobnum', 'int', '', '',
+        'depend_jobnum', 'int', '', '',
+      ],
+      'primary_key' => 'dependnum',
+      'unique'      => [],
+      'index'       => [ [ 'jobnum' ], [ 'depend_jobnum' ] ],
+    },
+
+    'export_svc' => {
+      'columns' => [
+        'exportsvcnum' => 'serial', '', '',
+        'exportnum'    => 'int', '', '',
+        'svcpart'      => 'int', '', '',
+      ],
+      'primary_key' => 'exportsvcnum',
+      'unique'      => [ [ 'exportnum', 'svcpart' ] ],
+      'index'       => [ [ 'exportnum' ], [ 'svcpart' ] ],
+    },
+
+    'part_export' => {
+      'columns' => [
+        'exportnum', 'serial', '', '',
+        #'svcpart',   'int', '', '',
+        'machine', 'varchar', '', $char_d,
+        'exporttype', 'varchar', '', $char_d,
+        'nodomain',     'char', 'NULL', 1,
+      ],
+      'primary_key' => 'exportnum',
+      'unique'      => [],
+      'index'       => [ [ 'machine' ], [ 'exporttype' ] ],
+    },
+
+    'part_export_option' => {
+      'columns' => [
+        'optionnum', 'serial', '', '',
+        'exportnum', 'int', '', '',
+        'optionname', 'varchar', '', $char_d,
+        'optionvalue', 'text', 'NULL', '',
+      ],
+      'primary_key' => 'optionnum',
+      'unique'      => [],
+      'index'       => [ [ 'exportnum' ], [ 'optionname' ] ],
+    },
+
+    'radius_usergroup' => {
+      'columns' => [
+        'usergroupnum', 'serial', '', '',
+        'svcnum',       'int', '', '',
+        'groupname',    'varchar', '', $char_d,
+      ],
+      'primary_key' => 'usergroupnum',
+      'unique'      => [],
+      'index'       => [ [ 'svcnum' ], [ 'groupname' ] ],
+    },
+
+    'msgcat' => {
+      'columns' => [
+        'msgnum', 'serial', '', '',
+        'msgcode', 'varchar', '', $char_d,
+        'locale', 'varchar', '', 16,
+        'msg', 'text', '', '',
+      ],
+      'primary_key' => 'msgnum',
+      'unique'      => [ [ 'msgcode', 'locale' ] ],
+      'index'       => [],
+    },
+
+    'cust_tax_exempt' => {
+      'columns' => [
+        'exemptnum', 'serial', '', '',
+        'custnum',   'int', '', '',
+        'taxnum',    'int', '', '',
+        'year',      'int', '', '',
+        'month',     'int', '', '',
+        'amount',   @money_type,
+      ],
+      'primary_key' => 'exemptnum',
+      'unique'      => [ [ 'custnum', 'taxnum', 'year', 'month' ] ],
+      'index'       => [],
+    },
+
+    'router' => {
+      'columns' => [
+        'routernum', 'serial', '', '',
+        'routername', 'varchar', '', $char_d,
+        'svcnum', 'int', 'NULL', '',
+      ],
+      'primary_key' => 'routernum',
+      'unique'      => [],
+      'index'       => [],
+    },
+
+    'part_svc_router' => {
+      'columns' => [
+        'svcrouternum', 'serial', '', '',
+        'svcpart', 'int', '', '',
+       'routernum', 'int', '', '',
+      ],
+      'primary_key' => 'svcrouternum',
+      'unique'      => [],
+      'index'       => [],
+    },
+
+    'addr_block' => {
+      'columns' => [
+        'blocknum', 'serial', '', '',
+       'routernum', 'int', '', '',
+        'ip_gateway', 'varchar', '', 15,
+        'ip_netmask', 'int', '', '',
+      ],
+      'primary_key' => 'blocknum',
+      'unique'      => [ [ 'blocknum', 'routernum' ] ],
+      'index'       => [],
+    },
+
+    'svc_broadband' => {
+      'columns' => [
+        'svcnum', 'int', '', '',
+        'blocknum', 'int', '', '',
+        'speed_up', 'int', '', '',
+        'speed_down', 'int', '', '',
+        'ip_addr', 'varchar', '', 15,
+      ],
+      'primary_key' => 'svcnum',
+      'unique'      => [],
+      'index'       => [],
+    },
+
+    'part_virtual_field' => {
+      'columns' => [
+        'vfieldpart', 'int', '', '',
+        'dbtable', 'varchar', '', 32,
+        'name', 'varchar', '', 32,
+        'check_block', 'text', 'NULL', '',
+        'length', 'int', 'NULL', '',
+        'list_source', 'text', 'NULL', '',
+        'label', 'varchar', 'NULL', 80,
+      ],
+      'primary_key' => 'vfieldpart',
+      'unique' => [],
+      'index' => [],
+    },
+
+    'virtual_field' => {
+      'columns' => [
+        'vfieldnum', 'serial', '', '',
+        'recnum', 'int', '', '',
+        'vfieldpart', 'int', '', '',
+        'value', 'varchar', '', 128,
+      ],
+      'primary_key' => 'vfieldnum',
+      'unique' => [ [ 'vfieldpart', 'recnum' ] ],
+      'index' => [],
+    },
+
+    'acct_snarf' => {
+      'columns' => [
+        'snarfnum',  'int', '', '',
+        'svcnum',    'int', '', '',
+        'machine',   'varchar', '', 255,
+        'protocol',  'varchar', '', $char_d,
+        'username',  'varchar', '', $char_d,
+        '_password', 'varchar', '', $char_d,
+      ],
+      'primary_key' => 'snarfnum',
+      'unique' => [],
+      'index'  => [ [ 'svcnum' ] ],
+    },
+
+    'svc_external' => {
+      'columns' => [
+        'svcnum', 'int', '', '',
+        'id',     'int', 'NULL', '',
+        'title',  'varchar', 'NULL', $char_d,
+      ],
+      'primary_key' => 'svcnum',
+      'unique'      => [],
+      'index'       => [],
+    },
+
+    'cust_pay_refund' => {
+      'columns' => [
+        'payrefundnum', 'serial', '', '',
+        'paynum',  'int', '', '',
+        'refundnum',  'int', '', '',
+        '_date',    @date_type,
+        'amount',   @money_type,
+      ],
+      'primary_key' => 'payrefundnum',
+      'unique' => [],
+      'index' => [ ['paynum'], ['refundnum'] ],
+    },
+
+    'part_pkg_option' => {
+      'columns' => [
+        'optionnum', 'serial', '', '',
+        'pkgpart', 'int', '', '',
+        'optionname', 'varchar', '', $char_d,
+        'optionvalue', 'text', 'NULL', '',
+      ],
+      'primary_key' => 'optionnum',
+      'unique'      => [],
+      'index'       => [ [ 'pkgpart' ], [ 'optionname' ] ],
+    },
+
+    'rate' => {
+      'columns' => [
+        'ratenum',  'serial', '', '',
+        'ratename', 'varchar', '', $char_d,
+      ],
+      'primary_key' => 'ratenum',
+      'unique'      => [],
+      'index'       => [],
+    },
+
+    'rate_detail' => {
+      'columns' => [
+        'ratedetailnum',   'serial', '', '',
+        'ratenum',         'int',     '', '',
+        'orig_regionnum',  'int', 'NULL', '',
+        'dest_regionnum',  'int',     '', '',
+        'min_included',    'int',     '', '',
+        'min_charge',      @money_type,
+        'sec_granularity', 'int',     '', '',
+        #time period (link to table of periods)?
+      ],
+      'primary_key' => 'ratedetailnum',
+      'unique'      => [ [ 'ratenum', 'orig_regionnum', 'dest_regionnum' ] ],
+      'index'       => [],
+    },
+
+    'rate_region' => {
+      'columns' => [
+        'regionnum',   'serial',      '', '',
+        'regionname',  'varchar',     '', $char_d,
+      ],
+      'primary_key' => 'regionnum',
+      'unique'      => [],
+      'index'       => [],
+    },
+
+    'rate_prefix' => {
+      'columns' => [
+        'prefixnum',   'serial',    '', '',
+        'regionnum',   'int',       '', '',,
+        'countrycode', 'varchar',     '', 3,
+        'npa',         'varchar', 'NULL', 6,
+        'nxx',         'varchar', 'NULL', 3,
+      ],
+      'primary_key' => 'prefixnum',
+      'unique'      => [],
+      'index'       => [ [ 'countrycode' ], [ 'regionnum' ] ],
+    },
+
+    'reg_code' => {
+      'columns' => [
+        'codenum',   'serial',    '', '',
+        'code',      'varchar',   '', $char_d,
+        'agentnum',  'int',       '', '',
+      ],
+      'primary_key' => 'codenum',
+      'unique'      => [ [ 'agentnum', 'code' ] ],
+      'index'       => [ [ 'agentnum' ] ],
+    },
+
+    'reg_code_pkg' => {
+      'columns' => [
+        'codepkgnum', 'serial', '', '',
+        'codenum',   'int',    '', '',
+        'pkgpart',   'int',    '', '',
+      ],
+      'primary_key' => 'codepkgnum',
+      'unique'      => [ [ 'codenum', 'pkgpart' ] ],
+      'index'       => [ [ 'codenum' ] ],
+    },
+
+    'clientapi_session' => {
+      'columns' => [
+        'sessionnum',  'serial',  '', '',
+        'sessionid',  'varchar',  '', $char_d,
+        'namespace',  'varchar',  '', $char_d,
+      ],
+      'primary_key' => 'sessionnum',
+      'unique'      => [ [ 'sessionid', 'namespace' ] ],
+      'index'       => [],
+    },
+
+    'clientapi_session_field' => {
+      'columns' => [
+        'fieldnum',    'serial',     '', '',
+        'sessionnum',     'int',     '', '',
+        'fieldname',  'varchar',     '', $char_d,
+        'fieldvalue',    'text', 'NULL', '',
+      ],
+      'primary_key' => 'fieldnum',
+      'unique'      => [ [ 'sessionnum', 'fieldname' ] ],
+      'index'       => [],
+    },
+
+    'payment_gateway' => {
+      'columns' => [
+        'gatewaynum',       'serial',   '',     '',
+        'gateway_module',   'varchar',  '',     $char_d,
+        'gateway_username', 'varchar',  'NULL', $char_d,
+        'gateway_password', 'varchar',  'NULL', $char_d,
+        'gateway_action',   'varchar',  'NULL', $char_d,
+        'disabled',   'char',  'NULL',   1,
+      ],
+      'primary_key' => 'gatewaynum',
+      'unique' => [],
+      'index'  => [ [ 'disabled' ] ],
+    },
+
+    'payment_gateway_option' => {
+      'columns' => [
+        'optionnum',   'serial',  '',     '',
+        'gatewaynum',  'int',     '',     '',
+        'optionname',  'varchar', '',     $char_d,
+        'optionvalue', 'text',    'NULL', '',
+      ],
+      'primary_key' => 'optionnum',
+      'unique'      => [],
+      'index'       => [ [ 'gatewaynum' ], [ 'optionname' ] ],
+    },
+
+    'agent_payment_gateway' => {
+      'columns' => [
+        'agentgatewaynum', 'serial', '', '',
+        'agentnum',        'int', '', '',
+        'gatewaynum',      'int', '', '',
+        'cardtype',        'varchar', 'NULL', $char_d,
+        'taxclass',        'varchar', 'NULL', $char_d,
+      ],
+      'primary_key' => 'agentgatewaynum',
+      'unique'      => [],
+      'index'       => [ [ 'agentnum', 'cardtype' ], ],
+    },
+
+  };
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<DBIx::DBSchema>
+
+=cut
+
+1;
+
index 5d68ddd..a1df560 100644 (file)
@@ -256,13 +256,18 @@ sub paymask {
   return $paymask;
 }
 
+=item paydate - expiration date, mm/yyyy, m/yyyy, mm/yy or m/yy
 
+=item paystart_month - start date month (maestro/solo cards only)
 
+=item paystart_year - start date year (maestro/solo cards only)
 
-=item paydate - expiration date, mm/yyyy, m/yyyy, mm/yy or m/yy
+=item payissue - issue number (maestro/solo cards only)
 
 =item payname - name on card or billing name
 
+=item payip - IP address from which payment information was received
+
 =item tax - tax exempt, empty or `Y'
 
 =item otaker - order taker (assigned automatically, see L<FS::UID>)
@@ -1099,6 +1104,19 @@ sub check {
   $self->payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP|PREPAY)$/
     or return "Illegal payby: ". $self->payby;
 
+  $error =    $self->ut_numbern('paystart_month')
+           || $self->ut_numbern('paystart_year')
+           || $self->ut_numbern('payissue')
+  ;
+  return $error if $error;
+
+  if ( $self->payip eq '' ) {
+    $self->payip('');
+  } else {
+    $error = $self->ut_ip('payip');
+    return $error if $error;
+  }
+
   # If it is encrypted and the private key is not availaible then we can't
   # check the credit card.
 
@@ -1110,7 +1128,7 @@ sub check {
 
   $self->payby($1);
 
-  if ( $check_payinfo && ($self->payby eq 'CARD' || $self->payby eq 'DCRD')) {
+  if ( $check_payinfo && $self->payby =~ /^(CARD|DCRD)$/ ) {
 
     my $payinfo = $self->payinfo;
     $payinfo =~ s/\D//g;
@@ -1138,7 +1156,31 @@ sub check {
       }
     }
 
-  } elsif ($check_payinfo && ( $self->payby eq 'CHEK' || $self->payby eq 'DCHK' )) {
+    my $cardtype = cardtype($payinfo);
+    if ( $cardtype =~ /^(Switch|Solo)$/i ) {
+
+      return "Start date or issue number is required for $cardtype cards"
+        unless $self->paystart_month && $self->paystart_year or $self->payissue;
+
+      return "Start month must be between 1 and 12"
+        if $self->paystart_month
+           and $self->paystart_month < 1 || $self->paystart_month > 12;
+
+      return "Start year must be 1990 or later"
+        if $self->paystart_year
+           and $self->paystart_year < 1990;
+
+      return "Issue number must be beween 1 and 99"
+        if $self->payissue
+          and $self->payissue < 1 || $self->payissue > 99;
+
+    } else {
+      $self->paystart_month('');
+      $self->paystart_year('');
+      $self->payissue('');
+    }
+
+  } elsif ( $check_payinfo && $self->payby =~ /^(CHEK|DCHK)$/ ) {
 
     my $payinfo = $self->payinfo;
     $payinfo =~ s/[^\d\@]//g;
@@ -2036,25 +2078,77 @@ sub realtime_bop {
 
   $options{'description'} ||= 'Internet services';
 
-  #pre-requisites
-  die "Real-time processing not enabled\n"
-    unless $conf->exists('business-onlinepayment');
   eval "use Business::OnlinePayment";  
   die $@ if $@;
 
-  #load up config
-  my $bop_config = 'business-onlinepayment';
-  $bop_config .= '-ach'
-    if $method eq 'ECHECK' && $conf->exists($bop_config. '-ach');
-  my ( $processor, $login, $password, $action, @bop_options ) =
-    $conf->config($bop_config);
-  $action ||= 'normal authorization';
-  pop @bop_options if scalar(@bop_options) % 2 && $bop_options[-1] =~ /^\s*$/;
-  die "No real-time processor is enabled - ".
-      "did you set the business-onlinepayment configuration value?\n"
-    unless $processor;
+  my $payinfo = exists($options{'payinfo'})
+                  ? $options{'payinfo'}
+                  : $self->payinfo;
 
-  #massage data
+  ###
+  # select a gateway
+  ###
+
+  my $taxclass = ''; #XXX not yet
+
+  #look for an agent gateway override first
+  my $cardtype;
+  if ( $method eq 'CC' ) {
+    $cardtype = cardtype($payinfo);
+  } elsif ( $method eq 'ECHECK' ) {
+    $cardtype = 'ACH';
+  } else {
+    $cardtype = $method;
+  }
+
+  my $override =
+       qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+                                           cardtype => $cardtype,
+                                           taxclass => $taxclass,       } )
+    || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+                                           cardtype => '',
+                                           taxclass => $taxclass,       } )
+    || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+                                           cardtype => $cardtype,
+                                           taxclass => '',              } )
+    || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+                                           cardtype => '',
+                                           taxclass => '',              } );
+
+  my $payment_gateway = '';
+  my( $processor, $login, $password, $action, @bop_options );
+  if ( $override ) { #use a payment gateway override
+
+    $payment_gateway = $override->payment_gateway;
+
+    $processor   = $payment_gateway->gateway_module;
+    $login       = $payment_gateway->gateway_username;
+    $password    = $payment_gateway->gateway_password;
+    $action      = $payment_gateway->gateway_action;
+    @bop_options = $payment_gateway->options;
+
+  } else { #use the standard settings from the config
+
+    die "Real-time processing not enabled\n"
+      unless $conf->exists('business-onlinepayment');
+
+    #load up config
+    my $bop_config = 'business-onlinepayment';
+    $bop_config .= '-ach'
+      if $method eq 'ECHECK' && $conf->exists($bop_config. '-ach');
+    ( $processor, $login, $password, $action, @bop_options ) =
+      $conf->config($bop_config);
+    $action ||= 'normal authorization';
+    pop @bop_options if scalar(@bop_options) % 2 && $bop_options[-1] =~ /^\s*$/;
+    die "No real-time processor is enabled - ".
+        "did you set the business-onlinepayment configuration value?\n"
+      unless $processor;
+
+  }
+
+  ###
+  # massage data
+  ###
 
   my $address = exists($options{'address1'})
                     ? $options{'address1'}
@@ -2088,10 +2182,6 @@ sub realtime_bop {
               ? $conf->config('business-onlinepayment-email-override')
               : $invoicing_list[0];
 
-  my $payinfo = exists($options{'payinfo'})
-                  ? $options{'payinfo'}
-                  : $self->payinfo;
-
   my %content = ();
   if ( $method eq 'CC' ) { 
 
@@ -2130,7 +2220,9 @@ sub realtime_bop {
     $content{phone} = $payinfo;
   }
 
-  #transaction(s)
+  ###
+  # run transaction(s)
+  ###
 
   my( $action1, $action2 ) = split(/\s*\,\s*/, $action );
 
@@ -2208,7 +2300,10 @@ sub realtime_bop {
 
   }
 
-  #remove paycvv after initial transaction
+  ###
+  # remove paycvv after initial transaction
+  ###
+
   #false laziness w/misc/process/payment.cgi - check both to make sure working
   # correctly
   if ( defined $self->dbdef_table->column('paycvv')
@@ -2221,7 +2316,10 @@ sub realtime_bop {
     }
   }
 
-  #result handling
+  ###
+  # result handling
+  ###
+
   if ( $transaction->is_success() ) {
 
     my %method2payby = (
@@ -2230,7 +2328,13 @@ sub realtime_bop {
       'LEC'    => 'LECB',
     );
 
-    my $paybatch = "$processor:". $transaction->authorization;
+    my $paybatch = '';
+    if ( $payment_gateway ) { # agent override
+      $paybatch = $payment_gateway->gatewaynum. '-';
+    }
+
+    $paybatch .= "$processor:". $transaction->authorization;
+
     $paybatch .= ':'. $transaction->order_number
       if $transaction->can('order_number')
       && length($transaction->order_number);
@@ -3278,17 +3382,10 @@ Returns an SQL expression identifying active cust_main records.
 
 =cut
 
-my $recurring_sql = "
-  '0' != ( select freq from part_pkg
-             where cust_pkg.pkgpart = part_pkg.pkgpart )
-";
-
 sub active_sql { "
   0 < ( SELECT COUNT(*) FROM cust_pkg
           WHERE cust_pkg.custnum = cust_main.custnum
-            AND $recurring_sql
-            AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
-            AND ( cust_pkg.susp   IS NULL OR cust_pkg.susp   = 0 )
+            AND ". FS::cust_pkg->active_sql. "
       )
 "; }
 
@@ -3299,6 +3396,12 @@ Returns an SQL expression identifying suspended cust_main records.
 
 =cut
 
+#my $recurring_sql = FS::cust_pkg->recurring_sql;
+my $recurring_sql = "
+  '0' != ( select freq from part_pkg
+             where cust_pkg.pkgpart = part_pkg.pkgpart )
+";
+
 sub suspended_sql { susp_sql(@_); }
 sub susp_sql { "
     0 < ( SELECT COUNT(*) FROM cust_pkg
@@ -3308,9 +3411,7 @@ sub susp_sql { "
         )
     AND 0 = ( SELECT COUNT(*) FROM cust_pkg
                 WHERE cust_pkg.custnum = cust_main.custnum
-                  AND $recurring_sql
-                  AND ( cust_pkg.susp IS NULL OR cust_pkg.susp = 0 )
-                  AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
+                  AND ". FS::cust_pkg->active_sql. "
             )
 "; }
 
diff --git a/FS/FS/option_Common.pm b/FS/FS/option_Common.pm
new file mode 100644 (file)
index 0000000..c57e55b
--- /dev/null
@@ -0,0 +1,295 @@
+package FS::option_Common;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+use FS::Record qw( qsearch qsearchs dbh );
+
+@ISA = qw( FS::Record );
+
+$DEBUG = 0;
+
+=head1 NAME
+
+FS::option_Common - Base class for option sub-classes
+
+=head1 SYNOPSIS
+
+use FS::option_Common;
+
+@ISA = qw( FS::option_Common );
+
+=head1 DESCRIPTION
+
+FS::option_Common is intended as a base class for classes which have a
+simple one-to-many class associated with them, used to store a hash-like data
+structure of keys and values.
+
+=head1 METHODS
+
+=over 4
+
+=item insert [ HASHREF | OPTION => VALUE ... ]
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+If a list or hash reference of options is supplied, option records are also
+created.
+
+=cut
+
+#false laziness w/queue.pm
+sub insert {
+  my $self = shift;
+  my $options = 
+    ( ref($_[0]) eq 'HASH' )
+      ? shift
+      : { @_ };
+  warn "FS::option_Common::insert called on $self with options ".
+       join(', ', map "$_ => ".$options->{$_}, keys %$options)
+    if $DEBUG;
+
+  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;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  my $pkey = $self->pkey;
+  my $option_table = $self->option_table;
+
+  foreach my $optionname ( keys %{$options} ) {
+    my $href = {
+      $pkey         => $self->get($pkey),
+      'optionname'  => $optionname,
+      'optionvalue' => $options->{$optionname},
+    };
+
+    #my $option_record = eval "new FS::$option_table \$href";
+    #if ( $@ ) {
+    #  $dbh->rollback if $oldAutoCommit;
+    #  return $@;
+    #}
+    my $option_record = $option_table->new($href);
+
+    $error = $option_record->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  '';
+
+}
+
+=item delete
+
+Delete this record from the database.  Any associated option records are also
+deleted.
+
+=cut
+
+#foreign keys would make this much less tedious... grr dumb mysql
+sub delete {
+  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::delete;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+  
+  my $pkey = $self->pkey;
+  my $option_table = $self->option_table;
+
+  foreach my $obj ( $self->option_objects ) {
+    my $error = $obj->delete;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  '';
+
+}
+
+=item replace [ HASHREF | OPTION => VALUE ... ]
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+If a list hash reference of options is supplied, part_export_option records are
+created or modified (see L<FS::part_export_option>).
+
+=cut
+
+sub replace {
+  my $self = shift;
+  my $old = shift;
+  my $options = 
+    ( ref($_[0]) eq 'HASH' )
+      ? shift
+      : { @_ };
+  warn "FS::option_Common::insert called on $self with options ".
+       join(', ', map "$_ => ". $options->{$_}, keys %$options)
+    if $DEBUG;
+
+  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::replace($old);
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  my $pkey = $self->pkey;
+  my $option_table = $self->option_table;
+
+  foreach my $optionname ( keys %{$options} ) {
+    my $old = qsearchs( $option_table, {
+        $pkey         => $self->get($pkey),
+        'optionname'  => $optionname,
+    } );
+
+    my $href = {
+        $pkey         => $self->get($pkey),
+        'optionname'  => $optionname,
+        'optionvalue' => $options->{$optionname},
+    };
+
+    #my $new = eval "new FS::$option_table \$href";
+    #if ( $@ ) {
+    #  $dbh->rollback if $oldAutoCommit;
+    #  return $@;
+    #}
+    my $new = $option_table->new($href);
+
+    $new->optionnum($old->optionnum) if $old;
+    my $error = $old ? $new->replace($old) : $new->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  #remove extraneous old options
+  foreach my $opt (
+    grep { !exists $options->{$_->optionname} } $old->option_objects
+  ) {
+    my $error = $opt->delete;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  '';
+
+}
+
+=item option_objects
+
+Returns all options as FS::I<tablename>_option objects.
+
+=cut
+
+sub option_objects {
+  my $self = shift;
+  my $pkey = $self->pkey;
+  my $option_table = $self->option_table;
+  qsearch($option_table, { $pkey => $self->get($pkey) } );
+}
+
+=item options 
+
+Returns a list of option names and values suitable for assigning to a hash.
+
+=cut
+
+sub options {
+  my $self = shift;
+  map { $_->optionname => $_->optionvalue } $self->option_objects;
+}
+
+=item option OPTIONNAME
+
+Returns the option value for the given name, or the empty string.
+
+=cut
+
+sub option {
+  my $self = shift;
+  my $pkey = $self->pkey;
+  my $option_table = $self->option_table;
+  my $obj =
+    qsearchs($option_table, {
+      $pkey      => $self->get($pkey),
+      optionname => shift,
+  } );
+  $obj ? $obj->optionvalue : '';
+}
+
+
+sub pkey {
+  my $self = shift;
+  my $pkey = $self->dbdef_table->primary_key;
+}
+
+sub option_table {
+  my $self = shift;
+  my $option_table = $self->table . '_option';
+  eval "use FS::$option_table";
+  die $@ if $@;
+  $option_table;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
index 3f677b2..dce2d2a 100644 (file)
@@ -5,11 +5,12 @@ use vars qw( @ISA @EXPORT_OK $DEBUG %exports );
 use Exporter;
 use Tie::IxHash;
 use FS::Record qw( qsearch qsearchs dbh );
+use FS::option_Common;
 use FS::part_svc;
 use FS::part_export_option;
 use FS::export_svc;
 
-@ISA = qw(FS::Record);
+@ISA = qw( FS::option_Common );
 @EXPORT_OK = qw(export_info);
 
 $DEBUG = 0;
@@ -103,48 +104,6 @@ otherwise returns false.
 If a hash reference of options is supplied, part_export_option records are
 created (see L<FS::part_export_option>).
 
-=cut
-
-#false laziness w/queue.pm
-sub insert {
-  my $self = shift;
-  my $options = 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;
-  if ( $error ) {
-    $dbh->rollback if $oldAutoCommit;
-    return $error;
-  }
-
-  foreach my $optionname ( keys %{$options} ) {
-    my $part_export_option = new FS::part_export_option ( {
-      'exportnum'   => $self->exportnum,
-      'optionname'  => $optionname,
-      'optionvalue' => $options->{$optionname},
-    } );
-    $error = $part_export_option->insert;
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return $error;
-    }
-  }
-
-  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
-  '';
-
-}
-
 =item delete
 
 Delete this record from the database.
@@ -171,14 +130,6 @@ sub delete {
     return $error;
   }
 
-  foreach my $part_export_option ( $self->part_export_option ) {
-    my $error = $part_export_option->delete;
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return $error;
-    }
-  }
-
   foreach my $export_svc ( $self->export_svc ) {
     my $error = $export_svc->delete;
     if ( $error ) {
@@ -193,72 +144,6 @@ sub delete {
 
 }
 
-=item replace OLD_RECORD HASHREF
-
-Replaces the OLD_RECORD with this one in the database.  If there is an error,
-returns the error, otherwise returns false.
-
-If a hash reference of options is supplied, part_export_option records are
-created or modified (see L<FS::part_export_option>).
-
-=cut
-
-sub replace {
-  my $self = shift;
-  my $old = shift;
-  my $options = 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::replace($old);
-  if ( $error ) {
-    $dbh->rollback if $oldAutoCommit;
-    return $error;
-  }
-
-  foreach my $optionname ( keys %{$options} ) {
-    my $old = qsearchs( 'part_export_option', {
-        'exportnum'   => $self->exportnum,
-        'optionname'  => $optionname,
-    } );
-    my $new = new FS::part_export_option ( {
-        'exportnum'   => $self->exportnum,
-        'optionname'  => $optionname,
-        'optionvalue' => $options->{$optionname},
-    } );
-    $new->optionnum($old->optionnum) if $old;
-    my $error = $old ? $new->replace($old) : $new->insert;
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return $error;
-    }
-  }
-
-  #remove extraneous old options
-  foreach my $opt (
-    grep { !exists $options->{$_->optionname} } $old->part_export_option
-  ) {
-    my $error = $opt->delete;
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return $error;
-    }
-  }
-
-  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
-  '';
-
-};
-
 =item check
 
 Checks all fields to make sure this is a valid export.  If there is
@@ -347,36 +232,17 @@ L<FS::part_export_option>).
 
 sub part_export_option {
   my $self = shift;
-  qsearch('part_export_option', { 'exportnum' => $self->exportnum } );
+  $self->option_objects;
 }
 
 =item options 
 
 Returns a list of option names and values suitable for assigning to a hash.
 
-=cut
-
-sub options {
-  my $self = shift;
-  map { $_->optionname => $_->optionvalue } $self->part_export_option;
-}
-
 =item option OPTIONNAME
 
 Returns the option value for the given name, or the empty string.
 
-=cut
-
-sub option {
-  my $self = shift;
-  my $part_export_option =
-    qsearchs('part_export_option', {
-      exportnum  => $self->exportnum,
-      optionname => shift,
-  } );
-  $part_export_option ? $part_export_option->optionvalue : '';
-}
-
 =item _rebless
 
 Reblesses the object into the FS::part_export::EXPORTTYPE class, where
index 33b5e5a..e759404 100644 (file)
@@ -104,7 +104,7 @@ sub check {
 
   my $error = 
     $self->ut_numbern('optionnum')
-    || $self->ut_number('exportnum')
+    || $self->ut_foreign_key('exportnum', 'part_export', 'exportnum')
     || $self->ut_alpha('optionname')
     || $self->ut_anything('optionvalue')
   ;
index fb08ddd..73e469c 100644 (file)
@@ -13,7 +13,9 @@ use FS::agent_type;
 use FS::type_pkgs;
 use FS::part_pkg_option;
 
-@ISA = qw( FS::Record );
+@ISA = qw( FS::Record ); # FS::option_Common ); # this can use option_Common
+                                                # when all the plandata bs is
+                                                # gone
 
 $DEBUG = 0;
 
@@ -755,6 +757,8 @@ The delete method is unimplemented.
 setup and recur semantics are not yet defined (and are implemented in
 FS::cust_bill.  hmm.).
 
+plandata should go
+
 =head1 SEE ALSO
 
 L<FS::Record>, L<FS::cust_pkg>, L<FS::type_pkgs>, L<FS::pkg_svc>, L<Safe>.
diff --git a/FS/FS/payment_gateway.pm b/FS/FS/payment_gateway.pm
new file mode 100644 (file)
index 0000000..a5cdd9d
--- /dev/null
@@ -0,0 +1,147 @@
+package FS::payment_gateway;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::option_Common;
+
+@ISA = qw( FS::option_Common );
+
+=head1 NAME
+
+FS::payment_gateway - Object methods for payment_gateway records
+
+=head1 SYNOPSIS
+
+  use FS::payment_gateway;
+
+  $record = new FS::payment_gateway \%hash;
+  $record = new FS::payment_gateway { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::payment_gateway object represents an payment gateway.
+FS::payment_gateway inherits from FS::Record.  The following fields are
+currently supported:
+
+=over 4
+
+=item gatewaynum - primary key
+
+=item gateway_module - Business::OnlinePayment:: module name
+
+=item gateway_username - payment gateway username
+
+=item gateway_password - payment gateway password
+
+=item gateway_action - optional action or actions (multiple actions are separated with `,': for example: `Authorization Only, Post Authorization').  Defaults to `Normal Authorization'.
+
+=item disabled - Disabled flag, empty or 'Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new payment gateway.  To add the payment gateway to the database, see
+L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'payment_gateway'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid payment gateway.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('gatewaynum')
+    || $self->ut_alpha('gateway_module')
+    || $self->ut_textn('gateway_username')
+    || $self->ut_anything('gateway_password')
+    || $self->ut_enum('disabled', [ '', 'Y' ] )
+    #|| $self->ut_textn('gateway_action')
+  ;
+  return $error if $error;
+
+  if ( $self->gateway_action ) {
+    my @actions = split(/,\s*/, $self->gateway_action);
+    $self->gateway_action(
+      join( ',', map { /^(Normal Authorization|Authorization Only|Credit|Post Authorization)$/
+                         or return "Unknown action $_";
+                       $1
+                     }
+                     @actions
+          )
+   );
+  } else {
+    $self->gateway_action('Normal Authorization');
+  }
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/payment_gateway_option.pm b/FS/FS/payment_gateway_option.pm
new file mode 100644 (file)
index 0000000..0576022
--- /dev/null
@@ -0,0 +1,126 @@
+package FS::payment_gateway_option;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::payment_gateway_option - Object methods for payment_gateway_option records
+
+=head1 SYNOPSIS
+
+  use FS::payment_gateway_option;
+
+  $record = new FS::payment_gateway_option \%hash;
+  $record = new FS::payment_gateway_option { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::payment_gateway_option object represents an option key and value for
+a payment gateway.  FS::payment_gateway_option inherits from
+FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item optionnum - primary key
+
+=item gatewaynum - 
+
+=item optionname - 
+
+=item optionvalue - 
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new option.  To add the option to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'payment_gateway_option'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid option.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('optionnum')
+    || $self->ut_foreign_key('gatewaynum', 'payment_gateway', 'gatewaynum')
+    || $self->ut_text('optionname')
+    || $self->ut_textn('optionvalue')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
index fa2cef0..50bc7b5 100644 (file)
@@ -92,6 +92,8 @@ FS/part_export/http.pm
 FS/part_export/infostreet.pm
 FS/part_export/ldap.pm
 FS/part_export/null.pm
+FS/part_export/radiator.pm
+FS/part_export/router.pm
 FS/part_export/shellcommands.pm
 FS/part_export/shellcommands_withdomain.pm
 FS/part_export/sqlmail.pm
@@ -221,6 +223,7 @@ t/part_export-ldap.t
 t/part_export-null.t
 t/part_export-passwdfile.t
 t/part_export-postfix.t
+t/part_export-radiator.t
 t/part_export-router.t
 t/part_export-shellcommands.t
 t/part_export-shellcommands_withdomain.t
@@ -276,3 +279,11 @@ t/msgcat.t
 t/raddb.t
 t/clientapi_session.t
 t/clientapi_session_field.t
+FS/payment_gateway.pm
+t/payment_gateway.t
+FS/payment_gateway_option.pm
+t/payment_gateway_option.t
+FS/option_Common.pm
+t/option_Common.t
+FS/agent_payment_gateway.pm
+t/agent_payment_gateway.t
index a2ef31e..14c5a84 100755 (executable)
@@ -1,29 +1,24 @@
 #!/usr/bin/perl -Tw
 
 #to delay loading dbdef until we're ready
-BEGIN { $FS::Record::setup_hack = 1; }
+BEGIN { $FS::Schema::setup_hack = 1; }
 
 use strict;
 use vars qw($opt_s);
 use Getopt::Std;
 use Locale::Country;
 use Locale::SubCountry;
-use DBI;
-use DBIx::DBSchema 0.25;
-use DBIx::DBSchema::Table;
-use DBIx::DBSchema::Column;
-use DBIx::DBSchema::ColGroup::Unique;
-use DBIx::DBSchema::ColGroup::Index;
 use FS::UID qw(adminsuidsetup datasrc checkeuid getsecrets);
+use FS::Schema qw( dbdef_dist );
 use FS::Record;
 use FS::cust_main_county;
-use FS::raddb;
+#use FS::raddb;
 use FS::part_bill_event;
 
 die "Not running uid freeside!" unless checkeuid();
 
-my %attrib2db =
-  map { lc($FS::raddb::attrib{$_}) => $_ } keys %FS::raddb::attrib;
+#my %attrib2db =
+#  map { lc($FS::raddb::attrib{$_}) => $_ } keys %FS::raddb::attrib;
 
 getopts("s");
 my $user = shift or die &usage;
@@ -75,158 +70,25 @@ my $username_len = 32; #usernamemax config file
 #  $x =~ /^y/i;
 #}
 
-my @check_attributes = (); #add later
-my @attributes = (); #add later
-my $ship = $opt_s;
-
-###
-
-my($char_d) = 80; #default maxlength for text fields
-
-#my(@date_type)  = ( 'timestamp', '', ''     );
-my(@date_type)  = ( 'int', 'NULL', ''     );
-my(@perl_type) = ( 'text', 'NULL', ''  ); 
-my @money_type = ( 'decimal',   '', '10,2' );
+#my @check_attributes = (); #add later
+#my @attributes = (); #add later
+#my $ship = $opt_s;
 
 ###
 # create a dbdef object from the old data structure
 ###
 
-my(%tables)=&tables_hash_hack;
-
-#turn it into objects
-my($dbdef) = new DBIx::DBSchema ( map {  
-  my(@columns);
-  while (@{$tables{$_}{'columns'}}) {
-    my($name,$type,$null,$length)=splice @{$tables{$_}{'columns'}}, 0, 4;
-    push @columns, new DBIx::DBSchema::Column ( $name,$type,$null,$length );
-  }
-  DBIx::DBSchema::Table->new(
-    $_,
-    $tables{$_}{'primary_key'},
-    DBIx::DBSchema::ColGroup::Unique->new($tables{$_}{'unique'}),
-    DBIx::DBSchema::ColGroup::Index->new($tables{$_}{'index'}),
-    @columns,
-  );
-} (keys %tables) );
-
-my $cust_main = $dbdef->table('cust_main');
-unless ($ship) { #remove ship_ from cust_main
-  $cust_main->delcolumn($_) foreach ( grep /^ship_/, $cust_main->columns );
-} else { #add indices
-  push @{$cust_main->index->lol_ref},
-    map { [ "ship_$_" ] } qw( last company daytime night fax );
-}
-
-#add radius attributes to svc_acct
-
-my($svc_acct)=$dbdef->table('svc_acct');
-
-my($attribute);
-foreach $attribute (@attributes) {
-  $svc_acct->addcolumn ( new DBIx::DBSchema::Column (
-    'radius_'. $attribute,
-    'varchar',
-    'NULL',
-    $char_d,
-  ));
-}
-
-foreach $attribute (@check_attributes) {
-  $svc_acct->addcolumn( new DBIx::DBSchema::Column (
-    'rc_'. $attribute,
-    'varchar',
-    'NULL',
-    $char_d,
-  ));
-}
-
-#create history tables (false laziness w/create-history-tables)
-foreach my $table (
-  grep { ! /^clientapi_session/ }
-  grep { ! /^h_/ }
-  $dbdef->tables
-) {
-  my $tableobj = $dbdef->table($table)
-    or die "unknown table $table";
-
-  die "unique->lol_ref undefined for $table"
-    unless defined $tableobj->unique->lol_ref;
-  die "index->lol_ref undefined for $table"
-    unless defined $tableobj->index->lol_ref;
-
-  my $h_tableobj = DBIx::DBSchema::Table->new( {
-    name        => "h_$table",
-    primary_key => 'historynum',
-    unique      => DBIx::DBSchema::ColGroup::Unique->new( [] ),
-    'index'     => DBIx::DBSchema::ColGroup::Index->new( [
-                     @{$tableobj->unique->lol_ref},
-                     @{$tableobj->index->lol_ref}
-                   ] ),
-    columns     => [
-                     DBIx::DBSchema::Column->new( {
-                       'name'    => 'historynum',
-                       'type'    => 'serial',
-                       'null'    => 'NOT NULL',
-                       'length'  => '',
-                       'default' => '',
-                       'local'   => '',
-                     } ),
-                     DBIx::DBSchema::Column->new( {
-                       'name'    => 'history_date',
-                       'type'    => 'int',
-                       'null'    => 'NULL',
-                       'length'  => '',
-                       'default' => '',
-                       'local'   => '',
-                     } ),
-                     DBIx::DBSchema::Column->new( {
-                       'name'    => 'history_user',
-                       'type'    => 'varchar',
-                       'null'    => 'NOT NULL',
-                       'length'  => '80',
-                       'default' => '',
-                       'local'   => '',
-                     } ),
-                     DBIx::DBSchema::Column->new( {
-                       'name'    => 'history_action',
-                       'type'    => 'varchar',
-                       'null'    => 'NOT NULL',
-                       'length'  => '80',
-                       'default' => '',
-                       'local'   => '',
-                     } ),
-                     map {
-                       my $column = $tableobj->column($_);
-
-                       #clone so as to not disturb the original
-                       $column = DBIx::DBSchema::Column->new( {
-                         map { $_ => $column->$_() }
-                           qw( name type null length default local )
-                       } );
-
-                       $column->type('int')
-                         if $column->type eq 'serial';
-                       #$column->default('')
-                       #  if $column->default =~ /^nextval\(/i;
-                       #( my $local = $column->local ) =~ s/AUTO_INCREMENT//i;
-                       #$column->local($local);
-                       $column;
-                     } $tableobj->columns
-                   ],
-  } );
-  $dbdef->addtable($h_tableobj);
-}
+my $dbdef = dbdef_dist;
 
 #important
 $dbdef->save($dbdef_file);
-&FS::Record::reload_dbdef($dbdef_file);
+&FS::Schema::reload_dbdef($dbdef_file);
 
 ###
 # create 'em
 ###
 
-my($dbh)=adminsuidsetup $user;
+my $dbh = adminsuidsetup $user;
 
 #create tables
 $|=1;
@@ -301,974 +163,8 @@ $dbh->disconnect or die $dbh->errstr;
 #print "Freeside database initialized sucessfully\n";
 
 sub usage {
-  die "Usage:\n  freeside-setup [ -s ] user\n"; 
+  die "Usage:\n  freeside-setup user\n"; 
 }
 
-###
-# Now it becomes an object.  much better.
-###
-sub tables_hash_hack {
-
-  #note that s/(date|change)/_$1/; to avoid keyword conflict.
-  #put a kludge in FS::Record to catch this or? (pry need some date-handling
-  #stuff anyway also)
-
-  my(%tables)=( #yech.}
-
-    'agent' => {
-      'columns' => [
-        'agentnum', 'serial',            '',     '',
-        'agent',    'varchar',           '',     $char_d,
-        'typenum',  'int',            '',     '',
-        'freq',     'int',       'NULL', '',
-        'prog',     @perl_type,
-        'disabled',     'char', 'NULL', 1,
-        'username', 'varchar',       'NULL',     $char_d,
-        '_password','varchar',       'NULL',     $char_d,
-      ],
-      'primary_key' => 'agentnum',
-      'unique' => [],
-      'index' => [ ['typenum'], ['disabled'] ],
-    },
-
-    'agent_type' => {
-      'columns' => [
-        'typenum',   'serial',  '', '',
-        'atype',     'varchar', '', $char_d,
-      ],
-      'primary_key' => 'typenum',
-      'unique' => [],
-      'index' => [],
-    },
-
-    'type_pkgs' => {
-      'columns' => [
-        'typepkgnum', 'serial', '', '',
-        'typenum',   'int',  '', '',
-        'pkgpart',   'int',  '', '',
-      ],
-      'primary_key' => 'typepkgnum',
-      'unique' => [ ['typenum', 'pkgpart'] ],
-      'index' => [ ['typenum'] ],
-    },
-
-    'cust_bill' => {
-      'columns' => [
-        'invnum',    'serial',  '', '',
-        'custnum',   'int',  '', '',
-        '_date',     @date_type,
-        'charged',   @money_type,
-        'printed',   'int',  '', '',
-        'closed',    'char', 'NULL', 1,
-      ],
-      'primary_key' => 'invnum',
-      'unique' => [],
-      'index' => [ ['custnum'], ['_date'] ],
-    },
-
-    'cust_bill_event' => {
-      'columns' => [
-        'eventnum',    'serial',  '', '',
-        'invnum',   'int',  '', '',
-        'eventpart',   'int',  '', '',
-        '_date',     @date_type,
-        'status', 'varchar', '', $char_d,
-        'statustext', 'text', 'NULL', '',
-      ],
-      'primary_key' => 'eventnum',
-      #no... there are retries now #'unique' => [ [ 'eventpart', 'invnum' ] ],
-      'unique' => [],
-      'index' => [ ['invnum'], ['status'] ],
-    },
-
-    'part_bill_event' => {
-      'columns' => [
-        'eventpart',    'serial',  '', '',
-        'payby',       'char',  '', 4,
-        'event',       'varchar',           '',     $char_d,
-        'eventcode',    @perl_type,
-        'seconds',     'int', 'NULL', '',
-        'weight',      'int', '', '',
-        'plan',       'varchar', 'NULL', $char_d,
-        'plandata',   'text', 'NULL', '',
-        'disabled',     'char', 'NULL', 1,
-      ],
-      'primary_key' => 'eventpart',
-      'unique' => [],
-      'index' => [ ['payby'], ['disabled'], ],
-    },
-
-    'cust_bill_pkg' => {
-      'columns' => [
-        'billpkgnum', 'serial', '', '',
-        'pkgnum',  'int', '', '',
-        'invnum',  'int', '', '',
-        'setup',   @money_type,
-        'recur',   @money_type,
-        'sdate',   @date_type,
-        'edate',   @date_type,
-        'itemdesc', 'varchar', 'NULL', $char_d,
-      ],
-      'primary_key' => 'billpkgnum',
-      'unique' => [],
-      'index' => [ ['invnum'] ],
-    },
-
-    'cust_bill_pkg_detail' => {
-      'columns' => [
-        'detailnum', 'serial', '', '',
-        'pkgnum',  'int', '', '',
-        'invnum',  'int', '', '',
-        'detail',  'varchar', '', $char_d,
-      ],
-      'primary_key' => 'detailnum',
-      'unique' => [],
-      'index' => [ [ 'pkgnum', 'invnum' ] ],
-    },
-
-    'cust_credit' => {
-      'columns' => [
-        'crednum',  'serial', '', '',
-        'custnum',  'int', '', '',
-        '_date',    @date_type,
-        'amount',   @money_type,
-        'otaker',   'varchar', '', 32,
-        'reason',   'text', 'NULL', '',
-        'closed',    'char', 'NULL', 1,
-      ],
-      'primary_key' => 'crednum',
-      'unique' => [],
-      'index' => [ ['custnum'] ],
-    },
-
-    'cust_credit_bill' => {
-      'columns' => [
-        'creditbillnum', 'serial', '', '',
-        'crednum',  'int', '', '',
-        'invnum',  'int', '', '',
-        '_date',    @date_type,
-        'amount',   @money_type,
-      ],
-      'primary_key' => 'creditbillnum',
-      'unique' => [],
-      'index' => [ ['crednum'], ['invnum'] ],
-    },
-
-    'cust_main' => {
-      'columns' => [
-        'custnum',  'serial',  '',     '',
-        'agentnum', 'int',  '',     '',
-#        'titlenum', 'int',  'NULL',   '',
-        'last',     'varchar', '',     $char_d,
-#        'middle',   'varchar', 'NULL', $char_d,
-        'first',    'varchar', '',     $char_d,
-        'ss',       'varchar', 'NULL', 11,
-        'company',  'varchar', 'NULL', $char_d,
-        'address1', 'varchar', '',     $char_d,
-        'address2', 'varchar', 'NULL', $char_d,
-        'city',     'varchar', '',     $char_d,
-        'county',   'varchar', 'NULL', $char_d,
-        'state',    'varchar', 'NULL', $char_d,
-        'zip',      'varchar', 'NULL', 10,
-        'country',  'char', '',     2,
-        'daytime',  'varchar', 'NULL', 20,
-        'night',    'varchar', 'NULL', 20,
-        'fax',      'varchar', 'NULL', 12,
-        'ship_last',     'varchar', 'NULL', $char_d,
-#        'ship_middle',   'varchar', 'NULL', $char_d,
-        'ship_first',    'varchar', 'NULL', $char_d,
-        'ship_company',  'varchar', 'NULL', $char_d,
-        'ship_address1', 'varchar', 'NULL', $char_d,
-        'ship_address2', 'varchar', 'NULL', $char_d,
-        'ship_city',     'varchar', 'NULL', $char_d,
-        'ship_county',   'varchar', 'NULL', $char_d,
-        'ship_state',    'varchar', 'NULL', $char_d,
-        'ship_zip',      'varchar', 'NULL', 10,
-        'ship_country',  'char', 'NULL', 2,
-        'ship_daytime',  'varchar', 'NULL', 20,
-        'ship_night',    'varchar', 'NULL', 20,
-        'ship_fax',      'varchar', 'NULL', 12,
-        'payby',    'char', '',     4,
-        'payinfo',  'varchar', 'NULL', 512,
-        'paycvv',   'varchar', 'NULL', 512,
-       'paymask', 'varchar', 'NULL', $char_d,
-        #'paydate',  @date_type,
-        'paydate',  'varchar', 'NULL', 10,
-        'payname',  'varchar', 'NULL', $char_d,
-        'tax',      'char', 'NULL', 1,
-        'otaker',   'varchar', '',    32,
-        'refnum',   'int',  '',     '',
-        'referral_custnum', 'int',  'NULL', '',
-        'comments', 'text', 'NULL', '',
-      ],
-      'primary_key' => 'custnum',
-      'unique' => [],
-      #'index' => [ ['last'], ['company'] ],
-      'index' => [ ['last'], [ 'company' ], [ 'referral_custnum' ],
-                   [ 'daytime' ], [ 'night' ], [ 'fax' ], [ 'refnum' ],
-                 ],
-    },
-
-    'cust_main_invoice' => {
-      'columns' => [
-        'destnum',  'serial',  '',     '',
-        'custnum',  'int',  '',     '',
-        'dest',     'varchar', '',  $char_d,
-      ],
-      'primary_key' => 'destnum',
-      'unique' => [],
-      'index' => [ ['custnum'], ],
-    },
-
-    'cust_main_county' => { #county+state+country are checked off the
-                            #cust_main_county for validation and to provide
-                            # a tax rate.
-      'columns' => [
-        'taxnum',   'serial',   '',    '',
-        'state',    'varchar',  'NULL',    $char_d,
-        'county',   'varchar',  'NULL',    $char_d,
-        'country',  'char',  '', 2, 
-        'taxclass',   'varchar', 'NULL', $char_d,
-        'exempt_amount', @money_type,
-        'tax',      'real',  '',    '', #tax %
-        'taxname',  'varchar',  'NULL',    $char_d,
-        'setuptax',  'char', 'NULL', 1, # Y = setup tax exempt
-        'recurtax',  'char', 'NULL', 1, # Y = recur tax exempt
-      ],
-      'primary_key' => 'taxnum',
-      'unique' => [],
-  #    'unique' => [ ['taxnum'], ['state', 'county'] ],
-      'index' => [],
-    },
-
-    'cust_pay' => {
-      'columns' => [
-        'paynum',   'serial',    '',   '',
-        #now cust_bill_pay #'invnum',   'int',    '',   '',
-        'custnum',  'int',    '',   '',
-        'paid',     @money_type,
-        '_date',    @date_type,
-        'payby',    'char',   '',     4, # CARD/BILL/COMP, should be index into
-                                         # payment type table.
-        'payinfo',  'varchar',   'NULL', $char_d,  #see cust_main above
-        'paybatch', 'varchar',   'NULL', $char_d, #for auditing purposes.
-        'closed',    'char', 'NULL', 1,
-      ],
-      'primary_key' => 'paynum',
-      'unique' => [],
-      'index' => [ [ 'custnum' ], [ 'paybatch' ], [ 'payby' ], [ '_date' ] ],
-    },
-
-    'cust_pay_void' => {
-      'columns' => [
-        'paynum',    'int',    '',   '',
-        'custnum',   'int',    '',   '',
-        'paid',      @money_type,
-        '_date',     @date_type,
-        'payby',     'char',   '',     4, # CARD/BILL/COMP, should be index into
-                                          # payment type table.
-        'payinfo',   'varchar',   'NULL', $char_d,  #see cust_main above
-        'paybatch',  'varchar',   'NULL', $char_d, #for auditing purposes.
-        'closed',    'char', 'NULL', 1,
-        'void_date', @date_type,
-        'reason',    'varchar',   'NULL', $char_d,
-        'otaker',   'varchar', '', 32,
-      ],
-      'primary_key' => 'paynum',
-      'unique' => [],
-      'index' => [ [ 'custnum' ] ],
-    },
-
-    'cust_bill_pay' => {
-      'columns' => [
-        'billpaynum', 'serial',     '',   '',
-        'invnum',  'int',     '',   '',
-        'paynum',  'int',     '',   '',
-        'amount',  @money_type,
-        '_date',   @date_type
-      ],
-      'primary_key' => 'billpaynum',
-      'unique' => [],
-      'index' => [ [ 'paynum' ], [ 'invnum' ] ],
-    },
-
-    'cust_pay_batch' => { #what's this used for again?  list of customers
-                          #in current CARD batch? (necessarily CARD?)
-      'columns' => [
-        'paybatchnum',   'serial',    '',   '',
-        'invnum',   'int',    '',   '',
-        'custnum',   'int',    '',   '',
-        'last',     'varchar', '',     $char_d,
-        'first',    'varchar', '',     $char_d,
-        'address1', 'varchar', '',     $char_d,
-        'address2', 'varchar', 'NULL', $char_d,
-        'city',     'varchar', '',     $char_d,
-        'state',    'varchar', 'NULL', $char_d,
-        'zip',      'varchar', 'NULL', 10,
-        'country',  'char', '',     2,
-#        'trancode', 'int', '', '',
-        'cardnum',  'varchar', '',     16,
-        #'exp',      @date_type,
-        'exp',      'varchar', '',     11,
-        'payname',  'varchar', 'NULL', $char_d,
-        'amount',   @money_type,
-      ],
-      'primary_key' => 'paybatchnum',
-      'unique' => [],
-      'index' => [ ['invnum'], ['custnum'] ],
-    },
-
-    'cust_pkg' => {
-      'columns' => [
-        'pkgnum',    'serial',    '',   '',
-        'custnum',   'int',    '',   '',
-        'pkgpart',   'int',    '',   '',
-        'otaker',    'varchar', '', 32,
-        'setup',     @date_type,
-        'bill',      @date_type,
-        'last_bill', @date_type,
-        'susp',      @date_type,
-        'cancel',    @date_type,
-        'expire',    @date_type,
-        'manual_flag', 'char', 'NULL', 1,
-      ],
-      'primary_key' => 'pkgnum',
-      'unique' => [],
-      'index' => [ ['custnum'] ],
-    },
-
-    'cust_refund' => {
-      'columns' => [
-        'refundnum',    'serial',    '',   '',
-        #now cust_credit_refund #'crednum',      'int',    '',   '',
-        'custnum',  'int',    '',   '',
-        '_date',        @date_type,
-        'refund',       @money_type,
-        'otaker',       'varchar',   '',   32,
-        'reason',       'varchar',   '',   $char_d,
-        'payby',        'char',   '',     4, # CARD/BILL/COMP, should be index
-                                             # into payment type table.
-        'payinfo',      'varchar',   'NULL', $char_d,  #see cust_main above
-        'paybatch',     'varchar',   'NULL', $char_d,
-        'closed',    'char', 'NULL', 1,
-      ],
-      'primary_key' => 'refundnum',
-      'unique' => [],
-      'index' => [],
-    },
-
-    'cust_credit_refund' => {
-      'columns' => [
-        'creditrefundnum', 'serial',     '',   '',
-        'crednum',  'int',     '',   '',
-        'refundnum',  'int',     '',   '',
-        'amount',  @money_type,
-        '_date',   @date_type
-      ],
-      'primary_key' => 'creditrefundnum',
-      'unique' => [],
-      'index' => [ [ 'crednum', 'refundnum' ] ],
-    },
-
-
-    'cust_svc' => {
-      'columns' => [
-        'svcnum',    'serial',    '',   '',
-        'pkgnum',    'int',    'NULL',   '',
-        'svcpart',   'int',    '',   '',
-      ],
-      'primary_key' => 'svcnum',
-      'unique' => [],
-      'index' => [ ['svcnum'], ['pkgnum'], ['svcpart'] ],
-    },
-
-    'part_pkg' => {
-      'columns' => [
-        'pkgpart',    'serial',    '',   '',
-        'pkg',        'varchar',   '',   $char_d,
-        'comment',    'varchar',   '',   $char_d,
-        'promo_code', 'varchar', 'NULL', $char_d,
-        'setup',      @perl_type,
-        'freq',       'varchar',   '',   $char_d,  #billing frequency
-        'recur',      @perl_type,
-        'setuptax',  'char', 'NULL', 1,
-        'recurtax',  'char', 'NULL', 1,
-        'plan',       'varchar', 'NULL', $char_d,
-        'plandata',   'text', 'NULL', '',
-        'disabled',   'char', 'NULL', 1,
-        'taxclass',   'varchar', 'NULL', $char_d,
-      ],
-      'primary_key' => 'pkgpart',
-      'unique' => [],
-      'index' => [ [ 'promo_code' ], [ 'disabled' ] ],
-    },
-
-#    'part_title' => {
-#      'columns' => [
-#        'titlenum',   'int',    '',   '',
-#        'title',      'varchar',   '',   $char_d,
-#      ],
-#      'primary_key' => 'titlenum',
-#      'unique' => [ [] ],
-#      'index' => [ [] ],
-#    },
-
-    'pkg_svc' => {
-      'columns' => [
-        'pkgsvcnum',  'serial', '',  '',
-        'pkgpart',    'int',    '',   '',
-        'svcpart',    'int',    '',   '',
-        'quantity',   'int',    '',   '',
-        'primary_svc','char', 'NULL',  1,
-      ],
-      'primary_key' => 'pkgsvcnum',
-      'unique' => [ ['pkgpart', 'svcpart'] ],
-      'index' => [ ['pkgpart'] ],
-    },
-
-    'part_referral' => {
-      'columns' => [
-        'refnum',   'serial',    '',   '',
-        'referral', 'varchar',   '',   $char_d,
-        'disabled',     'char', 'NULL', 1,
-      ],
-      'primary_key' => 'refnum',
-      'unique' => [],
-      'index' => [ ['disabled'] ],
-    },
-
-    'part_svc' => {
-      'columns' => [
-        'svcpart',    'serial',    '',   '',
-        'svc',        'varchar',   '',   $char_d,
-        'svcdb',      'varchar',   '',   $char_d,
-        'disabled',   'char',  'NULL',   1,
-      ],
-      'primary_key' => 'svcpart',
-      'unique' => [],
-      'index' => [ [ 'disabled' ] ],
-    },
-
-    'part_svc_column' => {
-      'columns' => [
-        'columnnum',   'serial',         '', '',
-        'svcpart',     'int',         '', '',
-        'columnname',  'varchar',     '', 64,
-        'columnvalue', 'varchar', 'NULL', $char_d,
-        'columnflag',  'char',    'NULL', 1, 
-      ],
-      'primary_key' => 'columnnum',
-      'unique' => [ [ 'svcpart', 'columnname' ] ],
-      'index' => [ [ 'svcpart' ] ],
-    },
-
-    #(this should be renamed to part_pop)
-    'svc_acct_pop' => {
-      'columns' => [
-        'popnum',    'serial',    '',   '',
-        'city',      'varchar',   '',   $char_d,
-        'state',     'varchar',   '',   $char_d,
-        'ac',        'char',   '',   3,
-        'exch',      'char',   '',   3,
-        'loc',       'char',   'NULL',   4, #NULL for legacy purposes
-      ],
-      'primary_key' => 'popnum',
-      'unique' => [],
-      'index' => [ [ 'state' ] ],
-    },
-
-    'part_pop_local' => {
-      'columns' => [
-        'localnum',  'serial',     '',     '',
-        'popnum',    'int',     '',     '',
-        'city',      'varchar', 'NULL', $char_d,
-        'state',     'char',    'NULL', 2,
-        'npa',       'char',    '',     3,
-        'nxx',       'char',    '',     3,
-      ],
-      'primary_key' => 'localnum',
-      'unique' => [],
-      'index' => [ [ 'npa', 'nxx' ], [ 'popnum' ] ],
-    },
-
-    'svc_acct' => {
-      'columns' => [
-        'svcnum',    'int',    '',   '',
-        'username',  'varchar',   '',   $username_len, #unique (& remove dup code)
-        '_password', 'varchar',   '',   72, #13 for encryped pw's plus ' *SUSPENDED* (md5 passwords can be 34, blowfish 60)
-        'sec_phrase', 'varchar',  'NULL',   $char_d,
-        'popnum',    'int',    'NULL',   '',
-        'uid',       'int', 'NULL',   '',
-        'gid',       'int', 'NULL',   '',
-        'finger',    'varchar',   'NULL',   $char_d,
-        'dir',       'varchar',   'NULL',   $char_d,
-        'shell',     'varchar',   'NULL',   $char_d,
-        'quota',     'varchar',   'NULL',   $char_d,
-        'slipip',    'varchar',   'NULL',   15, #four TINYINTs, bah.
-        'seconds',   'int', 'NULL',   '', #uhhhh
-        'domsvc',    'int', '',   '',
-      ],
-      'primary_key' => 'svcnum',
-      #'unique' => [ [ 'username', 'domsvc' ] ],
-      'unique' => [],
-      'index' => [ ['username'], ['domsvc'] ],
-    },
-
-    #'svc_charge' => {
-    #  'columns' => [
-    #    'svcnum',    'int',    '',   '',
-    #    'amount',    @money_type,
-    #  ],
-    #  'primary_key' => 'svcnum',
-    #  'unique' => [ [] ],
-    #  'index' => [ [] ],
-    #},
-
-    'svc_domain' => {
-      'columns' => [
-        'svcnum',    'int',    '',   '',
-        'domain',    'varchar',    '',   $char_d,
-        'catchall',  'int', 'NULL',    '',
-      ],
-      'primary_key' => 'svcnum',
-      'unique' => [ ['domain'] ],
-      'index' => [],
-    },
-
-    'domain_record' => {
-      'columns' => [
-        'recnum',    'serial',     '',  '',
-        'svcnum',    'int',     '',  '',
-        #'reczone',   'varchar', '',  $char_d,
-        'reczone',   'varchar', '',  255,
-        'recaf',     'char',    '',  2,
-        'rectype',   'varchar',    '',  5,
-        #'recdata',   'varchar', '',  $char_d,
-        'recdata',   'varchar', '',  255,
-      ],
-      'primary_key' => 'recnum',
-      'unique'      => [],
-      'index'       => [ ['svcnum'] ],
-    },
-
-    'svc_forward' => {
-      'columns' => [
-        'svcnum',   'int',            '',   '',
-        'srcsvc',   'int',        'NULL',   '',
-        'src',      'varchar',    'NULL',  255,
-        'dstsvc',   'int',        'NULL',   '',
-        'dst',      'varchar',    'NULL',  255,
-      ],
-      'primary_key' => 'svcnum',
-      'unique'      => [],
-      'index'       => [ ['srcsvc'], ['dstsvc'] ],
-    },
-
-    'svc_www' => {
-      'columns' => [
-        'svcnum',   'int',    '',  '',
-        'recnum',   'int',    '',  '',
-        'usersvc',  'int',    '',  '',
-      ],
-      'primary_key' => 'svcnum',
-      'unique'      => [],
-      'index'       => [],
-    },
-
-    #'svc_wo' => {
-    #  'columns' => [
-    #    'svcnum',    'int',    '',   '',
-    #    'svcnum',    'int',    '',   '',
-    #    'svcnum',    'int',    '',   '',
-    #    'worker',    'varchar',   '',   $char_d,
-    #    '_date',     @date_type,
-    #  ],
-    #  'primary_key' => 'svcnum',
-    #  'unique' => [ [] ],
-    #  'index' => [ [] ],
-    #},
-
-    'prepay_credit' => {
-      'columns' => [
-        'prepaynum',   'serial',     '',   '',
-        'identifier',  'varchar', '', $char_d,
-        'amount',      @money_type,
-        'seconds',     'int',     'NULL', '',
-        'agentnum',    'int',     'NULL', '',
-      ],
-      'primary_key' => 'prepaynum',
-      'unique'      => [ ['identifier'] ],
-      'index'       => [],
-    },
-
-    'port' => {
-      'columns' => [
-        'portnum',  'serial',     '',   '',
-        'ip',       'varchar', 'NULL', 15,
-        'nasport',  'int',     'NULL', '',
-        'nasnum',   'int',     '',   '',
-      ],
-      'primary_key' => 'portnum',
-      'unique'      => [],
-      'index'       => [],
-    },
-
-    'nas' => {
-      'columns' => [
-        'nasnum',   'serial',     '',    '',
-        'nas',      'varchar', '',    $char_d,
-        'nasip',    'varchar', '',    15,
-        'nasfqdn',  'varchar', '',    $char_d,
-        'last',     'int',     '',    '',
-      ],
-      'primary_key' => 'nasnum',
-      'unique'      => [ [ 'nas' ], [ 'nasip' ] ],
-      'index'       => [ [ 'last' ] ],
-    },
-
-    'session' => {
-      'columns' => [
-        'sessionnum', 'serial',       '',   '',
-        'portnum',    'int',       '',   '',
-        'svcnum',     'int',       '',   '',
-        'login',      @date_type,
-        'logout',     @date_type,
-      ],
-      'primary_key' => 'sessionnum',
-      'unique'      => [],
-      'index'       => [ [ 'portnum' ] ],
-    },
-
-    'queue' => {
-      'columns' => [
-        'jobnum', 'serial', '', '',
-        'job', 'text', '', '',
-        '_date', 'int', '', '',
-        'status', 'varchar', '', $char_d,
-        'statustext', 'text', 'NULL', '',
-        'svcnum', 'int', 'NULL', '',
-      ],
-      'primary_key' => 'jobnum',
-      'unique'      => [],
-      'index'       => [ [ 'svcnum' ], [ 'status' ] ],
-    },
-
-    'queue_arg' => {
-      'columns' => [
-        'argnum', 'serial', '', '',
-        'jobnum', 'int', '', '',
-        'arg', 'text', 'NULL', '',
-      ],
-      'primary_key' => 'argnum',
-      'unique'      => [],
-      'index'       => [ [ 'jobnum' ] ],
-    },
-
-    'queue_depend' => {
-      'columns' => [
-        'dependnum', 'serial', '', '',
-        'jobnum', 'int', '', '',
-        'depend_jobnum', 'int', '', '',
-      ],
-      'primary_key' => 'dependnum',
-      'unique'      => [],
-      'index'       => [ [ 'jobnum' ], [ 'depend_jobnum' ] ],
-    },
-
-    'export_svc' => {
-      'columns' => [
-        'exportsvcnum' => 'serial', '', '',
-        'exportnum'    => 'int', '', '',
-        'svcpart'      => 'int', '', '',
-      ],
-      'primary_key' => 'exportsvcnum',
-      'unique'      => [ [ 'exportnum', 'svcpart' ] ],
-      'index'       => [ [ 'exportnum' ], [ 'svcpart' ] ],
-    },
-
-    'part_export' => {
-      'columns' => [
-        'exportnum', 'serial', '', '',
-        #'svcpart',   'int', '', '',
-        'machine', 'varchar', '', $char_d,
-        'exporttype', 'varchar', '', $char_d,
-        'nodomain',     'char', 'NULL', 1,
-      ],
-      'primary_key' => 'exportnum',
-      'unique'      => [],
-      'index'       => [ [ 'machine' ], [ 'exporttype' ] ],
-    },
-
-    'part_export_option' => {
-      'columns' => [
-        'optionnum', 'serial', '', '',
-        'exportnum', 'int', '', '',
-        'optionname', 'varchar', '', $char_d,
-        'optionvalue', 'text', 'NULL', '',
-      ],
-      'primary_key' => 'optionnum',
-      'unique'      => [],
-      'index'       => [ [ 'exportnum' ], [ 'optionname' ] ],
-    },
-
-    'radius_usergroup' => {
-      'columns' => [
-        'usergroupnum', 'serial', '', '',
-        'svcnum',       'int', '', '',
-        'groupname',    'varchar', '', $char_d,
-      ],
-      'primary_key' => 'usergroupnum',
-      'unique'      => [],
-      'index'       => [ [ 'svcnum' ], [ 'groupname' ] ],
-    },
-
-    'msgcat' => {
-      'columns' => [
-        'msgnum', 'serial', '', '',
-        'msgcode', 'varchar', '', $char_d,
-        'locale', 'varchar', '', 16,
-        'msg', 'text', '', '',
-      ],
-      'primary_key' => 'msgnum',
-      'unique'      => [ [ 'msgcode', 'locale' ] ],
-      'index'       => [],
-    },
-
-    'cust_tax_exempt' => {
-      'columns' => [
-        'exemptnum', 'serial', '', '',
-        'custnum',   'int', '', '',
-        'taxnum',    'int', '', '',
-        'year',      'int', '', '',
-        'month',     'int', '', '',
-        'amount',   @money_type,
-      ],
-      'primary_key' => 'exemptnum',
-      'unique'      => [ [ 'custnum', 'taxnum', 'year', 'month' ] ],
-      'index'       => [],
-    },
-
-    'router' => {
-      'columns' => [
-        'routernum', 'serial', '', '',
-        'routername', 'varchar', '', $char_d,
-        'svcnum', 'int', 'NULL', '',
-      ],
-      'primary_key' => 'routernum',
-      'unique'      => [],
-      'index'       => [],
-    },
-
-    'part_svc_router' => {
-      'columns' => [
-        'svcrouternum', 'serial', '', '',
-        'svcpart', 'int', '', '',
-       'routernum', 'int', '', '',
-      ],
-      'primary_key' => 'svcrouternum',
-      'unique'      => [],
-      'index'       => [],
-    },
-
-    'addr_block' => {
-      'columns' => [
-        'blocknum', 'serial', '', '',
-       'routernum', 'int', '', '',
-        'ip_gateway', 'varchar', '', 15,
-        'ip_netmask', 'int', '', '',
-      ],
-      'primary_key' => 'blocknum',
-      'unique'      => [ [ 'blocknum', 'routernum' ] ],
-      'index'       => [],
-    },
-
-    'svc_broadband' => {
-      'columns' => [
-        'svcnum', 'int', '', '',
-        'blocknum', 'int', '', '',
-        'speed_up', 'int', '', '',
-        'speed_down', 'int', '', '',
-        'ip_addr', 'varchar', '', 15,
-      ],
-      'primary_key' => 'svcnum',
-      'unique'      => [],
-      'index'       => [],
-    },
-
-    'part_virtual_field' => {
-      'columns' => [
-        'vfieldpart', 'int', '', '',
-        'dbtable', 'varchar', '', 32,
-        'name', 'varchar', '', 32,
-        'check_block', 'text', 'NULL', '',
-        'length', 'int', 'NULL', '',
-        'list_source', 'text', 'NULL', '',
-        'label', 'varchar', 'NULL', 80,
-      ],
-      'primary_key' => 'vfieldpart',
-      'unique' => [],
-      'index' => [],
-    },
-
-    'virtual_field' => {
-      'columns' => [
-        'vfieldnum', 'serial', '', '',
-        'recnum', 'int', '', '',
-        'vfieldpart', 'int', '', '',
-        'value', 'varchar', '', 128,
-      ],
-      'primary_key' => 'vfieldnum',
-      'unique' => [ [ 'vfieldpart', 'recnum' ] ],
-      'index' => [],
-    },
-
-    'acct_snarf' => {
-      'columns' => [
-        'snarfnum',  'int', '', '',
-        'svcnum',    'int', '', '',
-        'machine',   'varchar', '', 255,
-        'protocol',  'varchar', '', $char_d,
-        'username',  'varchar', '', $char_d,
-        '_password', 'varchar', '', $char_d,
-      ],
-      'primary_key' => 'snarfnum',
-      'unique' => [],
-      'index'  => [ [ 'svcnum' ] ],
-    },
-
-    'svc_external' => {
-      'columns' => [
-        'svcnum', 'int', '', '',
-        'id',     'int', 'NULL', '',
-        'title',  'varchar', 'NULL', $char_d,
-      ],
-      'primary_key' => 'svcnum',
-      'unique'      => [],
-      'index'       => [],
-    },
-
-    'cust_pay_refund' => {
-      'columns' => [
-        'payrefundnum', 'serial', '', '',
-        'paynum',  'int', '', '',
-        'refundnum',  'int', '', '',
-        '_date',    @date_type,
-        'amount',   @money_type,
-      ],
-      'primary_key' => 'payrefundnum',
-      'unique' => [],
-      'index' => [ ['paynum'], ['refundnum'] ],
-    },
-
-    'part_pkg_option' => {
-      'columns' => [
-        'optionnum', 'serial', '', '',
-        'pkgpart', 'int', '', '',
-        'optionname', 'varchar', '', $char_d,
-        'optionvalue', 'text', 'NULL', '',
-      ],
-      'primary_key' => 'optionnum',
-      'unique'      => [],
-      'index'       => [ [ 'pkgpart' ], [ 'optionname' ] ],
-    },
-
-    'rate' => {
-      'columns' => [
-        'ratenum',  'serial', '', '',
-        'ratename', 'varchar', '', $char_d,
-      ],
-      'primary_key' => 'ratenum',
-      'unique'      => [],
-      'index'       => [],
-    },
-
-    'rate_detail' => {
-      'columns' => [
-        'ratedetailnum',   'serial', '', '',
-        'ratenum',         'int',     '', '',
-        'orig_regionnum',  'int', 'NULL', '',
-        'dest_regionnum',  'int',     '', '',
-        'min_included',    'int',     '', '',
-        'min_charge',      @money_type,
-        'sec_granularity', 'int',     '', '',
-        #time period (link to table of periods)?
-      ],
-      'primary_key' => 'ratedetailnum',
-      'unique'      => [ [ 'ratenum', 'orig_regionnum', 'dest_regionnum' ] ],
-      'index'       => [],
-    },
-
-    'rate_region' => {
-      'columns' => [
-        'regionnum',   'serial',      '', '',
-        'regionname',  'varchar',     '', $char_d,
-      ],
-      'primary_key' => 'regionnum',
-      'unique'      => [],
-      'index'       => [],
-    },
-
-    'rate_prefix' => {
-      'columns' => [
-        'prefixnum',   'serial',    '', '',
-        'regionnum',   'int',       '', '',,
-        'countrycode', 'varchar',     '', 3,
-        'npa',         'varchar', 'NULL', 6,
-        'nxx',         'varchar', 'NULL', 3,
-      ],
-      'primary_key' => 'prefixnum',
-      'unique'      => [],
-      'index'       => [ [ 'countrycode' ], [ 'regionnum' ] ],
-    },
-
-    'reg_code' => {
-      'columns' => [
-        'codenum',   'serial',    '', '',
-        'code',      'varchar',   '', $char_d,
-        'agentnum',  'int',       '', '',
-      ],
-      'primary_key' => 'codenum',
-      'unique'      => [ [ 'agentnum', 'code' ] ],
-      'index'       => [ [ 'agentnum' ] ],
-    },
-
-    'reg_code_pkg' => {
-      'columns' => [
-        'codepkgnum', 'serial', '', '',
-        'codenum',   'int',    '', '',
-        'pkgpart',   'int',    '', '',
-      ],
-      'primary_key' => 'codepkgnum',
-      'unique'      => [ [ 'codenum', 'pkgpart' ] ],
-      'index'       => [ [ 'codenum' ] ],
-    },
-
-    'clientapi_session' => {
-      'columns' => [
-        'sessionnum',  'serial',  '', '',
-        'sessionid',  'varchar',  '', $char_d,
-        'namespace',  'varchar',  '', $char_d,
-      ],
-      'primary_key' => 'sessionnum',
-      'unique'      => [ [ 'sessionid', 'namespace' ] ],
-      'index'       => [],
-    },
-
-    'clientapi_session_field' => {
-      'columns' => [
-        'fieldnum',    'serial',     '', '',
-        'sessionnum',     'int',     '', '',
-        'fieldname',  'varchar',     '', $char_d,
-        'fieldvalue',    'text', 'NULL', '',
-      ],
-      'primary_key' => 'fieldnum',
-      'unique'      => [ [ 'sessionnum', 'fieldname' ] ],
-      'index'       => [],
-    },
-
-  );
-
-  %tables;
-
-}
+1;
 
diff --git a/FS/bin/freeside-upgrade b/FS/bin/freeside-upgrade
new file mode 100755 (executable)
index 0000000..285ff7a
--- /dev/null
@@ -0,0 +1,129 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw($DEBUG $DRY_RUN);
+use Term::ReadKey;
+use DBIx::DBSchema 0.27;
+use FS::UID qw(adminsuidsetup checkeuid datasrc );  #getsecrets);
+use FS::Schema qw( dbdef dbdef_dist reload_dbdef );
+
+
+$DEBUG = 1;
+$DRY_RUN = 0;
+
+
+die "Not running uid freeside!" unless checkeuid();
+
+my $user = shift or die &usage;
+my $dbh = adminsuidsetup($user);
+
+#needs to match FS::Schema...
+my $dbdef_file = "/usr/local/etc/freeside/dbdef.". datasrc;
+
+dbdef_create($dbh, $dbdef_file);
+reload_dbdef($dbdef_file);
+
+
+foreach my $table ( dbdef_dist->tables ) {
+
+  if ( dbdef->table($table) ) {
+
+    warn "$table exists\n" if $DEBUG > 1;
+
+    foreach my $column ( dbdef_dist->table($table)->columns ) {
+      if ( dbdef->table($table)->column($column) )  {
+        warn "  $table.$column exists\n" if $DEBUG > 2;
+      } else {
+
+        if ( $DEBUG ) {
+          print STDERR "column $table.$column does not exist.  create?";
+          next unless yesno();
+        }
+
+        foreach my $statement (
+          dbdef_dist->table($table)->column($column)->sql_add_column( $dbh )
+        ) {
+          warn "$statement\n" if $DEBUG || $DRY_RUN;
+          unless ( $DRY_RUN ) {
+            $dbh->do( $statement)
+              or die "CREATE error: ". $dbh->errstr. "\nexecuting: $statement";
+          }
+        }
+
+      }
+
+    }
+
+    #should eventually check & create missing indices
+
+    #should eventually drop columns not in dbdef_dist...
+
+  } else {
+
+    if ( $DEBUG ) {
+      print STDERR "table $table does not exist.  create?";
+      next unless yesno();
+    }
+
+    foreach my $statement (
+      dbdef_dist->table($table)->sql_create_table( $dbh )
+    ) {
+      warn "$statement\n" if $DEBUG || $DRY_RUN;
+      unless ( $DRY_RUN ) {
+        $dbh->do( $statement)
+          or die "CREATE error: ". $dbh->errstr. "\nexecuting: $statement";
+      }
+    }
+
+  }
+
+}
+
+# should eventually drop tables not in dbdef_dist too i guess...
+
+$dbh->commit or die $dbh->errstr;
+
+dbdef_create($dbh, $dbdef_file);
+
+$dbh->disconnect or die $dbh->errstr;
+
+###
+
+my $all = 0;
+sub yesno {
+  print STDERR ' [yes/no/all] ';
+  if ( $all ) {
+    warn "yes\n";
+    return 1;
+  } else {
+    while ( 1 ) {
+      ReadMode 4;
+      my $x = lc(ReadKey);
+      ReadMode 0;
+      if ( $x eq 'n' ) {
+        warn "no\n"; 
+        return 0;
+      } elsif ( $x eq 'y' ) {
+        warn "yes\n"; 
+        return 1;
+      } elsif ( $x eq 'a' ) {
+        warn "yes\n"; 
+        $all = 1;
+        return 1;
+      }
+    }
+  }
+}
+
+sub dbdef_create { # reverse engineer the schema from the DB and save to file
+  my( $dbh, $file ) = @_;
+  my $dbdef = new_native DBIx::DBSchema $dbh;
+  $dbdef->save($file);
+}
+
+sub usage {
+  die "Usage:\n  freeside-upgrade user\n"; 
+}
+
+1;
+
diff --git a/FS/t/option_Common.t b/FS/t/option_Common.t
new file mode 100644 (file)
index 0000000..ad26141
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::option_Common;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/payment_gateway.t b/FS/t/payment_gateway.t
new file mode 100644 (file)
index 0000000..b91d582
--- /dev/null
@@ -0,0 +1,8 @@
+BEGIN { $| = 1; print "1..1
+" }
+END {print "not ok 1
+" unless $loaded;}
+use FS::payment_gateway;
+$loaded=1;
+print "ok 1
+";
diff --git a/FS/t/payment_gateway_option.t b/FS/t/payment_gateway_option.t
new file mode 100644 (file)
index 0000000..e52b6b4
--- /dev/null
@@ -0,0 +1,8 @@
+BEGIN { $| = 1; print "1..1
+" }
+END {print "not ok 1
+" unless $loaded;}
+use FS::payment_gateway_option;
+$loaded=1;
+print "ok 1
+";
diff --git a/README.1.5.8 b/README.1.5.8
new file mode 100644 (file)
index 0000000..49c3bc0
--- /dev/null
@@ -0,0 +1,6 @@
+
+install DBIx::DBSchema 0.27
+
+install the new version
+run "freeside-upgrade username" to uprade your database schema
+
index 063d01c..26ebeea 100644 (file)
@@ -1,7 +1,16 @@
-FS/bin/freeside-setup
-httemplate/docs/upgrade10.html
-README.1.5.X
-httemplate/docs/schema.html
-for new tables: edit FS/FS.pm, add a new FS/FS/table_name.pm
-                and FS/t/table_name.t, edit FS/MANIFEST
+primarily:
+- edit FS/FS/Schema.pm
+
+if the changes are something other than table and/or column additions:
+- httemplate/docs/upgrade10.html
+- README.1.5.X
+
+for new tables:
+- run bin/generate-table-module tablename
+- edit the resulting FS/FS/table.pm
+
+docs:
+- sorta neglected: FS/FS.pm
+- somehwat neglected: httemplate/docs/schema.html
+- really neglected: httemplate/docs/schema.dia
 
diff --git a/bin/generate-table-module b/bin/generate-table-module
new file mode 100755 (executable)
index 0000000..0baf23d
--- /dev/null
@@ -0,0 +1,87 @@
+#!/usr/bin/perl
+
+use FS::Schema qw( dbdef_dist );
+
+my $table = shift;
+
+###
+# add a new FS/FS/table.pm
+###
+
+my %ut = ( #just guesses
+  'int'     => 'number',
+  'number'  => 'float',
+  'varchar' => 'text',
+  'text'    => 'text',
+);
+
+my $dbdef_table = dbdef_dist->table($table)
+  or die "define table in Schema.pm first";
+my $primary_key = $dbdef_table->primary_key;
+
+open(SRC,"<eg/table_template.pm") or die $!;
+-e "FS/FS/$table.pm" and die "FS/FS/$table.pm already exists!";
+open(DEST,">FS/FS/$table.pm") or die $!;
+
+while (my $line = <SRC>) {
+
+  $line =~ s/table_name/$table/g;
+
+  if ( $line =~ /^=item\s+field\s+-\s+description\s*$/ ) {
+
+    foreach my $column ( $dbdef_table->columns ) {
+      print DEST "=item $column - ";
+      print DEST "primary key"
+        if $column eq $primary_key;
+      print DEST "\n\n";
+    }
+    next;
+
+  } elsif ( $line=~ /^(\s*)\$self->ut_numbern\('primary_key'\)\s*/ ) {
+
+    print DEST "$1\$self->ut_numbern('$primary_key')\n"
+      if $primary_key;
+    next;
+
+  } elsif (
+    $line =~ /^(\s*)\|\|\s+\$self->ut_number\('validate_other_fields'\)\s*/
+  ) {
+
+    foreach my $column ( grep { $_ ne $primary_key } $dbdef_table->columns ) {
+      my $ut = $ut{$dbdef_table->column($column)->type};
+      $ut .= 'n' if $dbdef_table->column($column)->null;
+      print DEST "$1|| \$self->ut_$ut('$column')\n";
+    }
+    next;
+
+  }
+
+  print DEST $line;
+}
+
+close SRC;
+close DEST;
+
+###
+# add FS/t/table.t
+###
+
+open(TEST,">FS/t/$table.t") or die $!;
+print TEST <<ENDTEST;
+BEGIN { \$| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless \$loaded;}
+use FS::$table;
+\$loaded=1;
+print "ok 1\n";
+ENDTEST
+close TEST;
+
+###
+# add them to MANIFEST
+###
+
+open(MANIFEST,">>FS/MANIFEST") or die $!;
+print MANIFEST "FS/$table.pm\n",
+               "t/$table.t\n";
+close MANIFEST;
+
index 39ec898..1979261 100644 (file)
@@ -82,6 +82,8 @@ use FS::msgcat;
 use FS::rate;
 use FS::rate_region;
 use FS::rate_prefix;
+use FS::payment_gateway;
+use FS::agent_payment_gateway;
 
 sub Script_OnStart {
   $Response->AddHeader('Cache-control' => 'no-cache');
index ad671d6..397e426 100644 (file)
@@ -165,6 +165,8 @@ sub handler
       use FS::rate;
       use FS::rate_region;
       use FS::rate_prefix;
+      use FS::payment_gateway;
+      use FS::agent_payment_gateway;
       use FS::XMLRPC;
 
       if ( %%%RT_ENABLED%%% ) {
index fd360ab..e3c4022 100755 (executable)
@@ -36,6 +36,7 @@ full offerings (via their type).<BR><BR>
   <TH>Reports</TH>
   <TH>Registration codes</TH>
   <TH>Prepaid cards</TH>
+  <TH><FONT SIZE=-1>Payment Gateway Overrides</FONT></TH>
   <TH><FONT SIZE=-1>Freq.</FONT></TH>
   <TH><FONT SIZE=-1>Prog.</FONT></TH>
 </TR>
@@ -174,6 +175,30 @@ foreach my $agent ( sort {
           <BR><A HREF="<%=$p%>edit/prepay_credit.cgi?agentnum=<%= $agent->agentnum %>">Generate cards</A>
         </TD>
 
+        <TD>
+          <TABLE CELLSPACING=0 CELLPADDING=0>
+            <% foreach my $override (
+                 # sort { }  want taxclass-full stuff first?  and default cards (empty cardtype)
+                 qsearch('agent_payment_gateway', { 'agentnum' => $agent->agentnum } )
+               ) {
+            %>
+              <TR>
+                <TD> 
+                  <%= $override->cardtype || 'Default' %> to <%= $override->payment_gateway->gateway_module %> (<%= $override->payment_gateway->gateway_username %>)
+                  <%= $override->taxclass
+                        ? ' for '. $override->taxclass. ' only'
+                        : ''
+                  %>
+                  <FONT SIZE=-1><A HREF="<%=$p%>misc/delete-agent_payment_gateway.cgi?<%= 'XXXoverridenum' %>">(delete)</A></FONT>
+                </TD>
+              </TR>
+            <% } %>
+            <TR>
+              <TD><FONT SIZE=-1><A HREF="<%=$p%>edit/agent_payment_gateway.html?agentnum=<%= $agent->agentnum %>">(add override)</A></FONT></TD>
+            </TR>
+          </TABLE>
+        </TD>
+
         <TD><%= $agent->freq %></TD>
         <TD><%= $agent->prog %></TD>
 
diff --git a/httemplate/browse/payment_gateway.html b/httemplate/browse/payment_gateway.html
new file mode 100644 (file)
index 0000000..bb7f315
--- /dev/null
@@ -0,0 +1,70 @@
+<%
+
+  my %search;
+  if ( $cgi->param('showdisabled') ) {
+    %search = ();
+  } else {
+    %search = ( 'disabled' => '' );
+  }
+
+%>
+<%= header('Payment gateways', menubar(
+  'Main Menu' => $p,
+  'Agents'    => $p. 'browse/agent.cgi',
+)) %>
+
+<A HREF="<%= $p %>edit/payment_gateway.html"><I>Add a new payment gateway</I></A><BR><BR>
+
+<%= $cgi->param('showdisabled')
+    ? do { $cgi->param('showdisabled', 0);
+           '( <a href="'. $cgi->self_url. '">hide disabled gateways</a> )'; }
+    : do { $cgi->param('showdisabled', 1);
+           '( <a href="'. $cgi->self_url. '">show disabled gateways</a> )'; }
+%>
+
+<%= table() %>
+<TR>
+  <TH COLSPAN=<%= $cgi->param('showdisabled') ? 1 : 2 %>>#</TH>
+  <TH>Gateway</TH>
+  <TH>Username</TH>
+  <TH>Password</TH>
+  <TH>Action</TH>
+  <TH>Options</TH>
+</TR>
+
+<% foreach my $payment_gateway ( qsearch( 'payment_gateway', \%search ) ) { %>
+
+  <TR>
+    <TD><%= $payment_gateway->gatewaynum %></TD>
+    <% if ( !$cgi->param('showdisabled') ) { %>
+      <TD><%= $payment_gateway->disabled ? 'DISABLED' : '' %></TD>
+    <% } %>
+    <TD><%= $payment_gateway->gateway_module %>
+      <%= !$payment_gateway->disabled 
+            ? '<FONT SIZE="-1"> <A HREF="misc/disable-payment_gateway.cgi?'. $payment_gateway->gatewaynum.'">(disable)</A></FONT>'
+            : ''
+      %>
+    </TD>
+    <TD><%= $payment_gateway->gateway_username %></TD>
+    <TD> - </TD>
+    <TD><%= $payment_gateway->gateway_action %></TD>
+    <TD>
+      <TABLE CELLSPACING=0 CELLPADDING=0>
+        <% my %options = $payment_gateway->options;
+           foreach my $option ( keys %options ) {
+        %>
+          <TR>
+            <TH><%= $option %>:</TH>
+            <TD><%= $options{$option} %></TD>
+          </TR>
+        <% } %>
+      </TABLE>
+    </TD>
+  </TR>
+
+<% } %>
+
+</TABLE>
+</BODY>
+</HTML>
+
index 1faaede..e1eb768 100644 (file)
@@ -5,6 +5,8 @@ NOTE: Version numbering has been simplified.  1.5.7 is the version after
 1.5.0pre6.  It is still a development version - releases with odd numbered 
 middle parts (NN in x.NN.x) are development versions, like Perl or Linux.
 
+If migrating from 1.5.7, see README.1.5.8 instead
+
 If migrating from 1.5.0pre6, see README.1.5.7 instead
 
 install DBD::Pg 1.32, 1.41 or later (not 1.40) (or, if you're using a Perl version before 5.6, you could try installing DBD::Pg 1.22 with <a href="http://420.am/~ivan/DBD-Pg-1.22-fixvercmp.patch">this patch</a> and commenting out the "use DBD::Pg 1.32" at the top of DBIx/DBSchema/DBD/Pg.pm)
@@ -23,326 +25,15 @@ Spreadsheet::WriteExcel, IO-stringy (IO::Scalar), Frontier::RPC
 INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 20, 'svc_external-id', 'en_US', 'External ID' );
 INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 21, 'svc_external-title', 'en_US', 'Title' );
 
-CREATE TABLE cust_bill_pkg_detail (
-  detailnum serial,
-  pkgnum int NOT NULL,
-  invnum int NOT NULL,
-  detail varchar(80),
-  PRIMARY KEY (detailnum)
-);
-CREATE INDEX cust_bill_pkg_detail1 ON cust_bill_pkg_detail ( pkgnum, invnum );
-
-CREATE TABLE part_virtual_field (
-  vfieldpart int NOT NULL,
-  dbtable varchar(32) NOT NULL,
-  name varchar(32) NOT NULL,
-  check_block text,
-  list_source text,
-  length integer,
-  label varchar(80),
-  PRIMARY KEY (vfieldpart)
-);
-
-CREATE TABLE virtual_field (
-  recnum integer NOT NULL,
-  vfieldpart integer NOT NULL,
-  value varchar(128) NOT NULL,
-  PRIMARY KEY (vfieldpart, recnum)
-);
-
-CREATE TABLE router (
-  routernum serial,
-  routername varchar(80),
-  svcnum int,
-  PRIMARY KEY (routernum)
-);
-
-CREATE TABLE part_svc_router (
-  svcrouternum serial,
-  svcpart int NOT NULL,
-  routernum int NOT NULL,
-  PRIMARY KEY (svcrouternum)
-);
-
-CREATE TABLE addr_block (
-  blocknum serial,
-  routernum int NOT NULL,
-  ip_gateway varchar(15) NOT NULL,
-  ip_netmask int NOT NULL,
-  PRIMARY KEY (blocknum)
-);
-CREATE UNIQUE INDEX addr_block1 ON addr_block ( blocknum, routernum );
-
-CREATE TABLE svc_broadband (
-  svcnum int NOT NULL,
-  blocknum int NOT NULL,
-  speed_up int NOT NULL,
-  speed_down int NOT NULL,
-  ip_addr varchar(15),
-  PRIMARY KEY (svcnum)
-);
-
-CREATE TABLE acct_snarf (
-  snarfnum serial,
-  svcnum int NOT NULL,
-  machine varchar(255) NULL,
-  protocol varchar(80) NULL,
-  username varchar(80) NULL,
-  _password varchar(80) NULL,
-  PRIMARY KEY (snarfnum)
-);
-CREATE INDEX acct_snarf1 ON acct_snarf ( svcnum );
-
-CREATE TABLE svc_external (
-  svcnum int NOT NULL,
-  id int,
-  title varchar(80),
-  PRIMARY KEY (svcnum)
-);
-
-<!--
-CREATE TABLE part_pkg_temp (
-    pkgpart serial NOT NULL,
-    pkg varchar(80) NOT NULL,
-    "comment" varchar(80) NOT NULL,
-    setup text NULL,
-    freq varchar(80) NOT NULL,
-    recur text NULL,
-    setuptax char(1) NULL,
-    recurtax char(1) NULL,
-    plan varchar(80) NULL,
-    plandata text NULL,
-    disabled char(1) NULL,
-    taxclass varchar(80) NULL,
-    PRIMARY KEY (pkgpart)
-);
-INSERT INTO part_pkg_temp SELECT * from part_pkg;
-DROP TABLE part_pkg;
-ALTER TABLE part_pkg_temp RENAME TO part_pkg;
-CREATE INDEX part_pkg1 ON part_pkg(disabled);
-
-On modern Pg:
-ALTER TABLE part_pkg DROP CONSTRAINT part_pkg_temp_pkey;
-ALTER TABLE part_pkg ADD PRIMARY KEY (pkgpart);
-select setval('public.part_pkg_temp_pkgpart_seq', ( select max(pkgpart) from part_pkg) );
-
-Or on Pg versions that don't support DROP CONSTRAINT and ADD PRIMARY KEY (tested on 7.1 and 7.2 so far):
-DROP INDEX part_pkg_temp_pkey;
-CREATE UNIQUE INDEX part_pkg_pkey ON part_pkg (pkgpart);
-probably this one?: select setval('part_pkg_temp_pkgpart_seq', ( select max(pkgpart) from part_pkg) );
-probably not this one?: select setval('part_pkg_pkgpart_seq', ( select max(pkgpart) from part_pkg) );
-
-CREATE TABLE h_part_pkg_temp (
-    historynum serial NOT NULL,
-    history_date int,
-    history_user varchar(80) NOT NULL,
-    history_action varchar(80) NOT NULL,
-    pkgpart int NOT NULL,
-    pkg varchar(80) NOT NULL,
-    "comment" varchar(80) NOT NULL,
-    setup text NULL,
-    freq varchar(80) NOT NULL,
-    recur text NULL,
-    setuptax char(1) NULL,
-    recurtax char(1) NULL,
-    plan varchar(80) NULL,
-    plandata text NULL,
-    disabled char(1) NULL,
-    taxclass varchar(80) NULL,
-    PRIMARY KEY (historynum)
-);
-INSERT INTO h_part_pkg_temp SELECT * from h_part_pkg;
-DROP TABLE h_part_pkg;
-ALTER TABLE h_part_pkg_temp RENAME TO h_part_pkg;
-CREATE INDEX h_part_pkg1 ON h_part_pkg(disabled);
-
-On modern Pg:
-ALTER TABLE h_part_pkg DROP CONSTRAINT h_part_pkg_temp_pkey;
-ALTER TABLE h_part_pkg ADD PRIMARY KEY (historynum);
-select setval('public.h_part_pkg_temp_historynum_seq', ( select max(historynum) from h_part_pkg) );
-
-Or on Pg versions that don't support DROP CONSTRAINT and ADD PRIMARY KEY (tested on 7.1 and 7.2 so far):
-DROP INDEX h_part_pkg_temp_pkey;
-CREATE UNIQUE INDEX h_part_pkg_pkey ON h_part_pkg (historynum);
-probably this one?: select setval('h_part_pkg_temp_historynum_seq', ( select max(historynum) from h_part_pkg) );
-probably not this one?: select setval('h_part_pkg_historynum_seq', ( select max(historynum) from h_part_pkg) );
--->ALTER TABLE part_pkg ADD COLUMN taxclass varchar(80) NULL;
-ALTER TABLE h_part_pkg ADD COLUMN taxclass varchar(80) NULL;
-
-CREATE TABLE cust_pay_refund (
-    payrefundnum serial NOT NULL,
-    paynum int NOT NULL,
-    refundnum int NOT NULL,
-    _date int NOT NULL,
-    amount decimal(10,2) NOT NULL,
-    PRIMARY KEY (payrefundnum)
-);
-CREATE INDEX cust_pay_refund1 ON cust_pay_refund(paynum);
-CREATE INDEX cust_pay_refund2 ON cust_pay_refund(refundnum);
-
-CREATE TABLE cust_pay_void (
-  paynum int NOT NULL,
-  custnum int NOT NULL,
-  paid decimal(10,2) NOT NULL,
-  _date int,
-  payby char(4) NOT NULL,
-  payinfo varchar(80),
-  paybatch varchar(80),
-  closed char(1),
-  void_date int,
-  reason varchar(80),
-  otaker varchar(32) NOT NULL,
-  PRIMARY KEY (paynum)
-);
-CREATE INDEX cust_pay_void1 ON cust_pay_void(custnum);
-
-CREATE TABLE part_pkg_option (
-  optionnum serial,
-  pkgpart int not null,
-  optionname varchar(80) not null,
-  optionvalue text NULL,
-  PRIMARY KEY (optionnum)
-);
-CREATE INDEX part_pkg_option1 ON part_pkg_option ( pkgpart );
-CREATE INDEX part_pkg_option2 ON part_pkg_option ( optionname );
-
-CREATE TABLE rate (
-    ratenum serial NOT NULL,
-    ratename varchar(80) NOT NULL,
-    PRIMARY KEY (ratenum)
-);
-
-CREATE TABLE rate_detail (
-    ratedetailnum serial NOT NULL,
-    ratenum int NOT NULL,
-    orig_regionnum int NULL,
-    dest_regionnum int NOT NULL,
-    min_included int NOT NULL,
-    min_charge decimal(10,2) NOT NULL,
-    sec_granularity int NOT NULL,
-    PRIMARY KEY (ratedetailnum)
-);
-CREATE UNIQUE INDEX rate_detail1 ON rate_detail ( ratenum, orig_regionnum, dest_regionnum );
-
-CREATE TABLE rate_region (
-    regionnum serial NOT NULL,
-    regionname varchar(80) NOT NULL,
-    PRIMARY KEY (regionnum)
-);
-
-CREATE TABLE rate_prefix (
-    prefixnum serial NOT NULL,
-    regionnum int NOT NULL, 
-    countrycode varchar(3) NOT NULL,
-    npa varchar(6) NULL,
-    nxx varchar(3) NULL,
-    PRIMARY KEY (prefixnum)
-);
-CREATE INDEX rate_prefix1 ON rate_prefix ( countrycode );
-CREATE INDEX rate_prefix2 ON rate_prefix ( regionnum );
-
-CREATE TABLE reg_code (
-    codenum serial NOT NULL,
-    code varchar(80) NOT NULL,
-    agentnum int NOT NULL,
-    PRIMARY KEY (codenum)
-);
-CREATE UNIQUE INDEX reg_code1 ON reg_code ( agentnum, code );
-CREATE INDEX reg_code2 ON reg_code ( agentnum );
-
-CREATE TABLE reg_code_pkg (
-    codepkgnum serial,
-    codenum int NOT NULL,
-    pkgpart int NOT NULL,
-    PRIMARY KEY (codepkgnum)
-);
-CREATE UNIQUE INDEX reg_code_pkg1 ON reg_code_pkg ( codenum, pkgpart );
-CREATE INDEX reg_code_pkg2 ON reg_code_pkg ( codenum );
-
-CREATE TABLE clientapi_session (
-    sessionnum serial NOT NULL,
-    sessionid varchar(80) NOT NULL,
-    namespace varchar(80) NOT NULL,
-    PRIMARY KEY (sessionnum)
-);
-CREATE UNIQUE INDEX clientapi_session1 ON clientapi_session ( sessionid, namespace );
-
-CREATE TABLE clientapi_session_field (
-    fieldnum serial NOT NULL,
-    sessionnum int NOT NULL,
-    fieldname varchar(80) NOT NULL,
-    fieldvalue text NULL,
-    PRIMARY KEY (fieldnum)
-);
-CREATE UNIQUE INDEX clientapi_session_field1 ON clientapi_session_field ( sessionnum, fieldname );
-
 DROP INDEX cust_bill_pkg1;
 
-ALTER TABLE cust_bill_pkg ADD itemdesc varchar(80) NULL;
-ALTER TABLE h_cust_bill_pkg ADD itemdesc varchar(80) NULL;
-ALTER TABLE cust_main_county ADD taxname varchar(80) NULL;
-ALTER TABLE h_cust_main_county ADD taxname varchar(80) NULL;
-ALTER TABLE cust_main_county ADD setuptax char(1) NULL;
-ALTER TABLE h_cust_main_county ADD setuptax char(1) NULL;
-ALTER TABLE cust_main_county ADD recurtax char(1) NULL;
-ALTER TABLE h_cust_main_county ADD recurtax char(1) NULL;
-ALTER TABLE cust_pkg ADD last_bill int NULL;
-ALTER TABLE h_cust_pkg ADD last_bill int NULL;
-ALTER TABLE agent ADD disabled char(1) NULL;
-ALTER TABLE h_agent ADD disabled char(1) NULL;
-ALTER TABLE agent ADD username varchar(80) NULL;
-ALTER TABLE h_agent ADD username varchar(80) NULL;
-ALTER TABLE agent ADD _password varchar(80) NULL;
-ALTER TABLE h_agent ADD _password varchar(80) NULL;
-ALTER TABLE cust_main ADD paycvv varchar(512) NULL;
-ALTER TABLE h_cust_main ADD paycvv varchar(512) NULL;
-ALTER TABLE cust_main ADD paymask varchar(80) NULL;
-ALTER TABLE h_cust_main ADD paymask varchar(80) NULL;
-ALTER TABLE part_referral ADD disabled char(1) NULL;
-ALTER TABLE h_part_referral ADD disabled char(1) NULL;
-CREATE INDEX part_referral1 ON part_referral ( disabled );
-ALTER TABLE pkg_svc ADD primary_svc char(1) NULL;
-ALTER TABLE h_pkg_svc ADD primary_svc char(1) NULL;
-ALTER TABLE svc_forward ADD src varchar(255) NULL;
-ALTER TABLE h_svc_forward ADD src varchar(255) NULL;
-ALTER TABLE part_pkg ADD promo_code varchar(80) NULL;
-ALTER TABLE h_part_pkg ADD promo_code varchar(80) NULL;
-CREATE INDEX part_pkg2 ON part_pkg ( promo_code );
-CREATE INDEX h_part_pkg2 ON h_part_pkg ( promo_code );
-ALTER TABLE prepay_credit ADD agentnum integer NULL;
-ALTER TABLE h_prepay_credit ADD agentnum integer NULL;
-
-ALTER TABLE type_pkgs ADD typepkgnum int;
-ALTER TABLE type_pkgs ALTER COLUMN typepkgnum SET DEFAULT nextval('public.type_pkgs_typepkgnum_seq'::text);
-CREATE SEQUENCE type_pkgs_typepkgnum_seq;
-UPDATE type_pkgs SET typepkgnum = nextval('public.type_pkgs_typepkgnum_seq'::text) WHERE typepkgnum IS NULL;
-ALTER TABLE type_pkgs ALTER typepkgnum SET NOT NULL;
-ALTER TABLE type_pkgs ADD PRIMARY KEY (typepkgnum);
-ALTER TABLE h_type_pkgs ADD typepkgnum int;
-
-ALTER TABLE cust_bill_pkg ADD billpkgnum int;
-ALTER TABLE cust_bill_pkg ALTER COLUMN billpkgnum SET DEFAULT nextval('public.cust_bill_pkg_billpkgnum_seq'::text);
-CREATE SEQUENCE cust_bill_pkg_billpkgnum_seq;
-UPDATE cust_bill_pkg SET billpkgnum = nextval('public.cust_bill_pkg_billpkgnum_seq'::text) WHERE billpkgnum IS NULL;
-ALTER TABLE cust_bill_pkg ALTER billpkgnum SET NOT NULL;
-ALTER TABLE cust_bill_pkg ADD PRIMARY KEY (billpkgnum);
-ALTER TABLE h_cust_bill_pkg ADD billpkgnum int;
-
-ALTER TABLE pkg_svc ADD pkgsvcnum int;
-ALTER TABLE pkg_svc ALTER COLUMN pkgsvcnum SET DEFAULT nextval('public.pkg_svc_pkgsvcnum_seq'::text);
-CREATE SEQUENCE pkg_svc_pkgsvcnum_seq;
-UPDATE pkg_svc SET pkgsvcnum = nextval('public.pkg_svc_pkgsvcnum_seq'::text) WHERE pkgsvcnum IS NULL;
-ALTER TABLE pkg_svc ALTER pkgsvcnum SET NOT NULL;
-ALTER TABLE pkg_svc ADD PRIMARY KEY (pkgsvcnum);
-ALTER TABLE h_pkg_svc ADD pkgsvcnum int;
-
 On recent Pg versions:
 
 ALTER TABLE cust_main ALTER COLUMN payinfo varchar(512) NULL;
 ALTER TABLE h_cust_main ALTER COLUMN payinfo varchar(512) NULL;
 
 Or on older Pg versions that don't support altering columns directly:
-
+(write me)
 
 On recent Pg versions:
 
@@ -368,25 +59,25 @@ If you created your database with a version before 1.4.2, dump database, edit:
 - domain_record and h_domain_record: increase recdata from 80 to 255
 then reload
 
-optionally:
-
-  CREATE INDEX cust_main6 ON cust_main ( daytime );
-  CREATE INDEX cust_main7 ON cust_main ( night );
-  CREATE INDEX cust_main8 ON cust_main ( fax );
-  CREATE INDEX cust_main9 ON cust_main ( ship_daytime );
-  CREATE INDEX cust_main10 ON cust_main ( ship_night );
-  CREATE INDEX cust_main11 ON cust_main ( ship_fax );
-  CREATE INDEX agent2 ON agent ( disabled );
-  CREATE INDEX part_bill_event2 ON part_bill_event ( disabled );
-  CREATE INDEX cust_pay4 ON cust_pay (_date);
+mandatory again:
 
-  serial columns
+make install-perl-modules to install the new libraries and CLI utilities
+run "freeside-upgrade username" to create the remaining new tables and columns
 
-mandatory again:
+optionally:
 
-dbdef-create username
-create-history-tables username cust_bill_pkg_detail router part_svc_router addr_block svc_broadband acct_snarf svc_external cust_pay_refund cust_pay_void part_pkg_option rate rate_detail rate_region rate_prefix reg_code reg_code_pkg
-dbdef-create username
+CREATE INDEX cust_main6 ON cust_main ( daytime );
+CREATE INDEX cust_main7 ON cust_main ( night );
+CREATE INDEX cust_main8 ON cust_main ( fax );
+CREATE INDEX cust_main9 ON cust_main ( ship_daytime );
+CREATE INDEX cust_main10 ON cust_main ( ship_night );
+CREATE INDEX cust_main11 ON cust_main ( ship_fax );
+CREATE INDEX agent2 ON agent ( disabled );
+CREATE INDEX part_bill_event2 ON part_bill_event ( disabled );
+CREATE INDEX cust_pay4 ON cust_pay (_date);
+CREATE INDEX part_referral1 ON part_referral ( disabled );
+CREATE INDEX part_pkg2 ON part_pkg ( promo_code );
+CREATE INDEX h_part_pkg2 ON h_part_pkg ( promo_code );
 
 apache - fix <Files> sections to include .html also
 
diff --git a/httemplate/edit/agent_payment_gateway.html b/httemplate/edit/agent_payment_gateway.html
new file mode 100644 (file)
index 0000000..61d29e0
--- /dev/null
@@ -0,0 +1,64 @@
+<%
+
+$cgi->param('agentnum') =~ /(\d+)$/ or die "illegal agentnum";
+my $agent = qsearchs('agent', { 'agentnum' => $1 } );
+die "agentnum $1 not found" unless $agent;
+
+#my @agent_payment_gateway;
+if ( $cgi->param('error') ) {
+}
+
+my $action = 'Add';
+
+%>
+
+<%= header("$action payment gateway override for ". $agent->agent,  menubar(
+  'Main Menu' => $p,
+  #'View all payment gateways' => $p. 'browse/payment_gateway.html',
+  'View all agents' => $p. 'browse/agent.html',
+)) %>
+
+<% if ( $cgi->param('error') ) { %>
+<FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT>
+<% } %>
+
+<FORM ACTION="<%=popurl(1)%>process/agent_payment_gateway.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agent->agentnum %>">
+
+Use gateway <SELECT NAME="gatewaynum">
+<% foreach my $payment_gateway (
+      qsearch('payment_gateway', { 'disabled' => '' } )
+    ) {
+%>
+  <OPTION VALUE="<%= $payment_gateway->gatewaynum %>"><%= $payment_gateway->gateway_module %> (<%= $payment_gateway->gateway_username %>)
+<% } %>
+</SELECT>
+<BR><BR>
+
+for <SELECT NAME="cardtype" MULTIPLE>
+<% foreach my $cardtype (
+  "",
+  "VISA card",
+  "MasterCard",
+  "Discover card",
+  "American Express card",
+  "Diner's Club/Carte Blanche",
+  "enRoute",
+  "JCB",
+  "BankCard",
+  "Switch",
+  "Solo",
+  'ACH',
+) { %>
+  <OPTION VALUE="<%= $cardtype %>"><%= $cardtype || '(Default fallback)' %>
+<% } %>
+</SELECT>
+<BR><BR>
+
+(optional) when invoice contains only items of taxclass <INPUT TYPE="text" NAME="taxclass">
+<BR><BR>
+
+<INPUT TYPE="submit" VALUE="Add gateway override">
+</FORM>
+</BODY>
+</HTML>
diff --git a/httemplate/edit/payment_gateway.html b/httemplate/edit/payment_gateway.html
new file mode 100644 (file)
index 0000000..837d0c4
--- /dev/null
@@ -0,0 +1,108 @@
+<%
+
+my $payment_gateway;
+if ( $cgi->param('error') ) {
+  $payment_gateway = new FS::payment_gateway ( {
+    map { $_, scalar($cgi->param($_)) } fields('payment_gateway')
+  } );
+} elsif ( $cgi->keywords ) {
+  my($query) = $cgi->keywords;
+  $query =~ /^(\d+)$/;
+  $payment_gateway = qsearchs( 'payment_gateway', { 'gatewaynum' => $1 } );
+} else { #adding
+  $payment_gateway = new FS::payment_gateway {};
+}
+my $action = $payment_gateway->gatewaynum ? 'Edit' : 'Add';
+#my $hashref = $payment_gateway->hashref;
+
+%>
+
+<%= header("$action Payment gateway", menubar(
+  'Main Menu' => $p,
+  'View all payment gateways' => $p. 'browse/payment_gateway.html',
+)) %>
+
+<% if ( $cgi->param('error') ) { %>
+<FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT>
+<% } %>
+
+<FORM ACTION="<%=popurl(1)%>process/payment_gateway.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="gatewaynum" VALUE="<%= $payment_gateway->gatewaynum %>">
+Gateway #<%= $payment_gateway->gatewaynum || "(NEW)" %>
+
+<%= ntable('#cccccc', 2, '') %>
+
+<TR>
+  <TH ALIGN="right">Gateway: </TH>
+  <TD><SELECT NAME="gateway_module" SIZE=1>
+    <% foreach my $module ( qw(
+         2CheckOut
+         AuthorizeNet
+         BankOfAmerica
+         Beanstream
+         Capstone
+         Cardstream
+         CashCow
+         CyberSource
+         eSec
+         eSelectPlus
+         Exact
+         iAuthorizer
+         IPaymentTPG
+         Jettis
+         LinkPoint
+         MerchantCommerce
+         Network1Financial
+         OCV
+         OpenECHO
+         PayConnect
+         PayflowPro
+         PaymentsGateway
+         PXPost
+         SecureHostingUPG
+         Skipjack
+         StGeorge
+         SurePay
+         TCLink
+         VirtualNet
+       ) ) {
+    %>
+      <OPTION VALUE="<%= $module %>"><%= $module %>
+    <% } %>
+    </SELECT>
+  </TD>
+</TR>
+
+<TR>
+  <TH ALIGN="right">Username: </TH>
+  <TD><INPUT TYPE="text" NAME="gateway_username"></TD>
+</TR>
+
+<TR>
+  <TH ALIGN="right">Password: </TH>
+  <TD><INPUT TYPE="text" NAME="gateway_password"></TD>
+</TR>
+
+<TR>
+  <TH ALIGN="right">Action: </TH>
+  <TD>
+    <SELECT NAME="gateway_action" SIZE=1>
+      <OPTION VALUE="Normal Authorization">Normal Authorization
+      <OPTION VALUE="Authorization Only">Authorization Only
+      <OPTION VALUE="Authorization Only, Post Authorization">Authorization Only, Post Authorization
+    </SELECT>
+  </TD>
+</TR>
+
+<TR>
+  <TH ALIGN="right">Options: </TH>
+  <TD><TEXTAREA ROWS="5" NAME="gateway_options"></TEXTAREA></TD>
+</TR>
+
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="<%= $payment_gateway->gatewaynum ? "Apply changes" : "Add gateway" %>">
+    </FORM>
+  </BODY>
+</HTML>
+
index 067fec5..82a0281 100644 (file)
         </ul>
       <A NAME="admin_billing">Billing</a>
         <ul>
+          <LI><A HREF="browse/payment_gateway.html">View/Edit payment gateways</A>
+            - Credit card and electronic check processors
           <LI><A HREF="browse/part_bill_event.cgi">View/Edit invoice events</A>
             - Actions for overdue invoices
           <LI><A HREF="search/prepay_credit.html">View/Edit prepaid cards</A>