agent virtualize address blocks and routers
authorjeff <jeff>
Sat, 28 Jun 2008 19:25:24 +0000 (19:25 +0000)
committerjeff <jeff>
Sat, 28 Jun 2008 19:25:24 +0000 (19:25 +0000)
26 files changed:
FS/FS/AccessRight.pm
FS/FS/addr_block.pm
FS/FS/router.pm
FS/FS/svc_broadband.pm
httemplate/browse/addr_block.cgi
httemplate/browse/router.cgi
httemplate/browse/svc_acct_pop.cgi
httemplate/edit/allocate.html
httemplate/edit/elements/edit.html
httemplate/edit/elements/svc_Common.html
httemplate/edit/process/addr_block/add.cgi
httemplate/edit/process/addr_block/allocate.cgi
httemplate/edit/process/addr_block/deallocate.cgi
httemplate/edit/process/addr_block/split.cgi
httemplate/edit/process/elements/process.html
httemplate/edit/process/router.cgi
httemplate/edit/process/svc_acct_pop.cgi
httemplate/edit/process/svc_broadband.cgi
httemplate/edit/router.cgi
httemplate/edit/svc_acct_pop.cgi
httemplate/edit/svc_broadband.cgi
httemplate/elements/checkboxes-table.html
httemplate/elements/menu.html
httemplate/elements/select-agent.html
httemplate/elements/select-table.html
httemplate/elements/tr-checkboxes-table.html [new file with mode: 0644]

index 5621a97..4e6eaaf 100644 (file)
@@ -238,6 +238,9 @@ tie my %rights, 'Tie::IxHash',
     'Edit billing events',
     { rightname=>'Edit global billing events', global=>1 },
   
+    { rightname=>'Engineering configuration' },
+    { rightname=>'Engineering global configuration', global=>1 },
+
     { rightname=>'Configuration', global=>1 }, #most of the rest of the configuraiton is not agent-virtualized
   ],
   
index 208684b..5815f1a 100755 (executable)
@@ -7,6 +7,7 @@ use FS::router;
 use FS::svc_broadband;
 use FS::Conf;
 use NetAddr::IP;
+use Carp qw( carp );
 
 @ISA = qw( FS::Record );
 
@@ -47,6 +48,8 @@ block is assigned.
 
 =item ip_netmask - the netmask of the block, expressed as an integer.
 
+=item agentnum - optional agent number (see L<FS::agent>)
+
 =back
 
 =head1 METHODS
