for exports with per-service machine selection, make sure there is always a machine...
authorMark Wells <mark@freeside.biz>
Wed, 17 Apr 2013 04:12:17 +0000 (21:12 -0700)
committerMark Wells <mark@freeside.biz>
Wed, 17 Apr 2013 04:12:17 +0000 (21:12 -0700)
FS/FS/Schema.pm
FS/FS/export_svc.pm
FS/FS/part_export.pm
FS/FS/svc_export_machine.pm
httemplate/edit/part_export.cgi
httemplate/edit/process/part_export.cgi

index bbf3b42..9f68a41 100644 (file)
@@ -2729,9 +2729,10 @@ sub tables_hashref {
       'columns' => [
         'exportnum',   'serial',     '',      '', '', '', 
         'exportname', 'varchar', 'NULL', $char_d, '', '',
-        'machine',    'varchar', 'NULL', $char_d, '', '', 
+        'machine',    'varchar', 'NULL', $char_d, '', '',
         'exporttype', 'varchar',     '', $char_d, '', '', 
         'nodomain',      'char', 'NULL',       1, '', '', 
+        'default_machine','int', 'NULL',      '', '', '',
       ],
       'primary_key' => 'exportnum',
       'unique'      => [],
index 0370f5f..b08f8f7 100644 (file)
@@ -5,6 +5,7 @@ use vars qw( @ISA );
 use FS::Record qw( qsearch qsearchs dbh );
 use FS::part_export;
 use FS::part_svc;
+use FS::svc_export_machine;
 
 @ISA = qw(FS::Record);
 
@@ -209,6 +210,19 @@ sub insert {
   } #end of duplicate check, whew
 
   $error = $self->SUPER::insert;
+
+  my $part_export = $self->part_export;
+  if ( !$error and $part_export->default_machine ) {
+    foreach my $cust_svc ( $self->part_svc->cust_svc ) {
+      my $svc_export_machine = FS::svc_export_machine->new({
+          'exportnum'   => $self->exportnum,
+          'svcnum'      => $cust_svc->svcnum,
+          'machinenum'  => $part_export->default_machine,
+      });
+      $error ||= $svc_export_machine->insert;
+    }
+  }
+
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
     return $error;
@@ -251,7 +265,23 @@ Delete this record from the database.
 
 =cut
 
-# the delete method can be inherited from FS::Record
+sub delete {
+  my $self = shift;
+  my $dbh = dbh;
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+
+  my $error = $self->SUPER::delete;
+  foreach ($self->svc_export_machine) {
+    $error ||= $_->delete;
+  }
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+}
+
 
 =item replace OLD_RECORD
 
@@ -307,6 +337,24 @@ sub part_svc {
   qsearchs( 'part_svc', { 'svcpart' => $self->svcpart } );
 }
 
+=item svc_export_machine
+
+Returns all export hostname records (L<FS::svc_export_machine>) for this
+combination of svcpart and exportnum.
+
+=cut
+
+sub svc_export_machine {
+  my $self = shift;
+  qsearch({
+    'table'     => 'svc_export_machine',
+    'select'    => 'svc_export_machine.*',
+    'addl_from' => 'JOIN cust_svc USING (svcnum)',
+    'hashref'   => { 'exportnum' => $self->exportnum },
+    'extra_sql' => ' AND cust_svc.svcpart = '.$self->svcpart,
+  });
+}
+
 =back
 
 =head1 BUGS
index 15ce9c0..28cb141 100644 (file)
@@ -125,31 +125,14 @@ sub insert {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
-  my $error = $self->SUPER::insert(@_);
+  my $error = $self->SUPER::insert(@_)
+           || $self->replace;
+  # use replace to do all the part_export_machine and default_machine stuff
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
     return $error;
   }
 
-  #kinda false laziness with process_m2name
-  my @machines = map { $_ =~ s/^\s+//; $_ =~ s/\s+$//; $_ }
-                   grep /\S/,
-                     split /[\n\r]{1,2}/,
-                       $self->part_export_machine_textarea;
-
-  foreach my $machine ( @machines ) {
-
-    my $part_export_machine = new FS::part_export_machine {
-      'exportnum' => $self->exportnum,
-      'machine'   => $machine,
-    };
-    $error = $part_export_machine->insert;
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return $error;
-    }
-  }
-
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
   '';
 }
