package FS::svc_broadband;
+use base qw(
+ FS::svc_Radius_Mixin
+ FS::svc_Tower_Mixin
+ FS::svc_MAC_Mixin
+ FS::svc_Common
+ );
use strict;
use vars qw(@ISA $conf);
-use base qw(FS::svc_Radius_Mixin FS::svc_Tower_Mixin FS::svc_Common);
{ no warnings 'redefine'; use NetAddr::IP; }
use FS::Record qw( qsearchs qsearch dbh );
use FS::svc_Common;
sub table_info {
{
- 'name' => 'Broadband',
- 'name_plural' => 'Broadband services',
- 'longname_plural' => 'Fixed (username-less) broadband services',
+ 'name' => 'Wireless broadband',
+ 'name_plural' => 'Wireless broadband services',
+ 'longname_plural' => 'Fixed wireless broadband services',
'display_weight' => 50,
'cancel_weight' => 70,
'ip_field' => 'ip_addr',
sub table { 'svc_broadband'; }
-sub table_dupcheck_fields { ( 'mac_addr' ); }
+sub table_dupcheck_fields { ( 'ip_addr', 'mac_addr' ); }
=item search HASHREF
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'";
),
'extra_sql' => $extra_sql,
'addl_from' => $addl_from,
- 'order_by' => "ORDER BY ".($params->{'order_by'} || 'svcnum'),
+ 'order_by' => ($params->{'order_by'} || 'ORDER BY svcnum'),
'count_query' => $count_query,
} );
}
my( $class, $string ) = @_;
if ( $string =~ /^(\d{1,3}\.){3}\d{1,3}$/ ) {
$class->search_sql_field('ip_addr', $string );
- }elsif ( $string =~ /^([a-fA-F0-9]{12})$/ ) {
+ } elsif ( $string =~ /^([A-F0-9]{12})$/i ) {
$class->search_sql_field('mac_addr', uc($string));
- }elsif ( $string =~ /^(([a-fA-F0-9]{1,2}:){5}([a-fA-F0-9]{1,2}))$/ ) {
- $class->search_sql_field('mac_addr', uc("$2$3$4$5$6$7") );
+ } elsif ( $string =~ /^(([A-F0-9]{2}:){5}([A-F0-9]{2}))$/i ) {
+ $string =~ s/://g;
+ $class->search_sql_field('mac_addr', uc($string) );
+ } elsif ( $string =~ /^(\d+)$/ ) {
+ my $table = $class->table;
+ "$table.svcnum = $1";
} else {
'1 = 0'; #false
}
=item label
-Returns the IP address.
+Returns the IP address, MAC address and description.
=cut
sub label {
my $self = shift;
- $self->ip_addr;
+ my $label = 'IP:'. ($self->ip_addr || 'Unknown');
+ $label .= ', MAC:'. $self->mac_addr
+ if $self->mac_addr;
+ $label .= ' ('. $self->description. ')'
+ if $self->description;
+ return $label;
}
=item insert [ , OPTION => VALUE ... ]
# remove delimiters
my $mac_addr = uc($self->get('mac_addr'));
- $mac_addr =~ s/[-: ]//g;
+ $mac_addr =~ s/[\W_]//g;
$self->set('mac_addr', $mac_addr);
my $error =
}
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,
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 ) {
}
}
- $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
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);
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) ) {
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 {
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}
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.")";
+ }
'';
}
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
-=item mac_addr_formatted CASE DELIMITER
-
-Format the MAC address (for use by exports). If CASE starts with "l"
-(for "lowercase"), it's returned in lowercase. DELIMITER is inserted
-between octets.
-
-=cut
-
-sub mac_addr_formatted {
- my $self = shift;
- my ($case, $delim) = @_;
- my $addr = $self->mac_addr;
- $addr = lc($addr) if $case =~ /^l/i;
- join( $delim || '', $addr =~ /../g );
-}
-
#class method
sub _upgrade_data {
my $class = shift;
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 ) {
# (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 {
": 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;
+ }
+
'';
}