@@ -84,6 +87,28 @@ sub delete {
 Replaces OLD_RECORD with this one in the database.  If there is an error,
 returns the error, otherwise returns false.
 
+At present it's not possible to reallocate a block to a different router 
+except by deallocating it first, which requires that none of its addresses 
+be assigned.  This is probably as it should be.
+
+sub replace_check {
+  my ( $new, $old ) = ( shift, shift );
+
+  unless($new->routernum == $old->routernum) {
+    my @svc = $self->svc_broadband;
+    if (@svc) {
+      return 'Block has assigned addresses: '.
+             join ', ', map {$_->ip_addr} @svc;
+    }
+
+    return 'Block is already allocated'
+      if($new->routernum && $old->routernum);
+
+  }
+
+  '';
+}
+
 =item check
 
 Checks all fields to make sure this is a valid record.  If there is an error,
@@ -99,6 +124,7 @@ sub check {
     $self->ut_number('routernum')
     || $self->ut_ip('ip_gateway')
     || $self->ut_number('ip_netmask')
+    || $self->ut_agentnum_acl('agentnum', 'Engineering global configuration')
   ;
   return $error if $error;
 
@@ -202,7 +228,7 @@ my @used =
 
 }
 
-=item allocate
+=item allocate -- deprecated
 
 Allocates this address block to a router.  Takes an FS::router object 
 as an argument.
@@ -215,25 +241,18 @@ be assigned.  This is probably as it should be.
 
 sub allocate {
   my ($self, $router) = @_;
-
-  return 'Block is already allocated'
-    if($self->router);
+  carp "deallocate deprecated -- use replace";
 
   return 'Block must be allocated to a router'
     unless(ref $router eq 'FS::router');
 
-  my @svc = $self->svc_broadband;
-  if (@svc) {
-    return 'Block has assigned addresses: '. join ', ', map {$_->ip_addr} @svc;
-  }
-
   my $new = new FS::addr_block {$self->hash};
   $new->routernum($router->routernum);
   return $new->replace($self);
 
 }
 
-=item deallocate
+=item deallocate -- deprecated
 
 Deallocates the block (i.e. sets the routernum to 0).  If any addresses in the 
 block are assigned to services, it fails.
@@ -241,13 +260,9 @@ block are assigned to services, it fails.
 =cut
 
 sub deallocate {
+  carp "deallocate deprecated -- use replace";
   my $self = shift;
 
-  my @svc = $self->svc_broadband;
-  if (@svc) {
-    return 'Block has assigned addresses: '. join ', ', map {$_->ip_addr} @svc;
-  }
-
   my $new = new FS::addr_block {$self->hash};
   $new->routernum(0);
   return $new->replace($self);
@@ -328,6 +343,29 @@ sub split_block {
 
 To be implemented.
 
+=item agent
+
+Returns the agent (see L<FS::agent>) for this address block, if one exists.
+
+=cut
+
+sub agent {
+  qsearchs('agent', { 'agentnum' => shift->agentnum } );
+}
+
+=item label
+
+Returns text including the router name, gateway ip, and netmask for this
+block.
+
+=cut
+
+sub label {
+  my $self = shift;
+  my $router = $self->router;
+  ($router ? $router->routername : '(unallocated)'). ':'. $self->NetAddr;
+}
+
 =back
 
 =head1 BUGS
index 88ba990..bfc5530 100755 (executable)
@@ -5,7 +5,7 @@ use vars qw( @ISA );
 use FS::Record qw( qsearchs qsearch );
 use FS::addr_block;
 
-@ISA = qw( FS::Record );
+@ISA = qw( FS::Record FS::m2m_Common );
 
 =head1 NAME
 
@@ -82,7 +82,9 @@ sub check {
 
   my $error =
     $self->ut_numbern('routernum')
-    || $self->ut_text('routername');
+    || $self->ut_text('routername')
+    || $self->ut_agentnum_acl('agentnum', 'Engineering global configuration')
+  ;
   return $error if $error;
 
   $self->SUPER::check;
@@ -125,6 +127,16 @@ sub part_svc {
       $self->part_svc_router;
 }
 
+=item agent
+
+Returns the agent associated with this router, if any.
+
+=cut
+
+sub agent {
+  qsearchs('agent', { 'agentnum' => shift->agentnum });
+}
+
 =back
 
 =head1 BUGS
index d123524..fa90437 100755 (executable)
@@ -220,6 +220,24 @@ sub check {
   if($self->speed_up < 0) { return 'speed_up must be positive'; }
   if($self->speed_down < 0) { return 'speed_down must be positive'; }
 
+  my $cust_svc = $self->svcnum
+                 ? qsearchs('cust_svc', { 'svcnum' => $self->svcnum } )
+                 : '';
+  my $cust_pkg;
+  if ($cust_svc) {
+    $cust_pkg = $cust_svc->cust_pkg;
+  }else{
+    $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $self->pkgnum } );
+    return "Invalid pkgnum" unless $cust_pkg;
+  }
+    
+  if ($cust_pkg) {
+    my $addr_agentnum = $self->addr_block->agentnum;
+    if ($addr_agentnum && $addr_agentnum != $cust_pkg->cust_main->agentnum) {
+      return "Address block does not service this customer";
+    }
+  }
+
   if (not($self->ip_addr) or $self->ip_addr eq '0.0.0.0') {
     my $next_addr = $self->addr_block->next_free_addr;
     if ($next_addr) {
@@ -290,6 +308,9 @@ sub allowed_routers {
 
 The business with sb_field has been 'fixed', in a manner of speaking.
 
+allowed_routers isn't agent virtualized because part_svc isn't agent
+virtualized
+
 =head1 SEE ALSO
 
 FS::svc_Common, FS::Record, FS::addr_block,
index d597ee1..917f94c 100644 (file)
@@ -2,13 +2,13 @@
                 'title'         => 'Address Blocks',
                 'name'          => 'address block',
                 'html_init'     => $html_init,
-                'html_form'     => $html_form,
+                'html_foot'     => $html_foot,
                 'query'         => { 'table'     => 'addr_block',
                                      'hashref'   => {},
                                      'extra_sql' => $extra_sql,
                                      'order_by'  => $order_by,
                                    },
-                'count_query'   => "SELECT count(*) from addr_block $extra_sql",
+                'count_query'   => "SELECT count(*) from addr_block $count_sql",
                 'header'        => [ 'Address Block',
                                      'Router',
                                      'Action(s)',
                                      'border-right:none;',
                                      'border-left:none;',
                                    ],
+                'agent_virt'    => 1,
+                'agent_null_right' => 'Engineering global configuration',
+                'agent_pos'     => 1,
           )
 %>
 <%init>
 
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+  unless $FS::CurrentUser::CurrentUser->access_right('Engineering configuration')
+  || $FS::CurrentUser::CurrentUser->access_right('Engineering global configuration');
 
 my $p2 = popurl(2);
 my $path = $p2 . "edit/process/addr_block";
 
-my $extra_sql = " ";
+my $extra_sql = "";
+
+my $count_sql = "WHERE ". $FS::CurrentUser::CurrentUser->agentnums_sql(
+  'null_right' => 'Engineering global configuration',
+);
+
 my $order_by = "ORDER BY ";
 $order_by .= "inet(ip_gateway), " if driver_name =~ /^Pg/i;
 $order_by .= "inet_aton(ip_gateway), " if driver_name =~ /^mysql/i;
@@ -74,10 +83,16 @@ my $confirm = sub {
   "javascript:addr_block_areyousure('$path/$verb.cgi?blocknum=$num', '$verb')";
 };
 
-my $html_form = qq(
+my $html_foot = qq(
   <FORM ACTION="$path/add.cgi" METHOD="POST">
   Gateway/Netmask: 
   <INPUT TYPE="text" NAME="ip_gateway" SIZE="15">/<INPUT TYPE="text" NAME="ip_netmask" SIZE="2">
+);
+$html_foot .= include( '/elements/select-agent.html',
+                       'agent_virt'       => 1,
+                       'agent_null_right' => 'Engineering global configuration',
+                     );
+$html_foot .= qq(
   <INPUT TYPE="submit" NAME="submit" VALUE="Add">
   </FORM>
 );
index 9d856f6..9f71673 100644 (file)
@@ -6,7 +6,7 @@
                                        'hashref'   => {},
                                        'extra_sql' => $extra_sql,
                                      },
-                'count_query'     => "SELECT count(*) from router $extra_sql",
+                'count_query'     => "SELECT count(*) from router $count_sql",
                 'header'          => [ 'Router name',
                                        'Address block(s)',
                                      ],
                 'links'           => [ [ "${p2}edit/router.cgi?", 'routernum' ],
                                        '',
                                      ],
+                'agent_virt'      => 1,
+                'agent_null_right'=> "Engineering global configuration",
+                'agent_pos'       => 1,
           )
 %>
 <%init>
 
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+  unless $FS::CurrentUser::CurrentUser->access_right('Engineering configuration')
+  || $FS::CurrentUser::CurrentUser->access_right('Engineering global configuration');
 
 my $p2 = popurl(2);
 my $extra_sql = '';
@@ -40,4 +44,9 @@ if ($cgi->param('hidecustomerrouters') eq '1') {
   push @menubar, 'Hide customer routers', $cgi->self_url();
 }
 
+my $count_sql = $extra_sql.  ( $extra_sql =~ /WHERE/ ? ' AND' : 'WHERE' ).
+  $FS::CurrentUser::CurrentUser->agentnums_sql(
+    'null_right' => 'Engineering global configuration',
+  );
+
 </%init>
index 44bc651..4e49371 100755 (executable)
 %>
 <%init>
 
+my $curuser = $FS::CurrentUser::CurrentUser;
+
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+  unless $curuser->access_right('Engineering configuration')
+      || $curuser->access_right('Engineering global configuration');
 
 my $html_init = qq!
   <A HREF="${p}edit/svc_acct_pop.cgi"><I>Add new Access Number</I></A>
index 29c16ae..0f05fcc 100644 (file)
                                      'table'         => 'router',
                                      'name_col'      => 'routername',
                                      'disable_empty' => 1,
+                                     'agent_virt'    => 1,
+                                     'agent_null_right' =>
+                                       'Engineering global configuration',
                                    },
                                  ],
                 'post_url'    => "process/addr_block/allocate.cgi",
                 'popup'       => 1,
+                'agent_virt'  => 1,
+                'agent_null_right' => 'Engineering global configuration',
           )
 %>
 <%init>
 
+my $curuser = $FS::CurrentUser::CurrentUser;
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+  unless $curuser->access_right('Engineering configuration')
+      || $curuser->access_right('Engineering global configuration');
 
 </%init>
index 86eb2b3..47b2464 100644 (file)
@@ -108,8 +108,8 @@ Example:
     #run before display to return a different value
     'value_callback' => sub { my( $columname, $value ) = @_; },
 
-    #XXX describe
-    'field_callback' => sub { },
+    #run before display to manipulate element of the 'fields' arrayref
+    'field_callback' => sub { my( $cgi, $object, $field_hashref ) = @_; },
 
     'viewall_dir' => '', #'search' or 'browse', defaults to 'search'
 
@@ -179,7 +179,7 @@ Example:
 %                       @$fields
 %                 ) {
 %
-%   &{ $opt{'field_callback'} }( $f )
+%   my $trash = &{ $opt{'field_callback'} }( $cgi, $object, $f )
 %     if $opt{'field_callback'};
 %
 %   my $field = $f->{'field'};
@@ -227,10 +227,18 @@ Example:
 %     'disabled'      => $f->{'disabled'},
 %   );
 %
