[freeside-commits] branch master updated. 87f255507af9f14dfbccd37eefd71a148f9af344

Mark Wells mark at 420.am
Tue Oct 30 12:16:46 PDT 2012


The branch, master has been updated
       via  87f255507af9f14dfbccd37eefd71a148f9af344 (commit)
       via  d77fe06b27410a41855e1425114ab8d9cdae4ff0 (commit)
      from  dbae615569712c6f392dfd6cc1930f9d93340e31 (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 87f255507af9f14dfbccd37eefd71a148f9af344
Author: Mark Wells <mark at freeside.biz>
Date:   Tue Oct 30 12:16:17 2012 -0700

    IP address management for svc_acct, #19567

diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index c9f30fe..e74c19f 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -1136,6 +1136,13 @@ sub reason_type_options {
   },
 
   {
+    'key'         => 'svc_acct-ip_addr',
+    'section'     => '',
+    'description' => 'Enable IP address management on login services like for broadband services.',
+    'type'        => 'checkbox',
+  },
+
+  {
     'key'         => 'exclude_ip_addr',
     'section'     => '',
     'description' => 'Exclude these from the list of available broadband service IP addresses. (One per line)',
diff --git a/FS/FS/IP_Mixin.pm b/FS/FS/IP_Mixin.pm
new file mode 100644
index 0000000..fdeb51d
--- /dev/null
+++ b/FS/FS/IP_Mixin.pm
@@ -0,0 +1,305 @@
+package FS::IP_Mixin;
+
+use strict;
+use NetAddr::IP;
+use FS::addr_block;
+use FS::router;
+use FS::Record qw(qsearch);
+use FS::Conf;
+# careful about importing anything here--it will end up in a LOT of 
+# namespaces
+
+use vars qw(@subclasses $DEBUG $conf);
+
+$DEBUG = 0;
+
+# any subclass that can have IP addresses needs to be added here
+ at subclasses = (qw(FS::svc_broadband FS::svc_acct));
+
+sub conf {
+  $conf ||= FS::Conf->new;
+}
+
+=head1 NAME
+
+FS::IP_Mixin - Mixin class for objects that have IP addresses assigned.
+
+=head1 INTERFACE
+
+The inheritor may provide the following methods:
+
+=over 4
+
+=item ip_addr [ ADDRESS ]
+
+Get/set the IP address, as a string.  If the inheritor is also an
+L<FS::Record> subclass and has an 'ip_addr' field, that field will be 
+used.  Otherwise an C<ip_addr> method must be defined.
+
+=item addr_block [ BLOCK ]
+
+Get/set the address block, as an L<FS::addr_block> object.  By default,
+the 'blocknum' field will be used.
+
+=item router [ ROUTER ]
+
+Get/set the router, as an L<FS::router> object.  By default, the 
+'routernum' field will be used.  This is strictly optional; if present
+the IP address can be assigned from all those available on a router, 
+rather than in a specific block.
+
+=item _used_addresses [ BLOCK ]
+
+Return a list of all addresses in use (within BLOCK, if it's specified).
+The inheritor should cache this if possible.
+
+=item _is_used ADDRESS
+
+Test a specific address for availability.  Should return an empty string
+if it's free, or else a description of who or what is using it.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item ip_check
+
+The method that should be called from check() in the subclass.  This does 
+the following:
+
+- In an C<auto_router> situation, sets the router and block to match the 
+  object's IP address.
+- Otherwise, if the router and IP address are both set, validate the 
+  choice of router and set the block correctly.
+- Otherwise, if the router is set, assign an address (in the selected
+  block if there is one).
+- Check the IP address for availability.
+
+Returns an error if this fails for some reason (an address can't be 
+assigned from the requested router/block, or the requested address is
+unavailable, or doesn't seem to be an IP address).
+
+If router and IP address are both empty, this will do nothing.  The 
+object's check() method should decide whether to allow a null IP address.
+
+=cut
+
+sub ip_check {
+  my $self = shift;
+
+  if ( $self->ip_addr eq '0.0.0.0' ) { #ipv6?
+    $self->ip_addr('');
+  }
+
+  if ( $self->ip_addr
+       and !$self->router
+       and $self->conf->exists('auto_router') ) {
+    # assign a router that matches this IP address
+    return $self->check_ip_addr || $self->assign_router;
+  }
+  if ( my $router = $self->router ) {
+    if ( $router->manual_addr ) {
+      # Router is set, and it's set to manual addressing, so 
+      # clear blocknum and don't tamper with ip_addr.
+      $self->addr_block(undef);
+    } else {
+      my $block = $self->addr_block;
+      if ( !$block or !$block->manual_flag ) {
+        my $error = $self->assign_ip_addr;
+        return $error if $error;
+      }
+      # otherwise block is set to manual addressing
+    }
+  }
+  return $self->check_ip_addr;
+}
+
+=item assign_ip_addr
+
+Set the IP address to a free address in the selected block (C<addr_block>)
+or router (C<router>) for this object.  A block or router MUST be selected.
+If the object already has an IP address and it is in that block/router's 
+address space, it won't be changed.
+
+=cut
+
+sub assign_ip_addr {
+  my $self = shift;
+  my %opt = @_;
+
+  my @blocks;
+  my $na = $self->NetAddr;
+
+  if ( $self->addr_block ) {
+    # choose an address in a specific block.
+    @blocks = ( $self->addr_block );
+  } elsif ( $self->router ) {
+    # choose an address from any block on a specific router.
+    @blocks = $self->router->auto_addr_block;
+  } else {
+    # what else should we do, search ALL blocks? that's crazy.
+    die "no block or router specified for assign_ip_addr\n";
+  }
+
+  my $new_addr;
+  my $new_block;
+  foreach my $block (@blocks) {
+    if ( $self->ip_addr and $block->NetAddr->contains($na) ) {
+      return '';
+    }
+    # don't exit early on assigning a free address--check the rest of 
+    # the blocks to see if the current address is in one of them.
+    if (!$new_addr) {
+      $new_addr = $block->next_free_addr->addr;
+      $new_block = $block;
+    }
+  }
+ 
+  return 'No IP address available on this router' unless $new_addr;
+
+  $self->ip_addr($new_addr);
+  $self->addr_block($new_block);
+  '';
+}
+
+=item assign_router
+
+If the IP address is set, set the router and block accordingly.  If there
+is no block containing that address, returns an error.
+
+=cut
+
+sub assign_router {
+  my $self = shift;
+  return '' unless $self->ip_addr;
+  my $na = $self->NetAddr;
+  foreach my $router (qsearch('router', {})) {
+    foreach my $block ($router->addr_block) {
+      if ( $block->NetAddr->contains($na) ) {
+        $self->addr_block($block);
+        $self->router($router);
+        return '';
+      }
+    }
+  }
+  return $self->ip_addr . ' is not in an allowed block.';
+}
+
+=item check_ip_addr
+
+Validate the IP address.  Returns an empty string if it's correct and 
+available (or null), otherwise an error message.
+
+=cut
+
+sub check_ip_addr {
+  my $self = shift;
+  my $addr = $self->ip_addr;
+  return '' if $addr eq '';
+  my $na = $self->NetAddr
+    or return "Can't parse address '$addr'";
+  if ( my $block = $self->addr_block ) {
+    if ( !$block->NetAddr->contains($na) ) {
+      return "Address $addr not in block ".$block->cidr;
+    }
+  }
+  # this returns '' if the address is in use by $self.
+  if ( my $dup = $self->is_used($self->ip_addr) ) {
+    return "Address $addr in use by $dup";
+  }
+  '';
+}
+
+# sensible defaults
+sub addr_block {
+  my $self = shift;
+  if ( @_ ) {
+    my $new = shift;
+    if ( defined $new ) {
+      die "addr_block() must take an address block"
+        unless $new->isa('FS::addr_block');
+      $self->blocknum($new->blocknum);
+      return $new;
+    } else {
+      #$new is undef
+      $self->blocknum('');
+      return undef;
+    }
+  }
+  # could cache this...
+  FS::addr_block->by_key($self->blocknum);
+}
+
+sub router {
+  my $self = shift;
+  if ( @_ ) {
+    my $new = shift;
+    if ( defined $new ) {
+      die "router() must take a router"
+        unless $new->isa('FS::router');
+      $self->routernum($new->routernum);
+      return $new;
+    } else {
+      #$new is undef
+      $self->routernum('');
+      return undef;
+    }
+  }
+  FS::router->by_key($self->routernum);
+}
+
+=item used_addresses [ BLOCK ]
+
+Returns a list of all addresses (in BLOCK, or in all blocks)
+that are in use.  If called as an instance method, excludes 
+that instance from the search.
+
+=cut
+
+sub used_addresses {
+  my $self = shift;
+  my $block = shift;
+  return ( map { $_->_used_addresses($block, $self) } @subclasses );
+}
+
+sub _used_addresses {
+  my $class = shift;
+  die "$class->_used_addresses not implemented";
+}
+
+=item is_used ADDRESS
+
+Returns a string describing what object is using ADDRESS, or 
+an empty string if it's not in use.
+
+=cut
+
+sub is_used {
+  my $self = shift;
+  my $addr = shift;
+  for (@subclasses) {
+    my $used = $_->_is_used($addr, $self);
+    return $used if $used;
+  }
+  '';
+}
+
+sub _is_used {
+  my $class = shift;
+  die "$class->_is_used not implemented";
+}
+
+=back
+
+=head1 BUGS
+
+We can't reliably check for duplicate addresses across tables.  A 
+more robust implementation would be to put all assigned IP addresses
+in a single table with a unique index.  We do a best-effort check 
+anyway, but it has a race condition.
+
+=cut
+
+1; 
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 01250e5..912f3e2 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -2184,6 +2184,9 @@ sub tables_hashref {
         'shell',     'varchar',   'NULL',   $char_d, '', '', 
         'quota',     'varchar',   'NULL',   $char_d, '', '', 
         'slipip',    'varchar',   'NULL',   15, '', '', #four TINYINTs, bah.
+        # IP address mgmt
+        'routernum', 'int', 'NULL',      '', '', '',
+        'blocknum',  'int', 'NULL',      '', '', '', 
         'seconds',   'int', 'NULL',   '', '', '', #uhhhh
         'seconds_threshold',   'int', 'NULL',   '', '', '',
         'upbytes',   'bigint', 'NULL',   '', '', '', 
diff --git a/FS/FS/addr_block.pm b/FS/FS/addr_block.pm
index 686bdbd..6a62777 100755
--- a/FS/FS/addr_block.pm
+++ b/FS/FS/addr_block.pm
@@ -6,6 +6,7 @@ use FS::Record qw( qsearchs qsearch dbh );
 use FS::router;
 use FS::svc_broadband;
 use FS::Conf;
+use FS::IP_Mixin;
 use NetAddr::IP;
 use Carp qw( carp );
 use List::Util qw( first );
@@ -238,7 +239,7 @@ sub next_free_addr {
   my $self = shift;
   my $selfaddr = $self->NetAddr;
 
-  return if $self->manual_flag;
+  return () if $self->manual_flag;
 
   my $conf = new FS::Conf;
   my @excludeaddr = $conf->config('exclude_ip_addr');
@@ -249,9 +250,7 @@ sub next_free_addr {
     $selfaddr->addr,
     $selfaddr->network->addr,
     $selfaddr->broadcast->addr,
-    (map { $_->NetAddr->addr }
-       qsearch('svc_broadband', { blocknum => $self->blocknum })
-    ), @excludeaddr
+    FS::IP_Mixin->used_addresses($self)
   );
 
   # just do a linear search of the block
diff --git a/FS/FS/svc_IP_Mixin.pm b/FS/FS/svc_IP_Mixin.pm
new file mode 100644
index 0000000..7026205
--- /dev/null
+++ b/FS/FS/svc_IP_Mixin.pm
@@ -0,0 +1,123 @@
+package FS::svc_IP_Mixin;
+
+use strict;
+use base 'FS::IP_Mixin';
+use FS::Record qw(qsearchs qsearch);
+
+=item addr_block
+
+Returns the address block assigned to this service.
+
+=item router
+
+Returns the router assigned to this service, if there is one.
+
+=cut
+
+#addr_block and router methods provided by FS::IP_Mixin
+
+=item NetAddr
+
+Returns the address as a L<NetAddr::IP> object.  Use C<$svc->NetAddr->addr>
+to put it into canonical string form.
+
+=cut
+
+sub NetAddr {
+  my $self = shift;
+  NetAddr::IP->new($self->ip_addr);
+}
+
+=item ip_addr
+
+Wrapper for set/get on the IP address field.
+
+=cut
+
+sub ip_addr {
+  my $self = shift;
+  my $ip_field = $self->table_info->{'ip_field'}
+    or return '';
+  if ( @_ ) {
+    $self->set($ip_field, @_);
+  } else {
+    $self->get($ip_field);
+  }
+}
+
+=item allowed_routers
+
+Returns a list of L<FS::router> objects allowed on this service.
+
+=cut
+
+sub allowed_routers {
+  my $self = shift;
+  my $svcpart = $self->svcnum ? $self->cust_svc->svcpart : $self->svcpart;
+  my @r = map { $_->router } 
+    qsearch('part_svc_router', { svcpart => $svcpart });
+
+  if ( $self->cust_main ) {
+    my $agentnum = $self->cust_main->agentnum;
+    return grep { !$_->agentnum or $_->agentnum == $agentnum } @r;
+  } else {
+    return @r;
+  }
+}
+
+=item svc_ip_check
+
+Wrapper for C<ip_check> which also checks the validity of the router.
+
+=cut
+
+sub svc_ip_check {
+  my $self = shift;
+  my $error = $self->ip_check;
+  return $error if $error;
+  if ( my $router = $self->router ) {
+    if ( grep { $_->routernum eq $router->routernum } $self->allowed_routers ) {
+      return '';
+    } else {
+      return 'Router '.$router->routername.' not available for this service';
+    }
+  }
+  '';
+}
+
+sub _used_addresses {
+  my ($class, $block, $exclude) = @_;
+  my $ip_field = $class->table_info->{'ip_field'}
+    or return ();
+  # if the service doesn't have an ip_field, then it has no IP addresses 
+  # in use, yes? 
+
+  my %hash = ( $ip_field => { op => '!=', value => '' } );
+  $hash{'blocknum'} = $block->blocknum if $block;
+  $hash{'svcnum'} = { op => '!=', value => $exclude->svcnum } if ref $exclude;
+  map { $_->NetAddr->addr } qsearch($class->table, \%hash);
+}
+
+sub _is_used {
+  my ($class, $addr, $exclude) = @_;
+  my $ip_field = $class->table_info->{'ip_field'}
+    or return '';
+
+  my $svc = qsearchs($class->table, { $ip_field => $addr })
+    or return '';
+
+  return '' if ( ref $exclude and $exclude->svcnum == $svc->svcnum );
+
+  my $cust_svc = $svc->cust_svc;
+  if ( $cust_svc ) {
+    my @label = $cust_svc->label;
+    # "svc_foo 1234 (Service Desc)"
+    # this should be enough to identify it without leaking customer
+    # names across agents
+    "$label[2] $label[3] ($label[0])";
+  } else {
+    join(' ', $class->table, $svc->svcnum, '(unlinked service)');
+  }
+}
+
+1;
diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm
index 7ce79ae..8e71d82 100644
--- a/FS/FS/svc_acct.pm
+++ b/FS/FS/svc_acct.pm
@@ -6,6 +6,7 @@ use base qw( FS::svc_Domain_Mixin
              FS::svc_CGPRule_Mixin
              FS::svc_Radius_Mixin
              FS::svc_Tower_Mixin
+             FS::svc_IP_Mixin
              FS::svc_Common );
 use vars qw( $DEBUG $me $conf $skip_fuzzyfiles
              $dir_prefix @shells $usernamemin
@@ -1126,6 +1127,8 @@ sub check {
               || $self->ut_foreign_key( 'domsvc', 'svc_domain', 'svcnum' )
               || $self->ut_foreign_keyn('pbxsvc', 'svc_pbx',    'svcnum' )
               || $self->ut_foreign_keyn('sectornum','tower_sector','sectornum')
+              || $self->ut_foreign_keyn('routernum','router','routernum')
+              || $self->ut_foreign_keyn('blocknum','addr_block','blocknum')
               || $self->ut_textn('sec_phrase')
               || $self->ut_snumbern('seconds')
               || $self->ut_snumbern('upbytes')
@@ -1161,6 +1164,15 @@ sub check {
   ;
   return $error if $error;
 
+  # assign IP address, etc.
+  if ( $conf->exists('svc_acct-ip_addr') ) {
+    my $error = $self->svc_ip_check;
+    return $error if $error;
+  } else { # I think this is correct
+    $self->routernum('');
+    $self->blocknum('');
+  }
+
   my $cust_pkg;
   local $username_letter = $username_letter;
   local $username_uppercase = $username_uppercase;
@@ -1314,7 +1326,7 @@ sub check {
 
   unless ( $part_svc->part_svc_column('slipip')->columnflag eq 'F' ) {
     if ( $recref->{slipip} eq '' ) {
-      $recref->{slipip} = '';
+      $recref->{slipip} = ''; # eh?
     } elsif ( $recref->{slipip} eq '0e0' ) {
       $recref->{slipip} = '0e0';
     } else {
@@ -1322,7 +1334,6 @@ sub check {
         or return "Illegal slipip: ". $self->slipip;
       $recref->{slipip} = $1;
     }
-
   }
 
   #arbitrary RADIUS stuff; allow ut_textn for now
@@ -1384,6 +1395,7 @@ sub check {
   else {
     return "invalid password encoding ('".$recref->{_password_encoding}."'";
   }
+
   $self->SUPER::check;
 
 }
diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm
index 26659d5..af81353 100755
--- a/FS/FS/svc_broadband.pm
+++ b/FS/FS/svc_broadband.pm
@@ -1,5 +1,10 @@
 package FS::svc_broadband;
-use base qw(FS::svc_Radius_Mixin FS::svc_Tower_Mixin FS::svc_Common);
+use base qw(
+  FS::svc_Radius_Mixin
+  FS::svc_Tower_Mixin
+  FS::svc_IP_Mixin 
+  FS::svc_Common
+  );
 
 use strict;
 use vars qw($conf);
@@ -412,38 +417,13 @@ sub check {
   }
   my $agentnum = $cust_pkg->cust_main->agentnum if $cust_pkg;
 
-  if ( $conf->exists('auto_router') and $self->ip_addr and !$self->routernum ) {
-    # assign_router is guaranteed to provide a router that's legal
-    # for this agent and svcpart
-    my $error = $self->_check_ip_addr || $self->assign_router;
-    return $error if $error;
+  # assign IP address / router / block
+  $error = $self->svc_ip_check;
+  return $error if $error;
+  if ( !$self->ip_addr 
+       and !$conf->exists('svc_broadband-allow_null_ip_addr') ) {
+    return 'IP address is required';
   }
-  elsif ($self->routernum) {
-    return "Router ".$self->routernum." does not provide this service"
-      unless qsearchs('part_svc_router', { 
-        svcpart => $svcpart,
-        routernum => $self->routernum
-    });
-  
-    my $router = $self->router;
-    return "Router ".$self->routernum." does not serve this customer"
-      if $router->agentnum and $agentnum and $router->agentnum != $agentnum;
-
-    if ( $router->manual_addr ) {
-      $self->blocknum('');
-    }
-    else {
-      my $addr_block = $self->addr_block;
-      if ( $self->ip_addr eq '' 
-           and not ( $addr_block and $addr_block->manual_flag ) ) {
-        my $error = $self->assign_ip_addr;
-        return $error if $error;
-      }
-    }
- 
-    my $error = $self->_check_ip_addr;
-    return $error if $error;
-  } # if $self->routernum
 
   if ( $cust_pkg && ! $self->latitude && ! $self->longitude ) {
     my $l = $cust_pkg->cust_location_or_main;
@@ -459,104 +439,12 @@ sub check {
   $self->SUPER::check;
 }
 
-=item assign_ip_addr
-
-Assign an IP address matching the selected router, and the selected block
-if there is one.
-
-=cut
-
-sub assign_ip_addr {
-  my $self = shift;
-  my @blocks;
-  my $ip_addr;
-
-  if ( $self->addr_block and $self->addr_block->routernum == $self->routernum ) {
-    # simple case: user chose a block, find an address in that block
-    # (this overrides an existing IP address if it's not in the block)
-    @blocks = ($self->addr_block);
-  }
-  elsif ( $self->routernum ) {
-    @blocks = $self->router->auto_addr_block;
-  }
-  else { 
-    return '';
-  }
-#warn "assigning ip address in blocks\n".join("\n",map{$_->cidr} @blocks)."\n";
-
-  foreach my $block ( @blocks ) {
-    if ( $self->ip_addr and $block->NetAddr->contains($self->NetAddr) ) {
-      # don't change anything
-      return '';
-    }
-    $ip_addr = $block->next_free_addr;
-    if ( $ip_addr ) {
-      $self->set(ip_addr => $ip_addr->addr);
-      $self->set(blocknum => $block->blocknum);
-      return '';
-    }
-  }
-  return 'No IP address available on this router';
-}
-
-=item assign_router
-
-Assign an address block and router matching the selected IP address.
-Does nothing if IP address is null.
-
-=cut
-
-sub assign_router {
-  my $self = shift;
-  return '' if !$self->ip_addr;
-  #warn "assigning router/block for ".$self->ip_addr."\n";
-  foreach my $router ($self->allowed_routers) {
-    foreach my $block ($router->addr_block) {
-      if ( $block->NetAddr->contains($self->NetAddr) ) {
-        $self->blocknum($block->blocknum);
-        $self->routernum($block->routernum);
-        return '';
-      }
-    }
-  }
-  return $self->ip_addr.' is not in an allowed block.';
-}
-
-sub _check_ip_addr {
-  my $self = shift;
-
-  if (not($self->ip_addr) or $self->ip_addr eq '0.0.0.0') {
-    return '' if $conf->exists('svc_broadband-allow_null_ip_addr'); 
-    return 'IP address required';
-  }
-  else {
-    return 'Cannot parse address: '.$self->ip_addr unless $self->NetAddr;
-  }
-
-  if ( $self->addr_block 
-      and not $self->addr_block->NetAddr->contains($self->NetAddr) ) {
-    return 'Address '.$self->ip_addr.' not in block '.$self->addr_block->cidr;
-  }
-
-#  if (my $dup = qsearchs('svc_broadband', {
-#        ip_addr => $self->ip_addr,
-#        svcnum  => {op=>'!=', value => $self->svcnum}
-#      }) ) {
-#    return 'IP address conflicts with svcnum '.$dup->svcnum;
-#  }
-  '';
-}
-
 sub _check_duplicate {
   my $self = shift;
   # Not a reliable check because the table isn't locked, but 
   # that's why we have a unique index.  This is just to give a
   # friendlier error message.
   my @dup;
-  @dup = $self->find_duplicates('global', 'ip_addr');
-  if ( @dup ) {
-    return "IP address in use (svcnum ".$dup[0]->svcnum.")";
-  }
   @dup = $self->find_duplicates('global', 'mac_addr');
   if ( @dup ) {
     return "MAC address in use (svcnum ".$dup[0]->svcnum.")";
@@ -565,64 +453,6 @@ sub _check_duplicate {
   '';
 }
 
-
-=item NetAddr
-
-Returns a NetAddr::IP object containing the IP address of this service.  The netmask 
-is /32.
-
-=cut
-
-sub NetAddr {
-  my $self = shift;
-  new NetAddr::IP ($self->ip_addr);
-}
-
-=item addr_block
-
-Returns the FS::addr_block record (i.e. the address block) for this broadband service.
-
-=cut
-
-sub addr_block {
-  my $self = shift;
-  qsearchs('addr_block', { blocknum => $self->blocknum });
-}
-
-=item router
-
-Returns the FS::router record for this service.
-
-=cut
-
-sub router {
-  my $self = shift;
-  qsearchs('router', { routernum => $self->routernum });
-}
-
-=item allowed_routers
-
-Returns a list of allowed FS::router objects.
-
-=cut
-
-sub allowed_routers {
-  my $self = shift;
-  my $svcpart = $self->svcnum ? $self->cust_svc->svcpart : $self->svcpart;
-  my @r = map { $_->router } qsearch('part_svc_router', 
-    { svcpart => $svcpart });
-  if ( $self->cust_main ) {
-    my $agentnum = $self->cust_main->agentnum;
-    return grep { !$_->agentnum or $_->agentnum == $agentnum } @r;
-  }
-  else {
-    return @r;
-  }
-}
-
-=back
-
-
 =item mac_addr_formatted CASE DELIMITER
 
 Format the MAC address (for use by exports).  If CASE starts with "l"
diff --git a/httemplate/edit/process/svc_acct.cgi b/httemplate/edit/process/svc_acct.cgi
index 41aca65..d4bcd35 100755
--- a/httemplate/edit/process/svc_acct.cgi
+++ b/httemplate/edit/process/svc_acct.cgi
@@ -31,6 +31,11 @@ foreach (map { $_,$_."_threshold" } qw( upbytes downbytes totalbytes )) {
   $cgi->param($_, FS::UI::bytecount::parse_bytecount($cgi->param($_)) );
 }
 
+#for slipip, convert '(automatic)' to null
+my $ip_addr = $cgi->param('slipip');
+$ip_addr =~ s/[^\d\.]//g;
+$cgi->param('slipip', $ip_addr);
+
 #unmunge cgp_accessmodes (falze laziness-ish w/part_svc.pm::process &svc_domain)
 unless ( $cgi->param('cgp_accessmodes') ) {
   $cgi->param('cgp_accessmodes', 
diff --git a/httemplate/edit/router.cgi b/httemplate/edit/router.cgi
index fdcd7b3..0df9b45 100755
--- a/httemplate/edit/router.cgi
+++ b/httemplate/edit/router.cgi
@@ -29,8 +29,15 @@ die "access denied"
   unless $curuser->access_right('Broadband configuration')
     || $curuser->access_right('Broadband global configuration');
 
+my @svc_x = 'svc_broadband';
+if ( FS::Conf->new->exists('svc_acct-ip_addr') ) {
+  push @svc_x, 'svc_acct';
+}
+
 my $callback = sub {
   my ($cgi, $object, $fields) = (shift, shift, shift);
+
+  my $extra_sql = ' AND svcdb IN(' . join(',', map { "'$_'" } @svc_x) . ')';
   unless ($object->svcnum) {
     push @{$fields},
       { 'type'          => 'tablebreak-tr-title',
@@ -41,7 +48,8 @@ my $callback = sub {
         'target_table'  => 'part_svc',
         'link_table'    => 'part_svc_router',
         'name_col'      => 'svc',
-        'hashref'       => { 'svcdb' => 'svc_broadband', 'disabled' => '' },
+        'hashref'       => { 'disabled' => '' },
+        'extra_sql'     => $extra_sql,
       };
   }
 };
diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi
index 142c111..c1f7455 100755
--- a/httemplate/edit/svc_acct.cgi
+++ b/httemplate/edit/svc_acct.cgi
@@ -276,14 +276,26 @@ function randomPass() {
              'communigate' => $communigate,
 &>
 
-% if ( $part_svc->part_svc_column('slipip')->columnflag =~ /^[FA]$/ ) { 
-  <INPUT TYPE="hidden" NAME="slipip" VALUE="<% $svc_acct->slipip %>">
-% } else { 
-  <TR>
-    <TD ALIGN="right"><% mt('IP') |h %></TD>
-    <TD><INPUT TYPE="text" NAME="slipip" VALUE="<% $svc_acct->slipip %>"></TD>
-  </TR>
-% } 
+% if ( $conf->exists('svc_acct-ip_addr') ) {
+%   # router/block selection UI
+%   # (should we show this if slipip is fixed?)
+<& /elements/tr-select-router_block_ip.html, 
+  'object' => $svc_acct,
+  'ip_field' => 'slipip'
+&>
+% } else {
+%   # don't expose these to the user--they're only useful in the other case
+  <INPUT TYPE="hidden" NAME="routernum" VALUE="<% $svc_acct->routernum %>">
+  <INPUT TYPE="hidden" NAME="blocknum"  VALUE="<% $svc_acct->blocknum  %>">
+%   if ( $part_svc->part_svc_column('slipip')->columnflag =~ /^[FA]$/ ) { 
+    <INPUT TYPE="hidden" NAME="slipip" VALUE="<% $svc_acct->slipip %>">
+%   } else { 
+    <TR>
+      <TD ALIGN="right"><% mt('IP') |h %></TD>
+      <TD><INPUT TYPE="text" NAME="slipip" VALUE="<% $svc_acct->slipip %>"></TD>
+    </TR>
+%   }
+% }
 
 % my %label = ( seconds => 'Time',
 %               upbytes => 'Upload bytes',
diff --git a/httemplate/elements/tr-select-router_block_ip.html b/httemplate/elements/tr-select-router_block_ip.html
index 95d1787..11f7c48 100644
--- a/httemplate/elements/tr-select-router_block_ip.html
+++ b/httemplate/elements/tr-select-router_block_ip.html
@@ -58,12 +58,13 @@ function clearhint_ip_addr (what) {
 </td></tr>
 <& /elements/tr-td-label.html, label => 'IP address' &>
 <td>
-% if ( $fixed{'ip_addr'} ) {
-  <input type="hidden" id="input_ip_addr" name="ip_addr" 
+% warn Dumper \%fixed;
+% if ( exists $fixed{$ip_field} ) {
+  <input type="hidden" id="input_ip_addr" name="<% $ip_field %>" 
     value="<% $opt{'ip_addr'} |h%>"><% $opt{'ip_addr'} || '' %>
 % }
 % else {
-  <input type="text" id="input_ip_addr" name="ip_addr" 
+  <input type="text" id="input_ip_addr" name="<% $ip_field %>" 
   value="<% $opt{'ip_addr'} |h%>" onfocus="clearhint_ip_addr(this)">
 % }
 </td> </tr>
@@ -78,6 +79,7 @@ my $conf = FS::Conf->new;
 
 my $svc_x = $opt{'object'};
 if ( $svc_x ) {
+  # $svc_x->ip_addr does work, even for non-svc_broadband.
   $opt{$_} = $svc_x->$_
     foreach qw(routernum blocknum ip_addr svcpart);
   if ( $svc_x->svcnum ) {
@@ -86,6 +88,8 @@ if ( $svc_x ) {
 }
 
 my $svcpart = $opt{'svcpart'} || '';
+my $ip_field = $opt{'ip_field'} || 'ip_addr';
+
 my %fixed; #  which fields are fixed
 $svcpart =~ /^\d*$/ or die "invalid svcpart '$svcpart'";
 if ( $svcpart ) {
@@ -93,13 +97,13 @@ if ( $svcpart ) {
   # Traditionally, columnflag 'F' on IP address means that it MUST 
   # be auto-assigned (or, if null IP addresses are allowed, that 
   # it must be null).
-  foreach (qw(routernum blocknum ip_addr)) {
+  foreach (qw(routernum blocknum), $ip_field) {
     my $psc = $part_svc->part_svc_column($_);
     if ( $psc and $psc->columnflag eq 'F' ) {
       $fixed{$_} = $psc->columnvalue;
     }
   }
-  if ( $fixed{'routernum'} ) {
+  if ( exists $fixed{'routernum'} ) {
     @routers = (FS::router->by_key($fixed{'routernum'}))
   }
   else {
diff --git a/httemplate/view/svc_acct/basics.html b/httemplate/view/svc_acct/basics.html
index 1cdf776..2d9953f 100644
--- a/httemplate/view/svc_acct/basics.html
+++ b/httemplate/view/svc_acct/basics.html
@@ -91,15 +91,29 @@
 
 % }
 
+<%perl>
+# minor false laziness w/ view/svc_broadband.cgi
+sub slipip {
+  my $svc_acct = shift;
+  my $out = $svc_acct->slipip or return '';
+  if ( $out eq '0.0.0.0' or $out eq '0e0' ) {
+    return '<I>('.mt('Dynamic').'</I>';
+  }
+  $out .= ' ('.
+          include('/elements/popup_link-ping.html', ip => $svc_acct->slipip).
+          ')';
+  if ( my $addr_block = $svc_acct->addr_block ) {
+    $out .= '<br>Netmask: ' . $addr_block->NetAddr->mask .
+            '<br>Gateway: ' . $addr_block->ip_gateway;
+  }
+  $out;
+}
+</%perl>
+
 % if ($svc_acct->slipip) { 
   <& /view/elements/tr.html,
        label=>mt('IP address'),
-       value=> ( $svc_acct->slipip eq "0.0.0.0" || $svc_acct->slipip eq '0e0' )
-                 ? "<I>(".mt('Dynamic').")</I>"
-                 : $svc_acct->slipip. ' '.
-                   include('/elements/popup_link-ping.html',
-                             'ip'=>$svc_acct->slipip,
-                          )
+       value=> slipip($svc_acct)
   &>
 % } 
 

commit d77fe06b27410a41855e1425114ab8d9cdae4ff0
Author: Mark Wells <mark at freeside.biz>
Date:   Tue Oct 30 12:16:13 2012 -0700

    fix ticket display, fallout from #17067

diff --git a/FS/FS/Misc/DateTime.pm b/FS/FS/Misc/DateTime.pm
index e36f3a9..9c12e64 100644
--- a/FS/FS/Misc/DateTime.pm
+++ b/FS/FS/Misc/DateTime.pm
@@ -32,13 +32,14 @@ the date as an integer UNIX timestamp.
 sub parse_datetime {
   my $string = shift;
   return '' unless $string =~ /\S/;
+  my $tz = shift || 'local';
 
   my $conf = new FS::Conf;
   my $format = $conf->config('date_format') || '%m/%d/%Y';
 
   if ( $format eq '%d/%m/%Y' ) { #  =~ /\%d.*\%m/ ) {
     #$format =~ s/\%//g;
-    my $parser = DateTime::Format::Natural->new( 'time_zone' => 'local',
+    my $parser = DateTime::Format::Natural->new( 'time_zone' => $tz,
                                                  #'format'=>'d/m/y',#lc($format)
                                                );
     $dt = $parser->parse_datetime($string);
@@ -51,7 +52,7 @@ sub parse_datetime {
       $dt->epoch;
     }
   } else {
-    return str2time($string);
+    return str2time($string, $tz);
   }
   
 }
diff --git a/httemplate/elements/table-tickets.html b/httemplate/elements/table-tickets.html
index ffcaf06..f89f984 100644
--- a/httemplate/elements/table-tickets.html
+++ b/httemplate/elements/table-tickets.html
@@ -153,7 +153,7 @@ if ( $ss_priority ) {
 my $format = $conf->config('date_format') || '%Y-%m-%d';
 
 my $date_formatter = sub {
-  my $time = str2time($_[0], 'GMT');
+  my $time = parse_datetime($_[0], 'GMT');
   # exclude times within 24 hours of zero
   ($time > 86400) ? time2str($format, $time) : '';
 };

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

Summary of changes:
 FS/FS/Conf.pm                                      |    7 +
 FS/FS/IP_Mixin.pm                                  |  305 ++++++++++++++++++++
 FS/FS/Misc/DateTime.pm                             |    5 +-
 FS/FS/Schema.pm                                    |    3 +
 FS/FS/addr_block.pm                                |    7 +-
 FS/FS/svc_IP_Mixin.pm                              |  123 ++++++++
 FS/FS/svc_acct.pm                                  |   16 +-
 FS/FS/svc_broadband.pm                             |  194 +------------
 httemplate/edit/process/svc_acct.cgi               |    5 +
 httemplate/edit/router.cgi                         |   10 +-
 httemplate/edit/svc_acct.cgi                       |   28 ++-
 httemplate/elements/table-tickets.html             |    2 +-
 httemplate/elements/tr-select-router_block_ip.html |   14 +-
 httemplate/view/svc_acct/basics.html               |   26 ++-
 14 files changed, 534 insertions(+), 211 deletions(-)
 create mode 100644 FS/FS/IP_Mixin.pm
 create mode 100644 FS/FS/svc_IP_Mixin.pm




More information about the freeside-commits mailing list