[freeside-commits] branch master updated. c2e126583354b58ef54ffa7f580b115b8eed1dd3

Mark Wells mark at 420.am
Mon Mar 18 19:36:44 PDT 2013


The branch, master has been updated
       via  c2e126583354b58ef54ffa7f580b115b8eed1dd3 (commit)
      from  04f99991071acf5c390ea4d52db37798543ff9d8 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit c2e126583354b58ef54ffa7f580b115b8eed1dd3
Author: Mark Wells <mark at freeside.biz>
Date:   Mon Mar 18 19:33:34 2013 -0700

    multiple inventory classes for service columns, #21442

diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm
index 6ca4889..da794dd 100644
--- a/FS/FS/part_svc.pm
+++ b/FS/FS/part_svc.pm
@@ -757,11 +757,9 @@ sub process {
                     if ( $flag =~ /^[MAH]$/ ) {
                       $param->{ $f } = delete( $param->{ $f.'_classnum' } );
                     }
-		    if ( $flag =~ /^S$/ 
-                          or $_ eq 'usergroup' ) {
-                      $param->{ $f } = ref($param->{ $f })
-                                         ? join(',', @{$param->{ $f }} )
-                                         : $param->{ $f };
+		    if ( ( $flag =~ /^[MAHS]$/ or $_ eq 'usergroup' )
+                         and ref($param->{ $f }) ) {
+                      $param->{ $f } = join(',', @{ $param->{ $f } });
 		    }
                     ( $f, $f.'_flag', $f.'_label' );
                   }
diff --git a/FS/FS/part_svc_column.pm b/FS/FS/part_svc_column.pm
index d467516..38ce1fa 100644
--- a/FS/FS/part_svc_column.pm
+++ b/FS/FS/part_svc_column.pm
@@ -99,8 +99,14 @@ sub check {
   $self->columnflag(uc($1));
 
   if ( $self->columnflag =~ /^[MA]$/ ) {
-    $error =
-      $self->ut_foreign_key( 'columnvalue', 'inventory_class', 'classnum' );
+    # split, check all values independently, and normalize
+    my @classnums = split(/\s*,\s*/, $self->columnvalue);
+    foreach (@classnums) {
+      $self->set('columnvalue', $_);
+      $error = $self->ut_foreign_key( 'columnvalue', 'inventory_class', 'classnum' );
+      return $error if $error;
+    }
+    $self->set('columnvalue', join(',', @classnums));
   }
   if ( $self->columnflag eq 'H' ) {
     $error = 
diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm
index ff465bf..0aea455 100644
--- a/FS/FS/svc_Common.pm
+++ b/FS/FS/svc_Common.pm
@@ -842,13 +842,20 @@ sub set_auto_inventory {
     next if $columnflag eq 'A' && $self->$field() ne '';
 
     my $classnum = $part_svc_column->columnvalue;
-    my %hash = ( 'classnum' => $classnum );
+    my %hash;
 
     if ( $columnflag eq 'A' && $self->$field() eq '' ) {
       $hash{'svcnum'} = '';
     } elsif ( $columnflag eq 'M' ) {
       return "Select inventory item for $field" unless $self->getfield($field);
       $hash{'item'} = $self->getfield($field);
+      my $chosen_classnum = $self->getfield($field.'_classnum');
+      if ( grep {$_ == $chosen_classnum} split(',', $classnum) ) {
+        $classnum = $chosen_classnum;
+      }
+      # otherwise the chosen classnum is either (all), or somehow not on 
+      # the list, so ignore it and choose the first item that's in any
+      # class on the list
     }
 
     my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql(
@@ -859,18 +866,30 @@ sub set_auto_inventory {
     my $inventory_item = qsearchs({
       'table'     => 'inventory_item',
       'hashref'   => \%hash,
-      'extra_sql' => "AND $agentnums_sql",
+      'extra_sql' => "AND classnum IN ($classnum) AND $agentnums_sql",
       'order_by'  => 'ORDER BY ( agentnum IS NULL ) '. #agent inventory first
                      ' LIMIT 1 FOR UPDATE',
     });
 
     unless ( $inventory_item ) {
+      # should really only be shown if columnflag eq 'A'...
       $dbh->rollback if $oldAutoCommit;
-      my $inventory_class =
-        qsearchs('inventory_class', { 'classnum' => $classnum } );
-      return "Can't find inventory_class.classnum $classnum"
-        unless $inventory_class;
-      return "Out of ". PL_N($inventory_class->classname);
+      my $message = 'Out of ';
+      my @classnums = split(',', $classnum);
+      foreach ( @classnums ) {
+        my $class = FS::inventory_class->by_key($_)
+          or return "Can't find inventory_class.classnum $_";
+        $message .= PL_N($class->classname);
+        if ( scalar(@classnums) > 2 ) { # english is hard
+          if ( $_ != $classnums[-1] ) {
+            $message .= ', ';
+          }
+        }
+        if ( scalar(@classnums) > 1 and $_ == $classnums[-2] ) {
+          $message .= 'and ';
+        }
+      }
+      return $message;
     }
 
     next if $columnflag eq 'M' && $inventory_item->svcnum == $self->svcnum;
@@ -878,13 +897,14 @@ sub set_auto_inventory {
     $self->setfield( $field, $inventory_item->item );
       #if $columnflag eq 'A' && $self->$field() eq '';
 
+    # release the old inventory item, if there was one
     if ( $old && $old->$field() && $old->$field() ne $self->$field() ) {
       my $old_inv = qsearchs({
         'table'     => 'inventory_item',
-        'hashref'   => { 'classnum' => $classnum,
+        'hashref'   => { 
                          'svcnum'   => $old->svcnum,
                        },
-        'extra_sql' => ' AND '.
+        'extra_sql' => "AND classnum IN ($classnum) AND ".
           '( ( svc_field IS NOT NULL AND svc_field = '.$dbh->quote($field).' )'.
           '  OR ( svc_field IS NULL AND item = '. dbh->quote($old->$field).' )'.
           ')',
@@ -920,6 +940,9 @@ sub set_auto_inventory {
 
 =item return_inventory
 
+Release all inventory items attached to this service's fields.  Call
+when unprovisioning the service.
+
 =cut
 
 sub return_inventory {
diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi
index f941ae5..0d36853 100755
--- a/httemplate/browse/part_svc.cgi
+++ b/httemplate/browse/part_svc.cgi
@@ -175,12 +175,14 @@ function part_export_areyousure(href) {
 % my $value = &$formatter($part_svc->part_svc_column($field)->columnvalue);
 % if ( $flag =~ /^[MAH]$/ ) { 
 %   my $select_table = ($flag eq 'H') ? 'hardware_class' : 'inventory_class';
-%   $select_class{$value} ||= 
-%       qsearchs($select_table, { 'classnum' => $value } );
+%   foreach my $classnum ( split(',', $value) ) {
+%     $select_class{$classnum} =
+%       qsearchs($select_table, { 'classnum' => $classnum } );
 % 
-            <% $select_class{$value}
-                  ? $select_class{$value}->classname
-                  : "WARNING: $select_table.classnum $value not found" %>
+      <% $select_class{$classnum}
+            ? $select_class{$classnum}->classname
+            : "WARNING: $select_table.classnum $classnum not found" %><BR>
+%   }
 % } else { 
 
             <% $value %>
diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html
index a24f238..3e6bd5b 100644
--- a/httemplate/edit/elements/edit.html
+++ b/httemplate/edit/elements/edit.html
@@ -329,6 +329,7 @@ Example:
 %     qw( country ),                                       #select-country
 %     qw( width height ),                                  #htmlarea
 %     qw( alt_format ),                                    #select-cust_location
+%     qw( classnum ),                                   # select-inventory_item
 %   ;
 %
 %   #select-table
diff --git a/httemplate/edit/elements/part_svc_column.html b/httemplate/edit/elements/part_svc_column.html
new file mode 100644
index 0000000..d03c49d
--- /dev/null
+++ b/httemplate/edit/elements/part_svc_column.html
@@ -0,0 +1,303 @@
+<%doc>
+To be called from part_svc.cgi.
+<& elements/part_svc_column.html, 
+    'svc_acct',
+    # options...
+    'part_svc'  => $part_svc, # the existing part_svc to edit
+    'clone'     => 0,         # or a svcpart to clone from
+&>
+
+</%doc>
+<%once>
+# the semantics of this could be better
+
+# all of these conditions are when NOT to allow that flag choice
+# don't allow the 'inventory' flags (M, A) to be chosen for 
+# fields that aren't free-text
+my $inv_sub = sub { $_[0]->{disable_inventory} || $_[0]->{type} ne 'text' };
+tie my %flag, 'Tie::IxHash',
+  ''  => { 'desc' => 'No default', 'condition' => sub { 0 } },
+  'D' => { 'desc' => 'Default', 
+           'condition' =>
+             sub { $_[0]->{disable_default } }
+         },
+  'F' => { 'desc' => 'Fixed (unchangeable)',
+           'condition' =>
+             sub { $_[0]->{disable_fixed} },
+         },
+  'S' => { 'desc' => 'Selectable Choice',
+           'condition' =>
+             sub { $_[0]->{disable_select} },
+         },
+  'M' => { 'desc' => 'Manual selection from inventory',
+           'condition' => $inv_sub,
+         },
+  'A' => { 'desc' => 'Automatically fill in from inventory',
+           'condition' => $inv_sub,
+         },
+  'H' => { 'desc' => 'Select from hardware class',
+           'condition' => sub { $_[0]->{type} ne 'select-hardware' },
+         },
+  'X' => { 'desc' => 'Excluded',
+           'condition' => sub { 1 }, # obsolete
+         },
+;
+
+# the semantics of this could be much better
+sub flag_condition {
+  my $f = shift;
+  not &{ $flag{$f}->{'condition'} }(@_);
+}
+
+my %communigate_fields = (
+  'svc_acct'        => { map { $_=>1 }
+                            qw( file_quota file_maxnum file_maxsize
+                                password_selfchange password_recover
+                              ),
+                            grep /^cgp_/, fields('svc_acct')
+  },
+  'svc_domain'      => { map { $_=>1 }
+                            qw( max_accounts trailer parent_svcnum ),
+                            grep /^(cgp|acct_def)_/, fields('svc_domain')
+  },
+);
+</%once>
+<INPUT TYPE="hidden" NAME="svcdb" VALUE="<% $svcdb %>">
+<BR><BR>
+<& /elements/table.html &>
+  <TR><TH COLSPAN=<% $columns %>>Exports</TH></TR>
+  <TR>
+% # exports
+% foreach my $part_export (@part_export) {
+    <TD>
+      <INPUT TYPE="checkbox" \
+             NAME="exportnum<% $part_export->exportnum %>" \
+             VALUE=1 \
+             <% $has_export_svc{$part_export->exportnum} ? 'CHECKED' : '' %>>
+      <% $part_export->label_html %>
+    </TD>
+%   $count++;
+%   if ( $count % $columns == 0 ) {
+  </TR>
+  <TR>
+%   }
+% }
+  </TR>
+</TABLE><BR><BR>
+For the selected table, you can give fields default or fixed (unchangeable)
+values, or select an inventory class to manually or automatically fill in 
+that field.
+<& /elements/table-grid.html, cellpadding => 4 &>
+  <TR>
+    <TH BGCOLOR="#cccccc">Field</TH>
+    <TH BGCOLOR="#cccccc">Label</TH>
+    <TH BGCOLOR="#cccccc" COLSPAN=2>Modifier</TH>
+  </TR>
+% $part_svc->set('svcpart' => $opt{'clone'}) if $opt{'clone'}; # for now
+% my $i = 0;
+% foreach my $field (@fields) {
+%   my $def = shift @defs;
+%   my $part_svc_column = $part_svc->part_svc_column($field);
+%   my $flag = $part_svc_column->columnflag;
+%   my $formatter = $def->{'format'} || sub { shift };
+%   my $value = &{$formatter}($part_svc_column->columnvalue);
+  <TR CLASS="row<%$i%>">
+    <TD ROWSPAN=2 CLASS="grid" ALIGN="right">
+      <% $def->{'label'} || $field %>
+    </TD>
+    <TD ROWSPAN=2 CLASS="grid">
+      <INPUT NAME="<% $svcdb %>__<% $field %>_label"
+             STYLE="text-align: right"
+             VALUE="<% $part_svc_column->columnlabel || $def->{'label'} |h %>">
+    </TD>
+
+    <TD ROWSPAN=1 CLASS="grid">
+%   # flag selection
+%   if ( $def->{'type'} eq 'disabled' ) {
+%     $flag = '';
+      No default
+%   } else {
+%     my $name = $svcdb.'__'.$field.'_flag';
+      <SELECT NAME="<%$name%>"
+              ID="<%$name%>"
+              STYLE="width:100%"
+              onchange="flag_changed(this)">
+%     foreach my $f (keys %flag) {
+%       if ( flag_condition($f, $def, $svcdb, $field) ) {
+          <OPTION VALUE="<%$f%>"<% $flag eq $f ? ' SELECTED' : ''%>>
+            <% $flag{$f}->{desc} %>
+          </OPTION>
+%       }
+%     }
+      </SELECT>
+%   } # if $def->{'type'} eq 'disabled'
+    </TD>
+    <TD CLASS="grid">
+%   # value entry/selection
+%   my $name = $svcdb.'__'.$field;
+%   # These are all MANDATORY SELECT types.  Regardless of the flag value,
+%   # there will never be a text input (either in svc_* or in part_svc) for
+%   # these fields.
+%   if ( $def->{'type'} eq 'checkbox' ) {
+      <& /elements/checkbox.html,
+          'field'       => $name,
+          'curr_value'  => $value,
+          'value'       => 'Y' &>
+%
+%   } elsif ( $def->{'type'} eq 'select' ) {
+%
+%     if ( $def->{'select_table'} ) {
+      <& /elements/select-table.html,
+          'field'       => $name,
+          'id'          => $name.'_select',
+          'table'       => $def->{'select_table'},
+          'name_col'    => $def->{'select_label'},
+          'value_col'   => $def->{'select_key'},
+          'order_by'    => dbdef->table($def->{'select_table'})->primary_key,
+          'multiple'    => $def->{'multiple'},
+          'disable_empty' => 1,
+          'curr_value'  => $value,
+      &>
+%     } else {
+%       my (@options, %labels);
+%       if ( $def->{'select_list'} ) {
+%         @options = @{ $def->{'select_list'} };
+%         @labels{@options} = @options;
+%       } elsif ( $def->{'select_hash'} ) {
+%         if ( ref($def->{'select_hash'}) eq 'ARRAY' ) {
+%           tie my %hash, 'Tie::IxHash', @{ $def->{'select_hash'} };
+%           $def->{'select_hash'} = \%hash;
+%         }
+%         @options = keys( %{ $def->{'select_hash'} } );
+%         %labels = %{ $def->{'select_hash'} };
+%       }
+      <& /elements/select.html,
+          'field'       => $name,
+          'id'          => $name.'_select',
+          'options'     => \@options,
+          'labels'      => \%labels,
+          'multiple'    => $def->{'multiple'},
+          'curr_value'  => $value,
+      &>
+%     }
+%   } elsif ( $def->{'type'} =~ /select-(.*?).html/ ) {
+      <& '/elements/'.$def->{'type'},
+          'field'       => $name,
+          'id'          => $name.'_select',
+          'multiple'    => $def->{'multiple'},
+          'curr_value'  => $value,
+      &>
+%   } elsif ( $def->{'type'} eq 'communigate_pro-accessmodes' ) {
+      <& /elements/communigate_pro-accessmodes.html,
+          'element_name_prefix' => $name.'_',
+          'curr_value'  => $value,
+      &>
+%   } elsif ( $def->{'type'} eq 'textarea' ) {
+%   # special cases
+      <TEXTAREA NAME="<%$name%>"><% $value |h %></TEXTAREA>
+%   } elsif ( $def->{'type'} eq 'disabled' ) {
+      <INPUT TYPE="hidden" NAME="<%$name%>" VALUE="">
+%   } else {
+%     # the normal case: a text input, and a _select which is an inventory
+%     # or hardware class
+      <INPUT TYPE="text"
+             NAME="<%$name%>"
+             ID="<%$name%>" 
+             VALUE="<%$value%>">
+%     # inventory class selection
+      <& /elements/select-table.html,
+          'field'       => $name.'_classnum',
+          'id'          => $name.'_select',
+          'table'       => 'inventory_class',
+          'name_col'    => 'classname',
+          'curr_value'  => $value,
+          'empty_label' => 'Select inventory class',
+          'multiple'    => 1,
+      &>
+%   }
+    </TD>
+  </TR>
+  <TR CLASS="row<%$i%>">
+    <TD COLSPAN=2 CLASS="def_info">
+%   if ( $def->{def_info} ) {
+      (<% $def->{def_info} %>)
+    </TD>
+  </TR>
+%   }
+% $i = 1-$i;
+% } # foreach my $field
+%
+% # special case: svc_acct password edit ACL
+% if ( $svcdb eq 'svc_acct' ) {
+%   push @fields, 'restrict_edit_password';
+  <TR>
+    <TD COLSPAN=3 ALIGN="right">
+      <% emt('Require "Provision" access right to edit password') %>
+    </TD>
+    <TD>
+      <INPUT TYPE="checkbox" NAME="restrict_edit_password" VALUE="Y" \
+      <% $part_svc->restrict_edit_password ? 'CHECKED' : '' %>>
+    </TD>
+  </TR>
+% }
+</TABLE>
+<& /elements/progress-init.html,
+  $svcdb, #form name
+  [ # form fields to send
+    qw(svc svcpart classnum selfservice_access disabled preserve exportnum),
+    @fields
+  ],
+  'process/part_svc.cgi',   # target
+  $p.'browse/part_svc.cgi', # redirect landing
+  $svcdb, #key
+&>
+% $svcpart = '' if $opt{clone};
+<BR>
+<INPUT NAME="submit"
+       TYPE="button"
+       VALUE="<% emt($svcpart ? 'Apply changes' : 'Add service') %>"
+       onclick="fixup_submit('<%$svcdb%>')"
+>
+<%init>
+my $svcdb = shift;
+my %opt = @_;
+my $columns = 3;
+my $count = 0;
+my $communigate = 0;
+my $conf = FS::Conf->new;
+
+my $part_svc = $opt{'part_svc'} || FS::part_svc->new;
+
+my @part_export;
+my $export_info = FS::part_export::export_info($svcdb);
+foreach (keys %{ $export_info }) {
+  push @part_export, qsearch('part_export', { exporttype => $_ });
+}
+$communigate = scalar(grep {$_->exporttype =~ /^communigate/} @part_export);
+
+my $svcpart = $opt{'clone'} || $part_svc->svcpart;
+my %has_export_svc;
+if ( $svcpart ) {
+  foreach (qsearch('export_svc', { svcpart => $svcpart })) {
+    $has_export_svc{$_->exportnum} = 1;
+  }
+}
+
+my @fields;
+if ( defined( dbdef->table($svcdb) ) ) { # when is it ever not defined?
+  @fields = grep {
+    $_ ne 'svcnum'
+      and ( $communigate || ! $communigate_fields{$svcdb}->{$_} )
+      and ( !FS::part_svc->svc_table_fields($svcdb)->{$_}->{disable_part_svc_column}
+            || $part_svc->part_svc_column($_)->columnflag )
+  } fields($svcdb);
+}
+if ( $svcdb eq 'svc_acct'
+      or ( $svcdb eq 'svc_broadband' and $conf->exists('svc_broadband-radius') )
+   )
+{
+  push @fields, 'usergroup';
+}
+
+my @defs = map { FS::part_svc->svc_table_fields($svcdb)->{$_} } @fields;
+</%init>
diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html
index 0d9d36c..d46d1cb 100644
--- a/httemplate/edit/elements/svc_Common.html
+++ b/httemplate/edit/elements/svc_Common.html
@@ -88,30 +88,13 @@
                    } elsif ( $flag eq 'A' ) {
                      $f->{'type'} = 'hidden';
                    } elsif ( $flag eq 'M' ) {
+                     $f->{'type'} = 'select-inventory_item';
                      $f->{'empty_label'} = 'Select inventory item';
-                     $f->{'type'}        = 'select-table';
-                     $f->{'table'}       = 'inventory_item';
-                     $f->{'name_col'}    = 'item'; 
-                     $f->{'value_col'}   = 'item'; 
-                     $f->{'agent_virt'}  = 1;
-                     $f->{'agent_null'}  = 1;
-                     $f->{'hashref'}     = {
-                                            'classnum'=>$columndef->columnvalue,
-                                            #'svcnum'  => '',
-                                           };
-                     $f->{'extra_sql'}   = 'AND ( svcnum IS NULL ';
-                     $f->{'extra_sql'}  .= ' OR svcnum = '. $object->svcnum
-                       if $object->svcnum;
-                     $f->{'extra_sql'}  .= ' ) ';
+                     $f->{'extra_sql'} = 'WHERE ( svcnum IS NULL ' .
+                        ($object->svcnum && ' OR svcnum = '.$object->svcnum) .
+                        ')';
+                     $f->{'classnum'} = $columndef->columnvalue;
                      $f->{'disable_empty'} = $object->svcnum ? 1 : 0;
-                     if ( $f->{'field'} eq 'mac_addr' ) {
-                       $f->{'compare_sub'} = sub {
-                         my($a, $b) = @_;
-                         $a =~ s/[-: ]//g;
-                         $b =~ s/[-: ]//g;
-                         lc($a) eq lc($b);
-                       };
-                     }
                    } elsif ( $flag eq 'H' ) {
                      $f->{'type'}        = 'select-hardware_type';
                      $f->{'hashref'}     = {
diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi
index 8a84b20..58c237e 100755
--- a/httemplate/edit/part_svc.cgi
+++ b/httemplate/edit/part_svc.cgi
@@ -1,11 +1,111 @@
-<& /elements/header.html, "$action Service Definition",
-           menubar('View all service definitions' => "${p}browse/part_svc.cgi"),
+<& /elements/header.html, "$action Service Definition" &>
+<& /elements/menubar.html,
+  'View all service definitions' => "${p}browse/part_svc.cgi"
            #" onLoad=\"visualize()\""
 &>
 
 <& /elements/init_overlib.html &>
 
-<BR>
+<BR><BR>
+
+<STYLE TYPE="text/css">
+.disabled {
+  background-color: #dddddd;
+}
+.hidden {
+  display: none;
+}
+.enabled {
+  background-color: #ffffff;
+}
+.row0 TD {
+  background-color: #eeeeee;
+}
+.row1 TD {
+  background-color: #ffffff;
+}
+.def_info {
+  text-align: center;
+  padding: 0px;
+  border-top: none;
+  font-size: smaller;
+  font-style: italic;
+}
+</STYLE>
+<SCRIPT TYPE="text/javascript">
+function fixup_submit(layer) {
+  document.forms[layer].submit.disabled = true;
+  fixup(document.forms[layer]);
+  window[layer+'process'].call();
+}
+
+function flag_changed(obj) {
+  var newflag = obj.value;
+  var a = obj.name.match(/(.*)__(.*)_flag/);
+  var layer = a[1];
+  var field = a[2];
+  var input = document.getElementById(layer + '__' + field);
+  // for fields that have both 'input' and 'select', 'select' is 'select from
+  // inventory class'.
+  var select = document.getElementById(layer + '__' + field + '_select');
+  if (newflag == "" || newflag == "X") { // disable
+    if ( input ) {
+      input.disabled = true;
+      input.className = 'disabled';
+    }
+    if ( select ) {
+      select.disabled = true;
+      select.className = 'hidden';
+    }
+  } else if ( newflag == 'D' || newflag == 'F' || newflag == 'S' ) {
+    if ( input ) {
+      // enable text box, disable inventory select
+      input.disabled = false;
+      input.className = 'enabled';
+      if ( select ) {
+        select.disabled = false;
+        select.className = 'hidden';
+      }
+    } else if ( select ) {
+      // enable select
+      select.disabled = false;
+      select.className = 'enabled';
+      if ( newflag == 'S' || select.getAttribute('should_be_multiple') ) {
+        select.multiple = true;
+      } else {
+        select.multiple = false;
+      }
+    }
+  } else if ( newflag == 'M' || newflag == 'A' || newflag == 'H' ) {
+    // these all require a class selection
+    if ( select ) {
+      select.disabled = false;
+      select.className = 'enabled';
+      if ( input ) {
+        input.disabled = false;
+        input.className = 'hidden';
+      }
+    }
+  }
+}
+
+window.onload = function() {
+  var selects = document.getElementsByTagName('SELECT');
+  for(i = 0; i < selects.length; i++) {
+    var obj = selects[i];
+    if ( obj.multiple ) {
+      obj.setAttribute('should_be_multiple', true);
+    }
+  }
+  for(i = 0; i < selects.length; i++) {
+    var obj = selects[i];
+    if ( obj.name.match(/_flag$/) ) {
+      flag_changed(obj);
+    }
+  }
+};
+
+</SCRIPT>
 
 <FORM NAME="dummy">
 
@@ -53,400 +153,6 @@
 
 <BR>
 
-% my %vfields;
-%  #code duplication w/ edit/part_svc.cgi, should move this hash to part_svc.pm
-%  # and generalize the subs
-%  # condition sub is tested to see whether to disable display of this choice
-%  # params: ( $def, $layer, $field )  (see SUB below)
-%  my $inv_sub = sub {
-%                      $_[0]->{disable_inventory}
-%                        || $_[0]->{'type'} ne 'text'
-%                    };
-%  tie my %flag, 'Tie::IxHash',
-%    ''  => { 'desc' => 'No default', },
-%    'D' => { 'desc' => 'Default',
-%             'condition' =>
-%               sub { $_[0]->{disable_default} }, 
-%           },
-%    'F' => { 'desc' => 'Fixed (unchangeable)',
-%             'condition' =>
-%               sub { $_[0]->{disable_fixed} }, 
-%           },
-%    'S' => { 'desc' => 'Selectable Choice',
-%             'condition' =>
-%               sub { !ref($_[0]) || $_[0]->{disable_select} }, 
-%           },
-%    'M' => { 'desc' => 'Manual selection from inventory',
-%             'condition' => $inv_sub,
-%           },
-%    'A' => { 'desc' => 'Automatically fill in from inventory',
-%             'condition' => $inv_sub,
-%           },
-%    'H' => { 'desc' => 'Select from hardware class',
-%             'condition' => sub { $_[0]->{type} ne 'select-hardware' },
-%           },
-%    'X' => { 'desc' => 'Excluded',
-%             'condition' =>
-%               sub { ! $vfields{$_[1]}->{$_[2]} },
-%
-%           },
-%  ;
-%  
-%  my @dbs = $hashref->{svcdb}
-%             ? ( $hashref->{svcdb} )
-%             : FS::part_svc->svc_tables();
-%
-%  my $help = '';
-%  unless ( $hashref->{svcpart} ) {
-%    $help = ' '.
-%            include('/elements/popup_link.html',
-%                      'action' => $p.'docs/part_svc-table.html',
-%                      'label'  => 'help',
-%                      'actionlabel' => 'Service table help',
-%                      'width'       => 763,
-%                      #'height'      => 400,
-%                    );
-%  }
-%
-%  tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs;
-%  my $widget = new HTML::Widgets::SelectLayers(
-%    #'selected_layer' => $p_svcdb,
-%    'selected_layer' => $hashref->{svcdb} || 'svc_acct',
-%    'options'        => \%svcdb,
-%    'form_name'      => 'dummy',
-%    #'form_action'    => 'process/part_svc.cgi',
-%    'form_action'    => 'part_svc.cgi', #self
-%    'form_elements'  => [qw( svc svcpart classnum selfservice_access
-%                             disabled preserve
-%                        )],
-%    'html_between'   => $help,
-%    'layer_callback' => sub {
-%      my $layer = shift;
-%      
-%      my $html = qq!<INPUT TYPE="hidden" NAME="svcdb" VALUE="$layer">!;
-%
-%      #$html .= $svcdb_info;
-%
-%      my $columns = 3;
-%      my $count = 0;
-%      my $communigate = 0;
-%      my @part_export =
-%        map { qsearch( 'part_export', {exporttype => $_ } ) }
-%          keys %{FS::part_export::export_info($layer)};
-%      $html .= '<BR><BR>'. include('/elements/table.html') . 
-%               "<TR><TH COLSPAN=$columns>Exports</TH></TR><TR>";
-%      foreach my $part_export ( @part_export ) {
-%        $communigate++ if $part_export->exporttype =~ /^communigate/;
-%        $html .= '<TD><INPUT TYPE="checkbox"'.
-%                 ' NAME="exportnum'. $part_export->exportnum. '"  VALUE="1" ';
-%        $html .= 'CHECKED'
-%          if ( $clone || $part_svc->svcpart ) #null svcpart search causing error
-%              && qsearchs( 'export_svc', {
-%                                   exportnum => $part_export->exportnum,
-%                                   svcpart   => $clone || $part_svc->svcpart });
-%        $html .= '>'. $part_export->label_html. '</TD>';
-%        $count++;
-%        $html .= '</TR><TR>' unless $count % $columns;
-%      }
-%      $html .= '</TR></TABLE><BR><BR>'. $mod_info;
-%
-%      $html .= include('/elements/table-grid.html', 'cellpadding' => 4 ).
-%               '<TR>'.
-%                 '<TH CLASS="grid" BGCOLOR="#cccccc">Field</TH>'.
-%                 '<TH CLASS="grid" BGCOLOR="#cccccc">Label</TH>'.
-%                 '<TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2>Modifier</TH>'.
-%               '</TR>';
-%
-%      my $bgcolor1 = '#eeeeee';
-%      my $bgcolor2 = '#ffffff';
-%      my $bgcolor;
-%
-%      #yucky kludge
-%      my @fields = ();
-%      if ( defined( dbdef->table($layer) ) ) {
-%        @fields = grep {
-%            $_ ne 'svcnum'
-%            && ( $communigate || !$communigate_fields{$layer}->{$_} )
-%            && ( !FS::part_svc->svc_table_fields($layer)
-%                   ->{$_}->{disable_part_svc_column}
-%                 || $part_svc->part_svc_column($_)->columnflag
-%               )
-%        } fields($layer);
-%      }
-%      push @fields, 'usergroup' 
-%        if $layer eq 'svc_acct'
-%          or ( $layer eq 'svc_broadband' and 
-%               $conf->exists('svc_broadband-radius') ); # double kludge
-%               # (but we do want to check the config, right?)
-%      $part_svc->svcpart($clone) if $clone; #haha, undone below
-%
-%
-%      foreach my $field (@fields) {
-%
-%        #a few lines of false laziness w/browse/part_svc.cgi
-%        my $def = FS::part_svc->svc_table_fields($layer)->{$field};
-%        my $def_info  = $def->{'def_info'};
-%        my $formatter = $def->{'format'} || sub { shift };
-%
-%        my $part_svc_column = $part_svc->part_svc_column($field);
-%        my $label = $part_svc_column->columnlabel || $def->{'label'};
-%        my $value = &$formatter($part_svc_column->columnvalue);
-%        my $flag  = $part_svc_column->columnflag;
-%
-%        if ( $bgcolor eq $bgcolor1 ) {
-%          $bgcolor = $bgcolor2;
-%        } else {
-%          $bgcolor = $bgcolor1;
-%        }
-%        
-%        $html .= qq!<TR><TD ROWSPAN=2 CLASS="grid" BGCOLOR="$bgcolor" ALIGN="right">!.
-%                 ( $def->{'label'} || $field ).
-%                 "</TD>";
-%
-%        $html .= qq!<TD ROWSPAN=2 CLASS="grid" BGCOLOR="$bgcolor"><INPUT NAME="${layer}__${field}_label" VALUE="!. encode_entities($label). '" STYLE="text-align:right"></TD>';
-%
-%        $flag = '' if $def->{type} eq 'disabled';
-%
-%        $html .= qq!<TD CLASS="grid" BGCOLOR="$bgcolor">!;
-%
-%        if ( $def->{type} eq 'disabled' ) {
-%        
-%          $html .= 'No default';
-%
-%        } else {
-%
-%          $html .= qq!<SELECT NAME="${layer}__${field}_flag"!.
-%                      qq! onChange="${layer}__${field}_flag_changed(this)">!;
-%
-%          foreach my $f ( keys %flag ) {
-%
-%            # need to template-ize more httemplate/edit/svc_* first
-%            next if $f eq 'M' and $layer !~ /^svc_(broadband|external|phone|dish)$/;
-%
-%            #here is where the SUB from above is called, to skip some choices
-%            next if $flag{$f}->{condition}
-%                 && &{ $flag{$f}->{condition} }( $def, $layer, $field );
-%
-%            $html .= qq!<OPTION VALUE="$f"!.
-%                     ' SELECTED'x($flag eq $f ).
-%                     '>'. $flag{$f}->{desc};
-%
-%          }
-%
-%          $html .= '</SELECT>';
-%
-%          $html .= join("\n",
-%            '<SCRIPT>',
-%            "  function ${layer}__${field}_flag_changed(what) {",
-%            '    var f = what.options[what.selectedIndex].value;',
-%            '    if ( f == "" || f == "X" ) { //disable',
-%            "      what.form.${layer}__${field}.disabled = true;".
-%            "      what.form.${layer}__${field}.style.backgroundColor = '#dddddd';".
-%            "      if ( what.form.${layer}__${field}_classnum ) {".
-%            "        what.form.${layer}__${field}_classnum.disabled = true;".
-%            "        what.form.${layer}__${field}_classnum.style.backgroundColor = '#dddddd';".
-%            "      }".
-%            '    } else if ( f == "D" || f == "F" || f =="S" ) { //enable, text box',
-%            "      what.form.${layer}__${field}.disabled = false;".
-%            "      what.form.${layer}__${field}.style.backgroundColor = '#ffffff';".
-%            "      if ( f == 'S' || '${field}' == 'usergroup' ) {". # kludge
-%            "        what.form.${layer}__${field}.multiple = true;".
-%            "      } else {".
-%            "        what.form.${layer}__${field}.multiple = false;".
-%            "      }".
-%            "      what.form.${layer}__${field}.style.display = '';".
-%            "      if ( what.form.${layer}__${field}_classnum ) {".
-%            "        what.form.${layer}__${field}_classnum.disabled = false;".
-%            "        what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';".
-%            "        what.form.${layer}__${field}_classnum.style.display = 'none';".
-%            "      }".
-%            '    } else if ( f == "M" || f == "A" || f == "H" ) { '.
-%                   '//enable, inventory',
-%            "      what.form.${layer}__${field}.disabled = false;".
-%            "      what.form.${layer}__${field}.style.backgroundColor = '#ffffff';".
-%            "      what.form.${layer}__${field}.style.display = 'none';".
-%            "      if ( what.form.${layer}__${field}_classnum ) {".
-%            "        what.form.${layer}__${field}_classnum.disabled = false;".
-%            "        what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';".
-%            "        what.form.${layer}__${field}_classnum.style.display = '';".
-%            "      }".
-%            '    }',
-%            '  }',
-%            '</SCRIPT>',
-%          );
-%
-%        }
-%
-%        $html .= qq!</TD><TD CLASS="grid" BGCOLOR="$bgcolor">!;
-%
-%        my $disabled = $flag ? ''
-%                             : 'DISABLED STYLE="background-color: #dddddd"';
-%        my $nodisplay = ' STYLE="display:none"';
-%
-%        if ( !$def->{type} || $def->{type} eq 'text' ) {
-%
-%          my $is_inv = ( $flag =~ /^[MA]$/ );
-%
-%          $html .=
-%            qq!<INPUT TYPE="text" NAME="${layer}__${field}" VALUE="$value" !.
-%            $disabled.
-%            ( $is_inv ? $nodisplay : $disabled ).
-%            '>';
-%
-%          $html .= include('/elements/select-table.html',
-%                             'element_name' => "${layer}__${field}_classnum",
-%                             'id'           => "${layer}__${field}_classnum",
-%                             'element_etc'  => ( $is_inv
-%                                                   ? $disabled
-%                                                   : $nodisplay
-%                                               ),
-%                             'table'        => 'inventory_class',
-%                             'name_col'     => 'classname',
-%                             'value'        => $value,
-%                             'empty_label'  => 'Select inventory class',
-%                          );
-%
-%        } elsif ( $def->{type} eq 'checkbox' ) {
-%
-%          $html .= include('/elements/checkbox.html',
-%                             'field'      => $layer.'__'.$field,
-%                             'curr_value' => $value,
-%                             'value'      => 'Y',
-%                          );
-%
-%        } elsif ( $def->{type} eq 'select' ) {
-%
-%          $html .= qq!<SELECT NAME="${layer}__${field}" $disabled!;
-%          $html .= ' MULTIPLE' if $flag eq 'S';
-%          $html .= '>';
-%          $html .= '<OPTION> </OPTION>' unless $value;
-%          if ( $def->{select_table} ) {
-%            foreach my $record ( qsearch( $def->{select_table}, {} ) ) {
-%              my $rvalue = $record->getfield($def->{select_key});
-%              my $select_label = $def->{select_label};
-%              $html .= qq!<OPTION VALUE="$rvalue"!.
-%                  (grep(/^$rvalue$/, split(',',$value)) ? ' SELECTED>' : '>' ).
-%                  $record->$select_label(). '</OPTION>';
-%            } #next $record
-%          } elsif ( $def->{select_list} ) {
-%            foreach my $item ( @{$def->{select_list}} ) {
-%              $html .= qq!<OPTION VALUE="$item"!.
-%                    (grep(/^$item$/, split(',',$value)) ? ' SELECTED>' : '>' ).
-%                    $item. '</OPTION>';
-%            } #next $item
-%          } elsif ( $def->{select_hash} ) {
-%            if ( ref($def->{select_hash}) eq 'ARRAY' ) {
-%              tie my %hash, 'Tie::IxHash', @{ $def->{select_hash} };
-%              $def->{select_hash} = \%hash;
-%            }
-%            foreach my $key ( keys %{$def->{select_hash}} ) {
-%              $html .= qq!<OPTION VALUE="$key"!.
-%                    (grep(/^$key$/, split(',',$value)) ? ' SELECTED>' : '>' ).
-%                    $def->{select_hash}{$key}. '</OPTION>';
-%            } #next $key
-%          } #endif
-%          $html .= '</SELECT>';
-%
-%        } elsif ( $def->{type} eq 'textarea' ) {
-%
-%          $html .=
-%            qq!<TEXTAREA NAME="${layer}__${field}">!. encode_entities($value).
-%            '</TEXTAREA>';
-%
-%        } elsif ( $def->{type} =~ /select-(.*?).html/ ) {
-%
-%          $html .= include("/elements/".$def->{type},
-%                             'curr_value'   => $value,
-%                             'element_name' => "${layer}__${field}",
-%                             'element_etc'  => $disabled,
-%                             'multiple'     => ($def->{multiple} ||
-%                                                $flag eq 'S'),
-%                                 # allow the table def to force 'multiple'
-%                          );
-%
-%        } elsif ( $def->{type} eq 'communigate_pro-accessmodes' ) {
-%
-%          $html .= include('/elements/communigate_pro-accessmodes.html',
-%                             'element_name_prefix' => "${layer}__${field}_",
-%                             'curr_value'          => $value,
-%                             #doesn't work#'element_etc'  => $disabled,
-%                          );
-%
-%        } elsif ( $def->{type} eq 'select-hardware' ) {
-%
-%          $html .= qq!<INPUT TYPE="text" NAME="${layer}__${field}" $disabled>!;
-%          $html .= include('/elements/select-hardware_class.html',
-%                             'curr_value'    => $value,
-%                             'element_name'  => "${layer}__${field}_classnum",
-%                             'id'            => "${layer}__${field}_classnum",
-%                             'element_etc'   => $flag ne 'H' && $nodisplay,
-%                             'empty_label'   => 'Select hardware class',
-%                          );
-%
-%        } elsif ( $def->{type} eq 'disabled' ) {
-%
-%          $html .=
-%            qq!<INPUT TYPE="hidden" NAME="${layer}__${field}" VALUE="">!;
-%
-%        } else {
-%
-%          $html .= '<font color="#ff0000">unknown type '. $def->{type};
-%
-%        }
-%
-%        $html .= "</TD></TR>\n";
-
-%        $def_info = "($def_info)" if $def_info;
-%        $html .=
-%          qq!<TR>!.
-%          qq!  <TD COLSPAN=2 BGCOLOR="$bgcolor" ALIGN="center" !.
-%          qq!      STYLE="padding:0; border-top: none">!.
-%          qq!    <FONT SIZE="-1"><I>$def_info</I></FONT>!.
-%          qq!  </TD>!.
-%          qq!</TR>\n!;
-%
-%      } #foreach my $field (@fields) {
-%
-%      if ( $layer eq 'svc_acct' ) {
-%        # eww, more ugly special-caseyness
-%        $html .= 
-%          '<TR><TD COLSPAN=3 ALIGN="right">'.
-%          emt('Require "Provision" access right to edit password').
-%          '</TD><TD>'.
-%          '<INPUT TYPE="checkbox" NAME="restrict_edit_password" VALUE="Y"'.
-%          ($part_svc->restrict_edit_password ? ' CHECKED' : '').
-%          '></TD></TR>';
-%      } else {
-%        $html .= 
-%          '<INPUT TYPE="hidden" NAME="restrict_edit_password" VALUE="">';
-%      }
-%
-%      $part_svc->svcpart('') if $clone; #undone
-%      $html .= "</TABLE>";
-%
-%      $html .= include('/elements/progress-init.html',
-%                         $layer, #form name
-%                         [ qw(svc svcpart classnum selfservice_access
-%                              disabled preserve
-%                              exportnum restrict_edit_password),
-%                           @fields ],
-%                         'process/part_svc.cgi',
-%                         $p.'browse/part_svc.cgi',
-%                         $layer,
-%                      );
-%      $html .= '<BR><INPUT NAME="submit" TYPE="button" VALUE="'.
-%               ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '" '.
-%               ' onClick="document.'. "$layer.submit.disabled=true; ".
-%               "fixup(document.$layer); $layer". 'process();">';
-%
-%      #$html .= '<BR><INPUT TYPE="submit" VALUE="'.
-%      #         ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '">';
-%
-%      $html;
-%
-%    },
-%  );
-
 <BR>
 Table <% $widget->html %>
 
@@ -479,28 +185,43 @@ my $action = $part_svc->svcpart ? 'Edit' : 'Add';
 my $hashref = $part_svc->hashref;
 #   my $p_svcdb = $part_svc->svcdb || 'svc_acct';
 
-my %communigate_fields = (
-  'svc_acct'        => { map { $_=>1 }
-                           qw( file_quota file_maxnum file_maxsize
-                               password_selfchange password_recover
-                             ),
-                           grep /^cgp_/, fields('svc_acct')
-                       },
-  'svc_domain'      => { map { $_=>1 }
-                           qw( max_accounts trailer parent_svcnum ),
-                           grep /^(cgp|acct_def)_/, fields('svc_domain')
-                       },
-  #'svc_forward'     => { map { $_=>1 } qw( ) },
-  #'svc_mailinglist' => { map { $_=>1 } qw( ) },
-  #'svc_cert'        => { map { $_=>1 } qw( ) },
-);
 
-my $mod_info = '
-For the selected table, you can give fields default or fixed (unchangable)
-values, or select an inventory class to manually or automatically fill in
-that field.
-';
+my @dbs = $hashref->{svcdb}
+           ? ( $hashref->{svcdb} )
+           : FS::part_svc->svc_tables();
+
+my $help = '';
+unless ( $hashref->{svcpart} ) {
+  $help = ' '.
+          include('/elements/popup_link.html',
+                    'action' => $p.'docs/part_svc-table.html',
+                    'label'  => 'help',
+                    'actionlabel' => 'Service table help',
+                    'width'       => 763,
+                    #'height'      => 400,
+                  );
+}
 
+tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs;
+my $widget = new HTML::Widgets::SelectLayers(
+  #'selected_layer' => $p_svcdb,
+  'selected_layer' => $hashref->{svcdb} || 'svc_acct',
+  'options'        => \%svcdb,
+  'form_name'      => 'dummy',
+  #'form_action'    => 'process/part_svc.cgi',
+  'form_action'    => 'part_svc.cgi', #self
+  'form_elements'  => [qw( svc svcpart classnum selfservice_access
+                           disabled preserve
+                      )],
+  'html_between'   => $help,
+  'layer_callback' => sub {
+    include('elements/part_svc_column.html',
+              shift,
+              'part_svc' => $part_svc,
+              'clone' => $clone
+    )
+  }
+);
 </%init>
 
 
diff --git a/httemplate/edit/process/elements/svc_Common.html b/httemplate/edit/process/elements/svc_Common.html
index 5a8afbd..06f4c00 100644
--- a/httemplate/edit/process/elements/svc_Common.html
+++ b/httemplate/edit/process/elements/svc_Common.html
@@ -10,5 +10,10 @@ my %opt = @_;
 my $table = $opt{'table'};
 $opt{'fields'} ||= [ fields($table) ];
 push @{ $opt{'fields'} }, qw( pkgnum svcpart );
+foreach (fields($table)) {
+  if ( $cgi->param($_.'_classnum') ) {
+    push @{ $opt{'fields'} }, $_.'_classnum';
+  }
+}
 
 </%init>
diff --git a/httemplate/elements/tr-select-inventory_item.html b/httemplate/elements/tr-select-inventory_item.html
new file mode 100644
index 0000000..669e85f
--- /dev/null
+++ b/httemplate/elements/tr-select-inventory_item.html
@@ -0,0 +1,48 @@
+% if ( scalar(@classnums) == 0 ) {
+<& tr-fixed.html, %opt &>
+% } elsif ( scalar(@classnums) == 1 ) {
+%   $opt{'extra_sql'} .= ' AND '.$classnum_sql;
+<& tr-select-table.html,
+  'table'     => 'inventory_item',
+  'name_col'  => 'item',
+  'value_col' => 'item',
+  %opt
+&>
+% } else {
+<& tr-td-label.html, %opt &>
+<TD>
+<& select-tiered.html,
+  'prefix' => $opt{'field'}.'_',
+  'tiers' => [
+    {
+      field         => $opt{'field'}.'_classnum',
+      table         => 'inventory_class',
+      extra_sql     => "WHERE $classnum_sql",
+      name_col      => 'classname',
+      empty_label   => '(all)',
+    },
+    {
+      field         => $opt{'field'},
+      table         => 'inventory_item',
+      name_col      => 'item',
+      value_col     => 'item',
+      link_col      => 'classnum',
+      extra_sql     => delete($opt{'extra_sql'}),
+      disable_empty => 1,
+    },
+  ],
+  %opt,
+&>
+</TD>
+</TR>
+% }
+<%init>
+my %opt = @_;
+my @classnums;
+if (ref($opt{'classnum'})) {
+  @classnums = @{ $opt{'classnum'} };
+} else {
+  @classnums = split(',', $opt{'classnum'});
+}
+my $classnum_sql = 'classnum IN('.join(',', @classnums).')';
+</%init>
diff --git a/httemplate/search/elements/svc_Common.html b/httemplate/search/elements/svc_Common.html
new file mode 100644
index 0000000..56c75bb
--- /dev/null
+++ b/httemplate/search/elements/svc_Common.html
@@ -0,0 +1,48 @@
+<& search.html, %opt &>
+<%doc>
+Currently does nothing but insert the classnames for fields chosen from an
+inventory class.
+</%doc>
+<%init>
+my %opt = @_;
+my $query = $opt{query};
+my $svcdb = $query->{'table'};
+
+# to avoid looking up the inventory class of every service in the database,
+# keep as much of the base query as possible.
+my $item_query = { %$query };
+$item_query->{'table'} = 'inventory_item';
+$item_query->{'addl_from'} = 
+  " JOIN ( $svcdb ". $query->{'addl_from'} .
+  ") ON inventory_item.svcnum = $svcdb.svcnum ".
+  " JOIN inventory_class ON (inventory_item.classnum = inventory_class.classnum)";
+# avoid conflict with inventory_item.agentnum
+$item_query->{'extra_sql'} =~ s/ agentnum/ cust_main.agentnum/g;
+$item_query->{'select'} = 'inventory_item.svcnum, '.
+                          'inventory_item.svc_field, '.
+                          'inventory_class.classname';
+my @items = qsearch($item_query);
+my %item_fields;
+foreach my $i (@items) {
+  $item_fields{ $i->svc_field } ||= {};
+  $item_fields{ $i->svc_field }{ $i->svcnum } = $i->classname;
+}
+
+$opt{'sort_fields'} ||= [];
+for ( my $i = 0; $i < @{ $opt{'fields'} }; $i++ ) {
+  my $f = $opt{'fields'}[$i];
+  next if ref($f); # it's not a plain table column
+  $opt{'sort_fields'}[$i] ||= $f;
+  my $classnames = $item_fields{$f}; # hashref of svcnum -> classname
+  next if !$classnames; # there are no inventory items in this column
+  $opt{'fields'}[$i] = sub {
+    my $svc = $_[0];
+    if ( exists($classnames->{$svc->svcnum}) ) {
+      return $svc->$f . '<BR><I>('. $classnames->{$svc->svcnum} . ')</I>';
+    } else {
+      return $svc->$f;
+    }
+  }; #sub
+}
+
+</%init>
diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi
index 92e1c50..b9e5a7c 100755
--- a/httemplate/search/svc_acct.cgi
+++ b/httemplate/search/svc_acct.cgi
@@ -1,4 +1,4 @@
-<& elements/search.html,
+<& elements/svc_Common.html,
                  'title'       => emt('Account Search Results'),
                  'name'        => emt('accounts'),
                  'query'       => $sql_query,
diff --git a/httemplate/search/svc_broadband.cgi b/httemplate/search/svc_broadband.cgi
index c22887e..8366d21 100755
--- a/httemplate/search/svc_broadband.cgi
+++ b/httemplate/search/svc_broadband.cgi
@@ -1,4 +1,4 @@
-<& elements/search.html,
+<& elements/svc_Common.html,
               'title'       => 'Broadband Search Results',
               'name'        => 'broadband services',
               'html_init'   => $html_init,
diff --git a/httemplate/search/svc_dish.cgi b/httemplate/search/svc_dish.cgi
index 1578aa1..1f8cbc3 100755
--- a/httemplate/search/svc_dish.cgi
+++ b/httemplate/search/svc_dish.cgi
@@ -1,4 +1,4 @@
-<& elements/search.html,
+<& elements/svc_Common.html,
                  'title'       => 'Dish Network Search Results',
                  'name'        => 'services',
                  'query'       => $sql_query,
diff --git a/httemplate/search/svc_external.cgi b/httemplate/search/svc_external.cgi
index 1909987..b282939 100755
--- a/httemplate/search/svc_external.cgi
+++ b/httemplate/search/svc_external.cgi
@@ -1,4 +1,4 @@
-<& elements/search.html,
+<& elements/svc_Common.html,
                  'title'             => 'External service search results',
                  'name'              => 'external services',
                  'query'             => $sql_query,
diff --git a/httemplate/search/svc_hardware.cgi b/httemplate/search/svc_hardware.cgi
index 28aa132..93fc2c3 100644
--- a/httemplate/search/svc_hardware.cgi
+++ b/httemplate/search/svc_hardware.cgi
@@ -1,4 +1,4 @@
-<& elements/search.html,
+<& elements/svc_Common.html,
             'title'             => 'Hardware service search results',
             'name'              => 'installations',
             'query'             => $sql_query,
diff --git a/httemplate/search/svc_phone.cgi b/httemplate/search/svc_phone.cgi
index cc4aa60..f3a0564 100644
--- a/httemplate/search/svc_phone.cgi
+++ b/httemplate/search/svc_phone.cgi
@@ -1,4 +1,4 @@
-<& elements/search.html,
+<& elements/svc_Common.html,
                  'title'             => "Phone number search results",
                  'name'              => 'phone numbers',
                  'query'             => $sql_query,
diff --git a/httemplate/search/svc_www.cgi b/httemplate/search/svc_www.cgi
index eefe893..7410262 100755
--- a/httemplate/search/svc_www.cgi
+++ b/httemplate/search/svc_www.cgi
@@ -1,4 +1,4 @@
-<& elements/search.html,
+<& elements/svc_Common.html,
                  'title'       => 'Virtual Host Search Results',
                  'name'        => 'virtual hosts',
                  'query'       => $sql_query,
diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html
index a7f8f6d..d735195 100644
--- a/httemplate/view/elements/svc_Common.html
+++ b/httemplate/view/elements/svc_Common.html
@@ -57,6 +57,7 @@ function areyousure(href) {
 
 <% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>
 
+% my @inventory_items = $svc_x->inventory_item;
 % foreach my $f ( @$fields ) {
 %
 %   my($field, $type, $value);
@@ -76,6 +77,14 @@ function areyousure(href) {
 %   }
 %
 %   my $columndef = $part_svc->part_svc_column($field);
+%   if ( $columndef->columnflag =~ /^[MA]$/ && $columndef->columnvalue =~ /,/ )
+%   {
+%     # inventory-select field with multiple classes
+%     # show the class name to disambiguate
+%     my ($item) = grep { $_->svc_field eq $field } @inventory_items;
+%     my $class = qsearchs('inventory_class', { classnum => $item->classnum });
+%     $value .= ' <i>('. $class->classname . ')</i>' if $class;
+%   }
 %   unless ($columndef->columnflag eq 'F' && !length($columndef->columnvalue)) {
 
       <TR>
diff --git a/httemplate/view/svc_hardware.cgi b/httemplate/view/svc_hardware.cgi
index aa3ff00..eef1c11 100644
--- a/httemplate/view/svc_hardware.cgi
+++ b/httemplate/view/svc_hardware.cgi
@@ -13,6 +13,9 @@ my %labels = map { $_ =>  ( ref($fields->{$_})
                              : $fields->{$_}
                          );
                  } keys %$fields;
+
+$labels{'display_hw_addr'} = 'Hardware address';
+
 my $model =  { field => 'typenum',
                type  => 'text',
                value_callback => sub { $_[0]->hardware_type->description }

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/part_svc.pm                                 |    8 +-
 FS/FS/part_svc_column.pm                          |   10 +-
 FS/FS/svc_Common.pm                               |   41 ++-
 httemplate/browse/part_svc.cgi                    |   12 +-
 httemplate/edit/elements/edit.html                |    1 +
 httemplate/edit/elements/part_svc_column.html     |  303 +++++++++++
 httemplate/edit/elements/svc_Common.html          |   27 +-
 httemplate/edit/part_svc.cgi                      |  555 +++++----------------
 httemplate/edit/process/elements/svc_Common.html  |    5 +
 httemplate/elements/tr-select-inventory_item.html |   48 ++
 httemplate/search/elements/svc_Common.html        |   48 ++
 httemplate/search/svc_acct.cgi                    |    2 +-
 httemplate/search/svc_broadband.cgi               |    2 +-
 httemplate/search/svc_dish.cgi                    |    2 +-
 httemplate/search/svc_external.cgi                |    2 +-
 httemplate/search/svc_hardware.cgi                |    2 +-
 httemplate/search/svc_phone.cgi                   |    2 +-
 httemplate/search/svc_www.cgi                     |    2 +-
 httemplate/view/elements/svc_Common.html          |    9 +
 httemplate/view/svc_hardware.cgi                  |    3 +
 20 files changed, 617 insertions(+), 467 deletions(-)
 create mode 100644 httemplate/edit/elements/part_svc_column.html
 create mode 100644 httemplate/elements/tr-select-inventory_item.html
 create mode 100644 httemplate/search/elements/svc_Common.html




More information about the freeside-commits mailing list