sqlradius data volume report improvements, #18823
[freeside.git] / FS / FS / svc_broadband.pm
index 6f5e170..26659d5 100755 (executable)
@@ -135,7 +135,7 @@ sub table_info {
 
 sub table { 'svc_broadband'; }
 
-sub table_dupcheck_fields { ( 'mac_addr' ); }
+sub table_dupcheck_fields { ( 'ip_addr', 'mac_addr' ); }
 
 =item search HASHREF
 
@@ -245,6 +245,12 @@ sub search {
     push @where, "svcpart = $1";
   }
 
+  #exportnum
+  if ( $params->{'exportnum'} =~ /^(\d+)$/ ) {
+    push @from, 'LEFT JOIN export_svc USING ( svcpart )';
+    push @where, "exportnum = $1";
+  }
+
   #ip_addr
   if ( $params->{'ip_addr'} =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/ ) {
     push @where, "ip_addr = '$1'";
@@ -406,7 +412,13 @@ sub check {
   }
   my $agentnum = $cust_pkg->cust_main->agentnum if $cust_pkg;
 
-  if ($self->routernum) {
+  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;
+  }
+  elsif ($self->routernum) {
     return "Router ".$self->routernum." does not provide this service"
       unless qsearchs('part_svc_router', { 
         svcpart => $svcpart,
@@ -415,18 +427,22 @@ sub check {
   
     my $router = $self->router;
     return "Router ".$self->routernum." does not serve this customer"
-      if $router->agentnum and $router->agentnum != $agentnum;
+      if $router->agentnum and $agentnum and $router->agentnum != $agentnum;
 
-    if ( $router->auto_addr ) {
+    if ( $router->manual_addr ) {
+      $self->blocknum('');
+    }
+    else {
       my $addr_block = $self->addr_block;
-      unless ( $addr_block and $addr_block->manual_flag ) {
+      if ( $self->ip_addr eq '' 
+           and not ( $addr_block and $addr_block->manual_flag ) ) {
         my $error = $self->assign_ip_addr;
         return $error if $error;
       }
     }
-    else {
-      $self->blocknum('');
-    }
+    my $error = $self->_check_ip_addr;
+    return $error if $error;
   } # if $self->routernum
 
   if ( $cust_pkg && ! $self->latitude && ! $self->longitude ) {
@@ -440,15 +456,12 @@ sub check {
     }
   }
 
-  $error = $self->_check_ip_addr;
-  return $error if $error;
-
   $self->SUPER::check;
 }
 
 =item assign_ip_addr
 
-Assign an address block matching the selected router, and the selected block
+Assign an IP address matching the selected router, and the selected block
 if there is one.
 
 =cut
@@ -458,7 +471,7 @@ sub assign_ip_addr {
   my @blocks;
   my $ip_addr;
 
-  if ( $self->blocknum and $self->addr_block->routernum == $self->routernum ) {
+  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);
@@ -469,6 +482,7 @@ sub assign_ip_addr {
   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) ) {
@@ -476,15 +490,36 @@ sub assign_ip_addr {
       return '';
     }
     $ip_addr = $block->next_free_addr;
-    last if $ip_addr;
-  }
-  if ( $ip_addr ) {
-    $self->set(ip_addr => $ip_addr->addr);
-    return '';
+    if ( $ip_addr ) {
+      $self->set(ip_addr => $ip_addr->addr);
+      $self->set(blocknum => $block->blocknum);
+      return '';
+    }
   }
-  else {
-    return 'No IP address available on this router';
+  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 {
@@ -494,6 +529,15 @@ sub _check_ip_addr {
     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}
@@ -505,11 +549,18 @@ sub _check_ip_addr {
 
 sub _check_duplicate {
   my $self = shift;
-
-  return "MAC already in use"
-    if ( $self->mac_addr &&
-         scalar( qsearch( 'svc_broadband', { 'mac_addr', $self->mac_addr } ) )
-       );
+  # 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.")";
+  }
 
   '';
 }
@@ -558,8 +609,15 @@ Returns a list of allowed FS::router objects.
 sub allowed_routers {
   my $self = shift;
   my $svcpart = $self->svcnum ? $self->cust_svc->svcpart : $self->svcpart;
-  map { $_->router } qsearch('part_svc_router', 
-    { svcpart => $self->cust_svc->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
@@ -593,6 +651,11 @@ sub _upgrade_data {
       routernum => ''
     })) {
     my $addr_block = $self->addr_block;
+    if ( !$addr_block ) {
+      # super paranoid mode
+      warn "WARNING: svcnum ".$self->svcnum." is assigned to addr_block ".$self->blocknum.", which does not exist; skipped.\n";
+      next;
+    }
     my $ip_addr = $self->ip_addr;
     my $routernum = $addr_block->routernum;
     if ( $routernum ) {
@@ -602,13 +665,13 @@ sub _upgrade_data {
       # (other than setting blocknum to null for a non-auto-assigned router)
       if ( $self->ip_addr ne $ip_addr 
         or ($self->blocknum and $self->blocknum != $addr_block->blocknum)) {
-        die "Upgrading service ".$self->svcnum." would change its block/address.\n\nCheck your router and address block configuration.\n";
+        warn "WARNING: Upgrading service ".$self->svcnum." would change its block/address; skipped.\n";
         next;
       }
 
       $error ||= $self->replace;
-      die "error assigning routernum $routernum to service ".$self->svcnum.
-          ":\n$error\n"
+      warn "WARNING: error assigning routernum $routernum to service ".$self->svcnum.
+          ":\n$error; skipped\n"
         if $error;
     }
     else {
@@ -616,6 +679,38 @@ sub _upgrade_data {
         ": no routernum in address block ".$addr_block->cidr.", skipped\n";
     }
   }
+
+  # assign blocknums to services that should have them
+  my @all_blocks = qsearch('addr_block', { });
+  SVC: foreach my $self ( 
+    qsearch({
+        'select' => 'svc_broadband.*',
+        'table' => 'svc_broadband',
+        'addl_from' => 'JOIN router USING (routernum)',
+        'hashref' => {},
+        'extra_sql' => 'WHERE svc_broadband.blocknum IS NULL '.
+                       'AND router.manual_addr IS NULL',
+    }) 
+  ) {
+   
+    next SVC if $self->ip_addr eq '';
+    my $NetAddr = $self->NetAddr;
+    # inefficient, but should only need to run once
+    foreach my $block (@all_blocks) {
+      if ($block->NetAddr->contains($NetAddr)) {
+        $self->set(blocknum => $block->blocknum);
+        my $error = $self->replace;
+        warn "WARNING: error assigning blocknum ".$block->blocknum.
+        " to service ".$self->svcnum."\n$error; skipped\n"
+          if $error;
+        next SVC;
+      }
+    }
+    warn "WARNING: no block found containing ".$NetAddr->addr." for service ".
+      $self->svcnum;
+    #next SVC;
+  }
+
   '';
 }