@@ -217,6 +200,7 @@ or modified.
 
 sub replace {
   my $self = shift;
+  my $old = $self->replace_old;
 
   local $SIG{HUP} = 'IGNORE';
   local $SIG{INT} = 'IGNORE';
@@ -228,12 +212,7 @@ sub replace {
   my $oldAutoCommit = $FS::UID::AutoCommit;
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
-
-  my $error = $self->SUPER::replace(@_);
-  if ( $error ) {
-    $dbh->rollback if $oldAutoCommit;
-    return $error;
-  }
+  my $error;
 
   if ( $self->part_export_machine_textarea ) {
 
@@ -258,6 +237,10 @@ sub replace {
           }
         }
 
+        if ( $self->default_machine_name eq $machine ) {
+          $self->default_machine( $part_export_machine{$machine}->machinenum );
+        }
+
         delete $part_export_machine{$machine}; #so we don't disable it below
 
       } else {
@@ -272,11 +255,13 @@ sub replace {
           return $error;
         }
   
+        if ( $self->default_machine_name eq $machine ) {
+          $self->default_machine( $part_export_machine->machinenum );
+        }
       }
 
     }
 
-
     foreach my $part_export_machine ( values %part_export_machine ) {
       $part_export_machine->disabled('Y');
       $error = $part_export_machine->replace;
@@ -286,6 +271,48 @@ sub replace {
       }
     }
 
+    if ( $old->machine ne '_SVC_MACHINE' ) {
+      # then set up the default for any already-attached export_svcs
+      foreach my $export_svc ( $self->export_svc ) {
+        my @svcs = qsearch('cust_svc', { 'svcpart' => $export_svc->svcpart });
+        foreach my $cust_svc ( @svcs ) {
+          my $svc_export_machine = FS::svc_export_machine->new({
+              'exportnum'   => $self->exportnum,
+              'svcnum'      => $cust_svc->svcnum,
+              'machinenum'  => $self->default_machine,
+          });
+          $error ||= $svc_export_machine->insert;
+        }
+      }
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return $error;
+      }
+    } # if switching to selectable hosts
+
+  } elsif ( $old->machine eq '_SVC_MACHINE' ) {
+    # then we're switching from selectable to non-selectable
+    foreach my $svc_export_machine (
+      qsearch('svc_export_machine', { 'exportnum' => $self->exportnum })
+    ) {
+      $error ||= $svc_export_machine->delete;
+    }
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+
+  }
+
+  $error = $self->SUPER::replace(@_);
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  if ( $self->machine eq '_SVC_MACHINE' and ! $self->default_machine ) {
+    $dbh->rollback if $oldAutoCommit;
+    return "no default export host selected";
   }
 
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
@@ -308,6 +335,13 @@ sub check {
     || $self->ut_domainn('machine')
     || $self->ut_alpha('exporttype')
   ;
+
+  if ( $self->machine eq '_SVC_MACHINE' ) {
+    $error ||= $self->ut_numbern('default_machine')
+  } else {
+    $self->set('default_machine', '');
+  }
+
   return $error if $error;
 
   $self->nodomain =~ /^(Y?)$/ or return "Illegal nodomain: ". $self->nodomain;
@@ -471,7 +505,9 @@ sub _rebless {
   $self;
 }
 
-=item svc_machine
+=item svc_machine SVC_X
+
+Return the export hostname for SVC_X.
 
 =cut
 
@@ -483,14 +519,33 @@ sub svc_machine {
   my $svc_export_machine = qsearchs('svc_export_machine', {
     'svcnum'    => $svc_x->svcnum,
     'exportnum' => $self->exportnum,
-  })
-    #would only happen if you add this export to existing services without a
-    #machine set then try to run exports without setting it... right?
-    or die "No hostname selected for ".($self->exportname || $self->exporttype);
+  });
+
+  if (!$svc_export_machine) {
+    warn "No hostname selected for ".($self->exportname || $self->exporttype);
+    return $self->default_export_machine->machine;
+  }
 
   return $svc_export_machine->part_export_machine->machine;
 }
 