-%   #select-table
+%   #select-table, checkboxes-table
 %   $include_common{$_} = $f->{$_}
 %     foreach grep exists($f->{$_}), qw( table name_col );
 %
+%   #checkboxes-table
+%   $include_common{$_} = $f->{$_}
+%     foreach grep exists($f->{$_}), qw( target_table link_table );
+%
+%   #*-table
+%   $include_common{$_} = $f->{$_}
+%     foreach grep exists($f->{$_}), qw( hashref agent_virt agent_null_right );
+%
 %   if ( $type eq 'tablebreak-tr-title' ) {
 %     $include_common{'table_id'} = 'TableNumber'. $tablenum++
 %   }
index 72abcba..b6737c1 100644 (file)
@@ -13,7 +13,7 @@
 %    $pkgnum = $1;
 %    $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart';
 %    $svcpart = $1;
-%    $cgi->delete_all(); #so edit.html treats this correctly as new??
+%    #$cgi->delete_all(); #so edit.html treats this correctly as new??
 %  }
 %
 <% include( 'edit.html',
@@ -63,7 +63,7 @@
                  },
 
                  'field_callback' => sub {
-                   my $f = shift;
+                   my ($cgi, $object, $f) = @_;
                    my $columndef = $part_svc->part_svc_column($f->{'field'});
                    my $flag = $columndef->columnflag;
                    if ( $flag eq 'F' ) {
                    }
                  },
 
+                 'html_init' => sub {
+                   my $cust_main;
+                   if ( $pkgnum ) {
+                     my $cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum});
+                     $cust_main = $cust_pkg->cust_main if $cust_pkg;
+                   }
+                   $cust_main
+                     ? include( '/elements/small_custview.html',
+                                $cust_main,
+                                '',
+                                1,
+                                popurl(2). "view/cust_main.cgi"
+                              ). '<BR>'
+                     : '';
+
+                 },
+
                  'html_table_bottom' => sub {
                    my $svc_x = shift;
                    my $html = '';
index e9f9b97..4321f94 100755 (executable)
@@ -1,28 +1,20 @@
-%
-%
-%my $error = '';
-%my $ip_gateway = $cgi->param('ip_gateway');
-%my $ip_netmask = $cgi->param('ip_netmask');
-%
-%my $new = new FS::addr_block {
-%    ip_gateway => $ip_gateway,
-%    ip_netmask => $ip_netmask,
-%    routernum  => 0 };
-%
-%$error = $new->insert;
-%
-%if ( $error ) {
-%  $cgi->param('error', $error);
-%  print $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string );
-%} else { 
-%  print $cgi->redirect(popurl(4). "browse/addr_block.cgi");
-%} 
-%
+<% include( '../elements/process.html',
+            'table'            => 'addr_block',
+            'redirect'         => popurl(4). 'browse/addr_block.cgi?dummy=',
+            'error_redirect'   => popurl(4). 'browse/addr_block.cgi?',
+            'agent_virt'       => 1,
+            'agent_null_right' => 'Engineering global configuration',
 
+          )
+%>
 <%init>
 
-my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+  unless $curuser->access_right('Engineering configuration')
+      || $curuser->access_right('Engineering global configuration');
+
+$cgi->param('routernum', 0)           # in FS::addr_block::check instead?
+  unless $cgi->param('routernum');
 
 </%init>
index d1bd73f..f377d81 100755 (executable)
@@ -8,7 +8,9 @@
 <%init>
 
 my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+  unless $curuser->access_right('Engineering configuration')
+      || $curuser->access_right('Engineering global configuration');
 
 </%init>
index 95e1b7e..91184b3 100755 (executable)
@@ -1,32 +1,20 @@
-%
-%my $error = '';
-%my $blocknum = $cgi->param('blocknum');
-%
-%my $addr_block = qsearchs('addr_block', { blocknum => $blocknum });
-%
-%if($addr_block) {
-%  my $router = $addr_block->router;
-%  if ($router) {
-%    $error = $addr_block->deallocate($router);
-%  } else {
-%    $error = "Block is not allocated to a router";
-%  }
-%} else {
-%  $error = "Cannot find block with blocknum $blocknum";
-%}
-%
-%if ( $error ) {
-%  $cgi->param('error', $error);
-%  print $cgi->redirect(popurl(4). "browse/addr_block.cgi?" . $cgi->query_string);
-%} else { 
-%  print $cgi->redirect(popurl(4). "browse/addr_block.cgi");
-%}
-%
-
+<% include( '../elements/process.html',
+            'table'            => 'addr_block',
+            'copy_on_empty'    => [ grep { $_ ne 'routernum' }
+                                    fields 'addr_block' ],
+            'redirect'         => popurl(4). 'browse/addr_block.cgi?',
+            'error_redirect'   => popurl(4). 'browse/addr_block.cgi?',
+            'agent_virt'       => 1,
+            'agent_null_right' => 'Engineering global configuration',
+          )
+%>
 <%init>
 
 my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+  unless $curuser->access_right('Engineering configuration')
