X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fsvc_broadband.pm;h=078df559ab087bb420af8bb8c9eb9de0cebdfc8f;hp=18b7036ea4cd0c0ec49f9f0e2c67fceea8286120;hb=4facec5414f411b97c9b19772c13f125a0b748cb;hpb=712864e9372891cc3773d01e546103c23c10ff03 diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index 18b7036ea..078df559a 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -1,5 +1,12 @@ 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_Torrus_Mixin + FS::svc_IP_Mixin + FS::MAC_Mixin + FS::svc_Common +); use strict; use vars qw($conf); @@ -96,18 +103,19 @@ sub table_info { 'display_weight' => 50, 'cancel_weight' => 70, 'ip_field' => 'ip_addr', + 'manual_require' => 1, 'fields' => { 'svcnum' => 'Service', - 'description' => 'Descriptive label for this particular device', - 'speed_down' => 'Maximum download speed for this service in Kbps. 0 denotes unlimited.', - 'speed_up' => 'Maximum upload speed for this service in Kbps. 0 denotes unlimited.', - 'ip_addr' => 'IP address. Leave blank for automatic assignment.', - 'blocknum' => - { 'label' => 'Address block', - 'type' => 'select', - 'select_table' => 'addr_block', - 'select_key' => 'blocknum', - 'select_label' => 'cidr', + 'description' => 'Descriptive label', + 'speed_down' => 'Download speed (Kbps)', + 'speed_up' => 'Upload speed (Kbps)', + 'ip_addr' => 'IP address', + 'blocknum' => { + 'label' => 'Address block', + 'type' => 'select', + 'select_table' => 'addr_block', + 'select_key' => 'blocknum', + 'select_label' => 'cidr', 'disable_inventory' => 1, }, 'plan_id' => 'Service Plan Id', @@ -129,13 +137,23 @@ sub table_info { disable_inventory => 1, multiple => 1, }, + 'radio_serialnum' => 'Radio Serial Number', + 'radio_location' => 'Radio Location', + 'poe_location' => 'POE Location', + 'rssi' => 'RSSI', + 'suid' => 'SUID', + 'shared_svcnum' => { label => 'Shared Service', + type => 'search-svc_broadband', + disable_inventory => 1, + }, + 'serviceid' => 'Torrus serviceid', #but is should be hidden }, }; } sub table { 'svc_broadband'; } -sub table_dupcheck_fields { ( 'mac_addr' ); } +sub table_dupcheck_fields { ( 'ip_addr', 'mac_addr' ); } =item search HASHREF @@ -170,109 +188,44 @@ Parameters: =cut -sub search { - my ($class, $params) = @_; - my @where = (); - my @from = ( - 'LEFT JOIN cust_svc USING ( svcnum )', - 'LEFT JOIN part_svc USING ( svcpart )', - 'LEFT JOIN cust_pkg USING ( pkgnum )', - 'LEFT JOIN cust_main USING ( custnum )', - ); - - # based on FS::svc_acct::search, probably the most mature of the bunch - #unlinked - push @where, 'pkgnum IS NULL' if $params->{'unlinked'}; - - #agentnum - if ( $params->{'agentnum'} =~ /^(\d+)$/ and $1 ) { - push @where, "cust_main.agentnum = $1"; - } - push @where, $FS::CurrentUser::CurrentUser->agentnums_sql( - 'null_right' => 'View/link unlinked services', - 'table' => 'cust_main' - ); - - #custnum - if ( $params->{'custnum'} =~ /^(\d+)$/ and $1 ) { - push @where, "custnum = $1"; - } - - #pkgpart, now properly untainted, can be arrayref - for my $pkgpart ( $params->{'pkgpart'} ) { - if ( ref $pkgpart ) { - my $where = join(',', map { /^(\d+)$/ ? $1 : () } @$pkgpart ); - push @where, "cust_pkg.pkgpart IN ($where)" if $where; - } - elsif ( $pkgpart =~ /^(\d+)$/ ) { - push @where, "cust_pkg.pkgpart = $1"; - } - } +sub _search_svc { + my( $class, $params, $from, $where ) = @_; #routernum, can be arrayref for my $routernum ( $params->{'routernum'} ) { # this no longer uses addr_block if ( ref $routernum and grep { $_ } @$routernum ) { my $in = join(',', map { /^(\d+)$/ ? $1 : () } @$routernum ); - my @orwhere; + my @orwhere = (); push @orwhere, "svc_broadband.routernum IN ($in)" if $in; push @orwhere, "svc_broadband.routernum IS NULL" if grep /^none$/, @$routernum; - push @where, '( '.join(' OR ', @orwhere).' )'; + push @$where, '( '.join(' OR ', @orwhere).' )'; } elsif ( $routernum =~ /^(\d+)$/ ) { - push @where, "svc_broadband.routernum = $1"; + push @$where, "svc_broadband.routernum = $1"; } elsif ( $routernum eq 'none' ) { - push @where, "svc_broadband.routernum IS NULL"; + push @$where, "svc_broadband.routernum IS NULL"; } } + #this should probably move to svc_Tower_Mixin, or maybe we never should have + # done svc_acct # towers (or, as mark thought, never should have done + # svc_broadband) + #sector and tower, as above my @where_sector = $class->tower_sector_sql($params); if ( @where_sector ) { - push @where, @where_sector; - push @from, 'LEFT JOIN tower_sector USING ( sectornum )'; + push @$where, @where_sector; + push @$from, 'LEFT JOIN tower_sector USING ( sectornum )'; } - #svcnum - if ( $params->{'svcnum'} =~ /^(\d+)$/ ) { - push @where, "svcnum = $1"; - } - - #svcpart - if ( $params->{'svcpart'} =~ /^(\d+)$/ ) { - push @where, "svcpart = $1"; - } - #ip_addr if ( $params->{'ip_addr'} =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/ ) { - push @where, "ip_addr = '$1'"; + push @$where, "ip_addr = '$1'"; } - #custnum - if ( $params->{'custnum'} =~ /^(\d+)$/ and $1) { - push @where, "custnum = $1"; - } - - my $addl_from = join(' ', @from); - my $extra_sql = ''; - $extra_sql = 'WHERE '.join(' AND ', @where) if @where; - my $count_query = "SELECT COUNT(*) FROM svc_broadband $addl_from $extra_sql"; - return( { - 'table' => 'svc_broadband', - 'hashref' => {}, - 'select' => join(', ', - 'svc_broadband.*', - 'part_svc.svc', - 'cust_main.custnum', - FS::UI::Web::cust_sql_fields($params->{'cust_fields'}), - ), - 'extra_sql' => $extra_sql, - 'addl_from' => $addl_from, - 'order_by' => "ORDER BY ".($params->{'order_by'} || 'svcnum'), - 'count_query' => $count_query, - } ); } =item search_sql STRING @@ -285,24 +238,46 @@ sub search_sql { 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 smart_search STRING + +=cut + +sub smart_search { + my( $class, $string ) = @_; + qsearch({ + 'table' => $class->table, #'svc_broadband', + 'hashref' => {}, + 'extra_sql' => 'WHERE '. $class->search_sql($string), + }); +} + =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 ... ] @@ -366,7 +341,7 @@ sub check { # 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 = @@ -385,6 +360,13 @@ sub check { || $self->ut_sfloatn('altitude') || $self->ut_textn('vlan_profile') || $self->ut_textn('plan_id') + || $self->ut_alphan('radio_serialnum') + || $self->ut_textn('radio_location') + || $self->ut_textn('poe_location') + || $self->ut_snumbern('rssi') + || $self->ut_numbern('suid') + || $self->ut_foreign_keyn('shared_svcnum', 'svc_broadband', 'svcnum') + || $self->ut_textn('serviceid') #too lenient? ; return $error if $error; @@ -406,28 +388,13 @@ sub check { } my $agentnum = $cust_pkg->cust_main->agentnum if $cust_pkg; - if ($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 $router->agentnum != $agentnum; - - if ( $router->auto_addr ) { - my $addr_block = $self->addr_block; - unless ( $addr_block and $addr_block->manual_flag ) { - my $error = $self->assign_ip_addr; - return $error if $error; - } - } - else { - $self->blocknum(''); - } - } # if $self->routernum + # 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'; + } if ( $cust_pkg && ! $self->latitude && ! $self->longitude ) { my $l = $cust_pkg->cust_location_or_main; @@ -440,157 +407,45 @@ 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 -if there is one. - -=cut - -sub assign_ip_addr { - my $self = shift; - my @blocks; - my $ip_addr; - - if ( $self->blocknum 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 ''; - } - - 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; - last if $ip_addr; - } - if ( $ip_addr ) { - $self->set(ip_addr => $ip_addr->addr); - return ''; - } - else { - return 'No IP address available on this router'; - } -} - -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'; - } -# 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; - - 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', 'mac_addr'); + if ( @dup ) { + return "MAC address in use (svcnum ".$dup[0]->svcnum.")"; + } ''; } - -=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; - map { $_->router } qsearch('part_svc_router', - { svcpart => $self->cust_svc->svcpart }); -} - -=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; + local($FS::svc_Common::noexport_hack) = 1; + + # fix wrong-case MAC addresses + my $dbh = dbh; + $dbh->do('UPDATE svc_broadband SET mac_addr = UPPER(mac_addr);') + or die $dbh->errstr; + # set routernum to addr_block.routernum foreach my $self (qsearch('svc_broadband', { blocknum => {op => '!=', value => ''}, 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 ) { @@ -600,13 +455,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 { @@ -614,6 +469,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; + } + ''; }