+=item default_export_machine
+
+Return the default export hostname for this export.
+
+=cut
+
+sub default_export_machine {
+  my $self = shift;
+  my $machinenum = $self->default_machine;
+  if ( $machinenum ) {
+    my $default_machine = FS::part_export_machine->by_key($machinenum);
+    return $default_machine->machine if $default_machine;
+  }
+  # this should not happen
+  die "no default export hostname for export ".$self->exportnum;
+}
+
 #these should probably all go away, just let the subclasses define em
 
 =item export_insert SVC_OBJECT
@@ -703,6 +758,55 @@ sub _upgrade_data {  #class method
     $error = $opt->replace;
     die $error if $error;
   }
+  # for exports that have selectable hostnames, make sure all services
+  # have a hostname selected
+  foreach my $part_export (
+    qsearch('part_export', { 'machine' => '_SVC_MACHINE' })
+  ) {
+
+    my $exportnum = $part_export->exportnum;
+    my $machinenum = $part_export->default_machine;
+    if (!$machinenum) {
+      my ($first) = $part_export->part_export_machine;
+      if (!$first) {
+        # user intervention really is required.
+        die "Export $exportnum has no hostname options defined.\n".
+            "You must correct this before upgrading.\n";
+      }
+      # warn about this, because we might not choose the right one
+      warn "Export $exportnum (". $part_export->exporttype.
+           ") has no default hostname.  Setting to ".$first->machine."\n";
+      $machinenum = $first->machinenum;
+      $part_export->set('default_machine', $machinenum);
+      my $error = $part_export->replace;
+      die $error if $error;
+    }
+
+    # the service belongs to a service def that uses this export
+    # and there is not a hostname selected for this export for that service
+    my $join = ' JOIN export_svc USING ( svcpart )'.
+               ' LEFT JOIN svc_export_machine'.
+               ' ON ( cust_svc.svcnum = svc_export_machine.svcnum'.
+               ' AND export_svc.exportnum = svc_export_machine.exportnum )';
+
+    my @svcs = qsearch( {
+          'select'    => 'cust_svc.*',
+          'table'     => 'cust_svc',
+          'addl_from' => $join,
+          'extra_sql' => ' WHERE svcexportmachinenum IS NULL'.
+                         ' AND export_svc.exportnum = '.$part_export->exportnum,
+      } );
+    foreach my $cust_svc (@svcs) {
+      my $svc_export_machine = FS::svc_export_machine->new({
+          'exportnum'   => $exportnum,
+          'machinenum'  => $machinenum,
+          'svcnum'      => $cust_svc->svcnum,
+      });
+      my $error = $svc_export_machine->insert;
+      die $error if $error;
+    }
+  }
+
   # pass downstream
   my %exports_in_use;
   $exports_in_use{ref $_} = 1 foreach qsearch('part_export', {});
index 10f7b68..7ca20cc 100644 (file)
@@ -40,6 +40,10 @@ fields are currently supported:
 
 primary key
 
+=item exportnum
+
+Export definition, see L<FS::part_export>
+
 =item svcnum
 
 Customer service, see L<FS::cust_svc>
index 4dd253b..2897cf3 100644 (file)
@@ -2,6 +2,34 @@
 
 <% include('/elements/error.html') %>
 
+<SCRIPT TYPE="text/javascript">
+  function svc_machine_changed (what, layer) {
+    if ( what.checked ) {
+      var machine = document.getElementById(layer + "_machine");
+      var part_export_machine = 
+        document.getElementById(layer + "_part_export_machine");
+      if ( what.value == 'Y' ) {
+        machine.disabled = true;
+        part_export_machine.disabled = false;
+      } else if ( what.value == 'N' ) {
+        machine.disabled = false;
+        part_export_machine.disabled = true;
+      }
+    }
+  }
+
+  function part_export_machine_changed (what, layer) {
+    var select_default = document.getElementById(layer + '_default_machine');
+    var selected = select_default.value;
+    select_default.options.length = 0;
+    var choices = what.value.split("\n");
+    for (var i = 0; i < choices.length; i++) {
+      select_default.options[i] = new Option(choices[i]);
+    }
+    select_default.value = selected;
+  }
+
+</SCRIPT>
 <FORM NAME="dummy">
 <INPUT TYPE="hidden" NAME="exportnum" VALUE="<% $part_export->exportnum %>">
 