+      || $curuser->access_right('Engineering global configuration');
 
+$cgi->param('routernum', 0);  # just to be explicit about what we are doing
 </%init>
index b3a33b1..eb55d00 100755 (executable)
@@ -1,27 +1,27 @@
-%
-%my $error = '';
-%my $blocknum = $cgi->param('blocknum');
-%my $addr_block = qsearchs('addr_block', { blocknum => $blocknum });
-%
-%if ( $addr_block) {
-%  $error = $addr_block->split_block;
-%} else {
-%  $error = "Unknown blocknum: $blocknum";
-%}
-%
-%
-%if ( $error ) {
-%  $cgi->param('error', $error);
-%  print $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string );
-%} else { 
-%  print $cgi->redirect(popurl(4). "browse/addr_block.cgi");
-%} 
-%
-
+<% $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string ) %>
 <%init>
 
-my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
+
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+  unless $curuser->access_right('Engineering configuration')
+      || $curuser->access_right('Engineering global configuration');
+
+my $error = '';
+$cgi->param('blocknum') =~ /^(\d+)$/ or die "invalid blocknum";
+my $blocknum = $1;
+
+my $addr_block = qsearchs({ 'table'     => 'addr_block',
+                            'hashref'   => { blocknum => $blocknum },
+                            'extra_sql' => ' AND '. $curuser->agentnums_sql(
+                              'null_right' => 'Engineering global configuration'
+                            ),
+                         })
+  or $error = "Unknown blocknum: $blocknum";
+
+$error ||= $addr_block->split_block;
+
+$cgi->param('error', $error)
+  if $error;
 
 </%init>
index 6f271ee..de0304a 100644 (file)
@@ -33,7 +33,8 @@ Example:
 
    'clear_on_error' => [ 'form_field1', 'form_field2', ... ],
 
-                    #pass an arrayref of hashrefs for multiple m2ms or m2names
+                  #pass an arrayref of hashrefs for multiple m2ms or m2names
+                  #be certain you incorporate m2m_Common if you see error: param
 
    'process_m2m' => { 'link_table'   => 'link_table_name',
                       'target_table' => 'target_table_name',
@@ -176,6 +177,13 @@ my %hash =
 
 my $new = $class->new( \%hash );
 
+if ($old && exists($opt{'copy_on_empty'})) {
+  foreach my $field (@{$opt{'copy_on_empty'}}) {
+    $new->set($field, $old->get($field))
+      unless scalar($cgi->param($field));
+  }
+}
+
 if ( $opt{'agent_virt'} ) {
   die "illegal agentnum"
     unless $curuser->agentnums_href->{$new->agentnum}
@@ -184,13 +192,6 @@ if ( $opt{'agent_virt'} ) {
            && $curuser->access_right($opt{'agent_null_right'});
 }
 
-if ($old && exists($opt{'copy_on_empty'})) {
-  foreach my $field (@{$opt{'copy_on_empty'}}) {
-    $new->set($field, $old->get($field))
-      unless scalar($cgi->param($field));
-  }
-}
-
 $error ||= $new->check;
 
 my @args = ();
index 7e0baf7..6e717d1 100644 (file)
@@ -1,70 +1,20 @@
-%local $FS::UID::AutoCommit=0;
-%
-%sub check {
-%  my $error = shift;
-%  if($error) {
-%    $cgi->param('error', $error);
-%    print $cgi->redirect(popurl(3) . "edit/router.cgi?". $cgi->query_string);
-%    dbh->rollback;
-%    exit;
-%  }
-%}
-%
-%my $error = '';
-%my $routernum  = $cgi->param('routernum');
-%my $routername = $cgi->param('routername');
-%my $old = qsearchs('router', { routernum => $routernum });
-%my @old_psr;
-%
-%my $new = new FS::router {
-%  map {
-%    ($_, scalar($cgi->param($_)));
-%  } fields('router')
-%};
-%
-%if($old) {
-%  $error = $new->replace($old);
-%} else {
-%  $error = $new->insert;
-%  $routernum = $new->routernum;
-%}
-%
-%check($error);
-%
-%if ($old) {
-%  @old_psr = $old->part_svc_router;
-%  foreach my $psr (@old_psr) {
-%    if($cgi->param('svcpart_'.$psr->svcpart) eq 'ON') {
-%      # do nothing
-%    } else {
-%      $error = $psr->delete;
-%    }
-%  }
-%  check($error);
-%}
-%
-%foreach($cgi->param) {
-%  if($cgi->param($_) eq 'ON' and /^svcpart_(\d+)$/) {
-%    my $svcpart = $1;
-%    if(grep {$_->svcpart == $svcpart} @old_psr) {
-%      # do nothing
-%    } else {
-%      my $new_psr = new FS::part_svc_router { svcpart   => $svcpart,
-%                                              routernum => $routernum };
-%      $error = $new_psr->insert;
-%    }
-%    check($error);
-%  }
-%}
-%
-%
-%# Yay, everything worked!
-%dbh->commit or die dbh->errstr;
-%print $cgi->redirect(popurl(3). "browse/router.cgi");
-%
+<% include('elements/process.html',
+           'table'            => 'router',
+           'viewall_dir'      => 'browse',
+           'viewall_ext'      => 'cgi',
+           'edit_ext'         => 'cgi',
+           'process_m2m'      => { 'link_table'   => 'part_svc_router',
+                                   'target_table' => 'part_svc',
+                                 },
+           'agent_virt'       => 1,
+           'agent_null_right' => 'Engineering global configuration',
+   )
+%>
 <%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
 
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+  unless $curuser->access_right('Engineering configuration')
+      || $curuser->access_right('Engineering global configuration');
 
 </%init>
index 75b89c8..522e949 100755 (executable)
@@ -6,8 +6,11 @@
 %}
 <%init>
 
+my $curuser = $FS::CurrentUser::CurrentUser;
+
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+  unless $curuser->access_right('Engineering configuration')
+      || $curuser->access_right('Engineering global configuration');
 
 my $popnum = $cgi->param('popnum');
 
index 8600da3..d5c9820 100644 (file)
@@ -1,38 +1,8 @@
-%if ( $error ) {
-%  $cgi->param('error', $error);
-%  $cgi->param('ip_addr', $new->ip_addr);
-<% $cgi->redirect(popurl(2). "svc_broadband.cgi?". $cgi->query_string ) %>
-%} else {
-<% $cgi->redirect(popurl(3). "view/svc_broadband.cgi?" . $svcnum ) %>
-%}
+<% include('elements/svc_Common.html', 'table' => 'svc_broadband') %>
 <%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
 
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
-
-$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
-my $svcnum = $1;
-
-my $old;
-if ( $svcnum ) {
-  $old = qsearchs('svc_broadband', { 'svcnum' => $svcnum } )
-    or die "fatal: can't find broadband service (svcnum $svcnum)!";
-} else {
-  $old = '';
-}
-
-my $new = new FS::svc_broadband ( {
-  map {
-    ($_, scalar($cgi->param($_)));
-  } ( fields('svc_broadband'), qw( pkgnum svcpart ) )
-} );
-
-my $error;
-if ( $svcnum ) {
-  $error = $new->replace($old);
-} else {
-  $error = $new->insert;
-  $svcnum = $new->svcnum;
-}
+  unless $curuser->access_right('Provision customer service'); #something else more specific?
 
 </%init>
index c08e544..180dcb7 100755 (executable)
@@ -1,78 +1,44 @@
-<% include('/elements/header.html', "$action Router", menubar(
-     'View all routers' => "${p}browse/router.cgi",
-   ))
+<% include('elements/edit.html',
+     'post_url'    => popurl(1).'process/router.cgi',
+     'name'        => 'router',
+     'table'       => 'router',
+     'viewall_url' => "${p}browse/router.cgi",
+     'labels'      => { 'routernum'  => 'Router',
+                        'routername' => 'Name',
+                        'svc_part'   => 'Service',
+                      },
+     'fields'      => [
+                        { 'field'=>'routername', 'type'=>'text', 'size'=>32 },
+                        { 'field'=>'agentnum',   'type'=>'select-agent' },
+                      ],
+     'error_callback' => $callback,
+     'edit_callback'  => $callback,
+     'new_callback'   => $callback,
+   )
 %>
-
-<% include('/elements/error.html') %>
-
-<FORM ACTION="<%popurl(1)%>process/router.cgi" METHOD=POST>
-  <INPUT TYPE="hidden" NAME="table" VALUE="router">
-  <INPUT TYPE="hidden" NAME="redirect_ok" VALUE="<%$p3%>/browse/router.cgi">
-  <INPUT TYPE="hidden" NAME="redirect_error" VALUE="<%$p3%>/edit/router.cgi">
-  <INPUT TYPE="hidden" NAME="routernum" VALUE="<%$routernum%>">
-  <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$router->svcnum%>">
-    Router #<%$routernum or "(NEW)"%>
-
-<BR><BR>Name <INPUT TYPE="text" NAME="routername" SIZE=32 VALUE="<%$router->routername%>">
-
-<BR><BR>
-Custom fields:
-<BR>
-<%table() %>
-%
-%foreach my $field ($router->virtual_fields) {
-%  print $router->pvf($field)->widget('HTML', 'edit', 
-%        $router->getfield($field));
-%}
-%
-
-</TABLE>
-%
-%unless ($router->svcnum) {
-%
-
-<BR><BR>Select the service types available on this router<BR>
-%
-%
-%  foreach my $part_svc ( qsearch('part_svc', { svcdb    => 'svc_broadband',
-%                                               disabled => '' }) ) {
-%  
-
-  <BR>
-  <INPUT TYPE="checkbox" NAME="svcpart_<%$part_svc->svcpart%>"<%
-      qsearchs('part_svc_router', { svcpart   => $part_svc->svcpart, 
-                                    routernum => $routernum } ) ? ' CHECKED' : ''%> VALUE="ON">
-  <A HREF="<%${p}%>edit/part_svc.cgi?<%$part_svc->svcpart%>">
-    <%$part_svc->svcpart%>: <%$part_svc->svc%></A>
-% } 
-% } 
-
-
-  <BR><BR><INPUT TYPE="submit" VALUE="Apply changes">
-  </FORM>
-
-<% include('/elements/footer.html') %>
-
 <%init>
 
-die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+my $curuser = $FS::CurrentUser::CurrentUser;
 
-my $router;
-if ( $cgi->keywords ) {
-  my($query) = $cgi->keywords;
-  $query =~ /^(\d+)$/;
-  $router = qsearchs('router', { routernum => $1 }) 
-      or print $cgi->redirect(popurl(2)."browse/router.cgi") ;
-} else {
-  $router = new FS::router ( {
-    map { $_, scalar($cgi->param($_)) } fields('router')
-  } );
-}
-
-my $routernum = $router->routernum;
-my $action = $routernum ? 'Edit' : 'Add';
-
-my $p3 = popurl(3);
+die "access denied"
+  unless $curuser->access_right('Engineering configuration')
+    || $curuser->access_right('Engineering global configuration');
+
+my $callback = sub {
+  my ($cgi, $object, $fields) = (shift, shift, shift);
+  unless ($object->svcnum) {
+    push @{$fields},
+      { 'type'          => 'tablebreak-tr-title',
+        'value'         => 'Select the service types available on this router',
+      },
+      { 'field'         => 'svc_part',
+        'type'          => 'checkboxes-table',
+        'target_table'  => 'part_svc',
+        'link_table'    => 'part_svc_router',
+        'name_col'      => 'svc',
+        'hashref'       => { 'svcdb' => 'svc_broadband', 'disabled' => '' },
+      };
+  }
+};
 
 </%init>
index 3c16a1f..b0ae1c3 100755 (executable)
@@ -27,8 +27,11 @@ Local     <INPUT TYPE="text" NAME="loc" SIZE=5 MAXLENGTH=4 VALUE="<% $hashref->{
 
 <%init>
 
+my $curuser = $FS::CurrentUser::CurrentUser;
+
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+  unless $curuser->access_right('Engineering configuration')
+      || $curuser->access_right('Engineering global configuration');
 
 my $svc_acct_pop;
 if ( $cgi->param('error') ) {
index c2fb58d..25fb009 100644 (file)
-<% include('/elements/header.html', "Broadband Service $action") %>
-
-<% include('/elements/error.html') %>
-
-Service #<B><%$svcnum ? $svcnum : "(NEW)"%></B><BR><BR>
-
-<FORM ACTION="<%${p1}%>process/svc_broadband.cgi" METHOD=POST>
-  <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>">
-  <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%$pkgnum%>">
-  <INPUT TYPE="hidden" NAME="svcpart" VALUE="<%$svcpart%>">
-
-  <%&ntable("#cccccc",2)%>
-    <TR>
-      <TD ALIGN="right">Description</TD>
-      <TD BGCOLOR="#ffffff">
-% if ( $part_svc->part_svc_column('description')->columnflag eq 'F' ) { 
-
-        <INPUT TYPE="hidden" NAME="description" VALUE="<%$description%>"><%$description%>
-% } else { 
-
-    <INPUT TYPE="text" NAME="description" VALUE="<%$description%>">
-% } 
-
-      </TD>
-    </TR>
-    <TR>
-      <TD ALIGN="right">IP Address</TD>
-      <TD BGCOLOR="#ffffff">
-% if ( $part_svc->part_svc_column('ip_addr')->columnflag eq 'F' ) { 
-
-        <INPUT TYPE="hidden" NAME="ip_addr" VALUE="<%$ip_addr%>"><%$ip_addr%>
-% } else { 
-
-        <INPUT TYPE="text" NAME="ip_addr" VALUE="<%$ip_addr%>">
-% } 
-
-      </TD>
-    </TR>
-    <TR>
-      <TD ALIGN="right">Download speed</TD>
-      <TD BGCOLOR="#ffffff">
-% if ( $part_svc->part_svc_column('speed_down')->columnflag eq 'F' ) { 
-
-        <INPUT TYPE="hidden" NAME="speed_down" VALUE="<%$speed_down%>"><%$speed_down%>Kbps
-% } else { 
-
-    <INPUT TYPE="text" NAME="speed_down" SIZE=5 VALUE="<%$speed_down%>">Kbps
-% } 
-
-      </TD>
-    </TR>
-    <TR>
-      <TD ALIGN="right">Upload speed</TD>
-      <TD BGCOLOR="#ffffff">
-% if ( $part_svc->part_svc_column('speed_up')->columnflag eq 'F' ) { 
-
-        <INPUT TYPE="hidden" NAME="speed_up" VALUE="<%$speed_up%>"><%$speed_up%>Kbps
-% } else { 
-
-        <INPUT TYPE="text" NAME="speed_up" SIZE=5 VALUE="<%$speed_up%>">Kbps
-% } 
-
-      </TD>
-    </TR>
-% if ($action eq 'Add') { 
-
-    <TR>
-      <TD ALIGN="right">Router/Block</TD>
-      <TD BGCOLOR="#ffffff">
-        <SELECT NAME="blocknum">
-%
-%  warn $svc_broadband->svcpart;
-%  foreach my $router ($svc_broadband->allowed_routers) {
-%    warn $router->routername;
-%    foreach my $addr_block ($router->addr_block) {
-%
-
-        <OPTION VALUE="<%$addr_block->blocknum%>"<%($addr_block->blocknum eq $blocknum) ? ' SELECTED' : ''%>>
-          <%$router->routername%>:<%$addr_block->ip_gateway%>/<%$addr_block->ip_netmask%></OPTION>
-%
-%    }
-%  }
-%
-
-        </SELECT>
-      </TD>
-    </TR>
-% } else { 
-
-
-    <TR>
-      <TD ALIGN="right">Router/Block</TD>
-      <TD BGCOLOR="#ffffff">
-        <%$svc_broadband->addr_block->router->routername%>:<%$svc_broadband->addr_block->NetAddr%>
-        <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%$svc_broadband->blocknum%>">
-      </TD>
-    </TR>
-% } 
-    <TR>
-      <TD ALIGN="right">MAC Address</TD>
-      <TD BGCOLOR="#ffffff">
-        <INPUT TYPE="text" NAME="mac_addr" VALUE="<%$mac_addr%>">
-      </TD>
-    </TR>
-    <TR>
-      <TD ALIGN="right">Latitude</TD>
-      <TD BGCOLOR="#ffffff">
-        <INPUT TYPE="text" NAME="latitude" VALUE="<%$latitude%>">
-      </TD>
-    </TR>
-    <TR>
-      <TD ALIGN="right">Longitude</TD>
-      <TD BGCOLOR="#ffffff">
-        <INPUT TYPE="text" NAME="longitude" VALUE="<%$longitude%>">
-      </TD>
-    </TR>
-    <TR>
-      <TD ALIGN="right">Altitude</TD>
-      <TD BGCOLOR="#ffffff">
-        <INPUT TYPE="text" NAME="altitude" VALUE="<%$altitude%>">
-      </TD>
-    </TR>
-    <TR>
-      <TD ALIGN="right">VLAN Profile</TD>
-      <TD BGCOLOR="#ffffff">
-% if ( $part_svc->part_svc_column('vlan_profile')->columnflag eq 'F' ) { 
-
-        <INPUT TYPE="hidden" NAME="vlan_profile" VALUE="<%$vlan_profile%>"><%$vlan_profile%>
-% } else { 
-
-        <INPUT TYPE="text" NAME="vlan_profile" VALUE="<%$vlan_profile%>">
-% } 
-
-      </TD>
-    </TR>
-    <TR>
-      <TD ALIGN="right">Authentication Key</TD>
-      <TD BGCOLOR="#ffffff">
-% if ( $part_svc->part_svc_column('auth_key')->columnflag eq 'F' ) { 
-
-        <INPUT TYPE="hidden" NAME="auth_key" VALUE="<%$auth_key%>"><%$auth_key%>
-% } else { 
-
-        <INPUT TYPE="text" NAME="auth_key" VALUE="<%$auth_key%>">
-% } 
-
-      </TD>
-    </TR>
-%
-%foreach my $field ($svc_broadband->virtual_fields) {
-%  if ( $part_svc->part_svc_column($field)->columnflag ne 'F' &&
-%       $part_svc->part_svc_column($field)->columnflag ne 'X') {
-%    print $svc_broadband->pvf($field)->widget('HTML', 'edit',
-%        $svc_broadband->getfield($field));
-%  }
-%} 
-
-  </TABLE>
-  <BR>
-  <INPUT TYPE="submit" NAME="submit" VALUE="Submit">
-</FORM>
-
-<% include('/elements/footer.html') %>
-
+<% include('elements/svc_Common.html',
+     'post_url'             => popurl(1). 'process/svc_broadband.cgi',
+     'name'                 => 'broadband service',
+     'table'                => 'svc_broadband',
+     'labels'               => { 'svcnum'       => 'Service #',
+                                 'description'  => 'Description',
+                                 'ip_addr'      => 'IP address',
+                                 'speed_down'   => 'Download speed',
+                                 'speed_up'     => 'Upload speed',
+                                 'blocknum'     => 'Router/Block',
+                                 'block_disp'   => 'Router/Block',
+                                 'mac_addr'     => 'MAC address',
+                                 'latitude'     => 'Latitude',
+                                 'longitude'    => 'Longitude',
+                                 'altitude'     => 'Altitude',
+                                 'vlan_profile' => 'VLAN profile',
+                                 'authkey'      => 'Authentication key',
+                               },
+     'fields'               => \@fields, 
+     'field_callback'       => $callback,
+     'dummy'                => $cgi->query_string,
+     )
+%>
 <%init>
 
 die "access denied"
@@ -170,85 +29,58 @@ die "access denied"
 # If it's stupid but it works, it's still stupid.
 #  -Kristian
 
-use HTML::Widgets::SelectLayers;
-use Tie::IxHash;
-
-my( $svcnum,  $pkgnum, $svcpart, $part_svc, $svc_broadband );
-if ( $cgi->param('error') ) {
-
-  $svc_broadband = new FS::svc_broadband ( {
-    map { $_, scalar($cgi->param($_)) } fields('svc_broadband'), qw(svcpart)
-  } );
-  $svcnum = $svc_broadband->svcnum;
-  $pkgnum = $cgi->param('pkgnum');
-  $svcpart = $svc_broadband->svcpart;
-  $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
-  die "No part_svc entry!" unless $part_svc;
-
-} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding
-
-  $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum';
-  $pkgnum = $1;
-  $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart';
-  $svcpart = $1;
-
-  $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
-  die "No part_svc entry!" unless $part_svc;
-
-  $svc_broadband = new FS::svc_broadband({ svcpart => $svcpart });
-
-  $svcnum='';
+my @fields = (
+  qw( description ip_addr speed_down speed_up blocknum ),
+  { field=>'block_label', type=>'fixed' },
+  qw( mac_addr latitude longitude altitude vlan_profile authkey )
+);
 
-  $svc_broadband->set_default_and_fixed;
+my $callback = sub {
+  my ($cgi, $object, $fieldref) = @_;
 
-} else { #editing
+  my $svcpart = $object->svcnum ? $object->cust_svc->svcpart
+                                : $cgi->param('svcpart');
 
-  my($query) = $cgi->keywords;
-  $query =~ /^(\d+)$/ or die "unparsable svcnum";
-  $svcnum=$1;
-  $svc_broadband=qsearchs('svc_broadband',{'svcnum'=>$svcnum})
-    or die "Unknown (svc_broadband) svcnum!";
-
-  my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
-    or die "Unknown (cust_svc) svcnum!";
-
-  $pkgnum=$cust_svc->pkgnum;
-  $svcpart=$cust_svc->svcpart;
-  
-  $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+  my $part_svc = qsearchs( 'part_svc', { svcpart => $svcpart } );
   die "No part_svc entry!" unless $part_svc;
 
-}
-my $action = $svc_broadband->svcnum ? 'Edit' : 'Add';
-
-if ($pkgnum) {
-
-  #Nothing?
-
-} elsif ( $action eq 'Edit' ) {
-
-  #Nothing?
-
-} else {
-  die "\$action eq Add, but \$pkgnum is null!\n";
-}
-
-my $p1 = popurl(1);
-
-my ($ip_addr, $speed_up, $speed_down, $blocknum, $mac_addr,
-    $latitude, $longitude, $altitude, $vlan_profile, $auth_key,
-    $description) =
-    ($svc_broadband->ip_addr,
-     $svc_broadband->speed_up,
-     $svc_broadband->speed_down,
-     $svc_broadband->blocknum,
-     $svc_broadband->mac_addr,
-     $svc_broadband->latitude,
-     $svc_broadband->longitude,
-     $svc_broadband->altitude,
-     $svc_broadband->vlan_profile,
-     $svc_broadband->auth_key,
-     $svc_broadband->description,
-    );
+  my $columndef = $part_svc->part_svc_column($fieldref->{'field'});
+  if ($columndef->columnflag eq 'F') {
+    $fieldref->{'type'} = 'fixed';
+    $fieldref->{'value'} = $columndef->columnvalue;
+  }
+
+  if ($object->svcnum) { 
+
+    $fieldref->{type} = 'hidden'
+      if $fieldref->{field} eq 'blocknum';
+      
+    $fieldref->{value} = $object->addr_block->label
+      if $fieldref->{field} eq 'block_label';
+
+  } else { 
+
+    $fieldref->{type} = 'hidden' if $fieldref->{field} eq 'block_label';
+
+    if ($fieldref->{field} eq 'blocknum') {
+      my $cust_pkg = qsearchs( 'cust_pkg', {pkgnum => $cgi->param('pkgnum')} );
+      die "No cust_pkg entry!" unless $cust_pkg;
+
+      $object->svcpart($part_svc->svcpart);
+      my @addr_block =
+        grep {  ! $_->agentnum
+               || $cust_pkg->cust_main->agentnum == $_->agentnum
+               && $FS::CurrentUser::CurrentUser->agentnum($_->agentnum)
+             }
+        map { $_->addr_block } $object->allowed_routers;
+      my @options = map { $_->blocknum } @addr_block;
+      my %option_labels = map { ( $_->blocknum => $_->label ) } @addr_block;
+      $fieldref->{type}    = 'select';
+      $fieldref->{options} = \@options;
+      $fieldref->{labels}  = \%option_labels;
+    }
+
+  }
+}; 
 
 </%init>
index cdfa58e..b6b04d1 100644 (file)
@@ -28,9 +28,9 @@
 %  my $target_pkey = dbdef->table($opt{'target_table'})->primary_key;
 %
 %  my( $source_pkey, $sourcenum, $source_obj );
-%  if ( $opt{'source_obj'} ) {
+%  if ( $opt{'source_obj'} || $opt{'object'} ) {
 %
-%    $source_obj = $opt{'source_obj'};
+%    $source_obj = $opt{'source_obj'} || $opt{'object'};
 %    #$source_table = $source_obj->dbdef_table->table;
 %    $source_pkey = $source_obj->dbdef_table->primary_key;
 %    $sourcenum = $source_obj->$source_pkey();
 %
 %  my $extra_sql = '';
 %
+%  if ( $opt{'agent_virt'} ) {
+%    $extra_sql .= ' AND' . $FS::CurrentUser::CurrentUser->agentnums_sql(
+%                             'null_right' => $opt{'agent_null_right'}
+%                           );
+%  }
+%
 %  if ( $opt{'disable-able'} ) {
 %    $hashref->{'disabled'} = '';
 %
 %    $extra_sql .= ( $sourcenum && $source_pkey ) 
-%                    ? "OR $source_pkey = $sourcenum"
+%                    ? " OR $source_pkey = $sourcenum"
 %                    : '';
 %  }
 %
index 9ace19e..5a947ea 100644 (file)
@@ -334,7 +334,7 @@ $config_menu{'Resellers'} = [ \%config_agent, ''    ]
 $config_menu{'Billing'} = [ \%config_billing, ''    ]
   if $curuser->access_right('Edit billing events')
   || $curuser->access_right('Edit global billing events');
-if ( $curuser->access_right('Configuration') ) {
+if ( $curuser->access_right('Engineering configuration') ) {
   $config_menu{'Dialup'}  = [ \%config_dialup, ''    ];
   $config_menu{'Fixed (username-less) broadband'} = 
                             [ \%config_broadband, ''    ];
index 54069a5..d8ab500 100644 (file)
@@ -4,9 +4,8 @@
                  'value'       => $agentnum || '',
                  'empty_label' => 'all',
                  'hashref'     => { 'disabled' => '' },
-                 'extra_sql'   => ' AND '.
-                                  $FS::CurrentUser::CurrentUser->agentnums_sql.
-                                  ' ORDER BY agent',
+                 'order_by'    => ' ORDER BY agent',
+                 'disable_empty' => $disable_empty,
                  %opt,
              )
 %>
@@ -18,4 +17,13 @@ my $agentnum = $opt{'curr_value'} || $opt{'value'};
 $opt{'records'} = delete $opt{'agents'}
   if $opt{'agents'};
 
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $disable_empty = 1;
+if ( $opt{'agent_null_right'} &&
+     $curuser->access_right($opt{'agent_null_right'})
+   )
+{
+  $disable_empty--;
+}
+
 </%init>
index 7339f36..d24c9ab 100644 (file)
@@ -92,14 +92,25 @@ my $name_col = $opt{'name_col'};
 my $value = $opt{'curr_value'} || $opt{'value'};
 $value = [ split(/\s*,\s*/, $value) ] if $opt{'multiple'} && $value =~ /,/;
 
+my $extra_sql = $opt{'extra_sql'} || '';
+my $hashref =   $opt{'hashref'} || {};
+
+if ( $opt{'agent_virt'} ) {
+  $extra_sql .=
+    ( $extra_sql =~ /WHERE/i || scalar(keys %$hashref ) ? ' AND ' : ' WHERE ' ).
+    $FS::CurrentUser::CurrentUser->agentnums_sql(
+                                    'null_right' => $opt{'agent_null_right'}
+                                   );
+}
+
 my @records = ();
 if ( $opt{'records'} ) {
   @records = @{ $opt{'records'} };
 } else {
   @records = qsearch( {
     'table'     => $opt{'table'},
-    'hashref'   => ( $opt{'hashref'} || {} ),
-    'extra_sql' => ( $opt{'extra_sql'} || '' ),
+    'hashref'   => $hashref,
+    'extra_sql' => $extra_sql,
     'order_by'  => ( $opt{'order_by'} || "ORDER BY $name_col" ),
   });
 }
@@ -113,7 +124,7 @@ unless (    ! $value
   $opt{hashref}->{$key} = $value;
   my $record = qsearchs( {
     'table'     => $opt{table},
-    'hashref'   => $opt{hashref},
+    'hashref'   => $hashref,
     'extra_sql' => ( $opt{extra_sql} || '' ),
   });
   push @records, $record if $record;
diff --git a/httemplate/elements/tr-checkboxes-table.html b/httemplate/elements/tr-checkboxes-table.html
new file mode 100644 (file)
index 0000000..0099427
--- /dev/null
@@ -0,0 +1,20 @@
+% unless ( $opt{'js_only'} ) {
+
+    <% include('tr-td-label.html', @_ ) %>
+
+      <TD <% $style %>>
+% }
+
+        <% include( '/elements/checkboxes-table.html', %opt ) %>
+
+% unless ( $opt{'js_only'} ) {
+      </TD>
+    </TR>
+% }
+<%init>
+
+my( %opt ) = @_;
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+</%init>