@@ -58,7 +86,6 @@ my $widget = new HTML::Widgets::SelectLayers(
   'form_name'      => 'dummy',
   'form_action'    => 'process/part_export.cgi',
   'form_text'      => [qw( exportnum exportname )],
-#  'form_checkbox'  => [qw()],
   'html_between'    => "</TD></TR></TABLE>\n",
   'layer_callback'  => sub {
     my $layer = shift;
@@ -87,7 +114,8 @@ my $widget = new HTML::Widgets::SelectLayers(
         if ( $exports->{$layer}{svc_machine} ) {
           my( $N_CHK, $Y_CHK) = ( 'CHECKED', '' );
           my( $machine_DISABLED, $pem_DISABLED) = ( '', 'DISABLED' );
-          my $part_export_machine = '';
+          my @part_export_machine;
+          my $default_machine = '';
           if ( $cgi->param('svc_machine') eq 'Y'
                  || $machine eq '_SVC_MACHINE'
              )
@@ -97,38 +125,43 @@ my $widget = new HTML::Widgets::SelectLayers(
             $machine_DISABLED = 'DISABLED';
             $pem_DISABLED = '';
             $machine = '';
-            $part_export_machine =
-              $cgi->param('part_export_machine')
-              || join "\n",
+            @part_export_machine = $cgi->param('part_export_machine');
+            if (!@part_export_machine) {
+              @part_export_machine = 
                    map $_->machine,
                      grep ! $_->disabled,
                        $part_export->part_export_machine;
+            }
+            $default_machine =
+              $cgi->param('default_machine_name')
+              || $part_export->default_export_machine;
           }
-          my $oc = qq(onChange="${layer}_svc_machine_changed(this)");
+          my $oc = qq(onChange="svc_machine_changed(this, '$layer')");
           $html .= qq[
             <INPUT TYPE="radio" NAME="svc_machine" VALUE="N" $N_CHK $oc>
             <INPUT TYPE="text" NAME="machine" ID="${layer}_machine" VALUE="$machine" $machine_DISABLED>
             <BR>
             <INPUT TYPE="radio" NAME="svc_machine" VALUE="Y" $Y_CHK $oc>
-            Selected in each customer service from these choices
-            <TEXTAREA NAME="part_export_machine" ID="${layer}_part_export_machine" $pem_DISABLED>$part_export_machine</TEXTAREA>
-
-            <SCRIPT TYPE="text/javascript">
-              function ${layer}_svc_machine_changed (what) {
-                if ( what.checked ) {
-                  var machine = document.getElementById("${layer}_machine");
-                  var part_export_machine = document.getElementById("${layer}_part_export_machine");
-                  if ( what.value == 'Y' ) {
-                    machine.disabled = true;
-                    part_export_machine.disabled = false;
-                  } else if ( what.value == 'N' ) {
-                    machine.disabled = false;
-                    part_export_machine.disabled = true;
-                  }
-                }
-              }
-            </SCRIPT>
+            <DIV STYLE="display:inline-block; vertical-align: top; text-align: right">
+              Selected in each customer service from these choices:
+              <TEXTAREA STYLE="vertical-align: top" NAME="part_export_machine"
+                ID="${layer}_part_export_machine"
+                onchange="part_export_machine_changed(this, '$layer')"
+                $pem_DISABLED>] .
+                
+                join("\n", @part_export_machine) .
+                
+                qq[</TEXTAREA>
+              <BR>
+              Default: 
+              <SELECT NAME="default_machine_name" ID="${layer}_default_machine">
           ];
+          foreach (@part_export_machine) {
+            $_ = encode_entities($_); # oh noes, XSS
+            my $sel = ($default_machine eq $_) ? ' SELECTED' : '';
+            $html .= qq!<OPTION VALUE="$_"$sel>$_</OPTION>\n!;
+          }
+          $html .= '</DIV></SELECT>'
         } else {
           $html .= qq(<INPUT TYPE="text" NAME="machine" VALUE="$machine">).
                      '<INPUT TYPE="hidden" NAME="svc_machine" VALUE=N">';
index bcb9c0d..e0c4706 100644 (file)
@@ -56,6 +56,7 @@ my $new = new FS::part_export ( {
 if ( $cgi->param('svc_machine') eq 'Y' ) {
   $new->machine('_SVC_MACHINE');
   $new->part_export_machine_textarea( $cgi->param('part_export_machine') );
+  $new->default_machine_name( $cgi->param('default_machine_name') );
 }
 
 my $error;