X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fsvc_broadband.pm;h=35de8b3d94c50e05efa88cf76fc2aabf896fdff4;hp=68e7496d194c37bff81dba4d82fdef8bf760a474;hb=573139dbd6c37808697bfa72a3a468bb0980d4dd;hpb=5e05724a635a22776f1b973f5d7e77989da4e048 diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index 68e7496d1..35de8b3d9 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -2,13 +2,15 @@ package FS::svc_broadband; use strict; use vars qw(@ISA $conf); +use NetAddr::IP; use FS::Record qw( qsearchs qsearch dbh ); use FS::svc_Common; use FS::cust_svc; use FS::addr_block; -use NetAddr::IP; +use FS::part_svc_router; +use FS::tower_sector; -@ISA = qw( FS::svc_Common ); +@ISA = qw( FS::svc_Radius_Mixin FS::svc_Common ); $FS::UID::callback{'FS::svc_broadband'} = sub { $conf = new FS::Conf; @@ -69,6 +71,8 @@ customer's router will have the same address for both its internal and external interfaces thus saving address space. This has been found to work on most NAT routers available. +=item plan_id + =back =head1 METHODS @@ -92,18 +96,165 @@ sub table_info { 'longname_plural' => 'Fixed (username-less) broadband services', 'display_weight' => 50, 'cancel_weight' => 70, + 'ip_field' => 'ip_addr', 'fields' => { - 'description' => 'Descriptive label for this particular device.', + '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' => 'Address block.', + 'blocknum' => { 'label' => 'Address block', + 'type' => 'select', + 'select_table' => 'addr_block', + 'select_key' => 'blocknum', + 'select_label' => 'cidr', + 'disable_inventory' => 1, + }, + 'plan_id' => 'Service Plan Id', + 'performance_profile' => 'Peformance Profile', + 'authkey' => 'Authentication key', + 'mac_addr' => 'MAC address', + 'latitude' => 'Latitude', + 'longitude' => 'Longitude', + 'altitude' => 'Altitude', + 'vlan_profile' => 'VLAN profile', + 'usergroup' => { + label => 'RADIUS groups', + type => 'select-radius_group.html', + #select_table => 'radius_group', + #select_key => 'groupnum', + #select_label => 'groupname', + disable_inventory => 1, + multiple => 1, + }, }, }; } sub table { 'svc_broadband'; } +sub table_dupcheck_fields { ( 'mac_addr' ); } + +=item search HASHREF + +Class method which returns a qsearch hash expression to search for parameters +specified in HASHREF. + +Parameters: + +=over 4 + +=item unlinked - set to search for all unlinked services. Overrides all other options. + +=item agentnum + +=item custnum + +=item svcpart + +=item ip_addr + +=item pkgpart - arrayref + +=item routernum - arrayref + +=item order_by + +=back + +=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"; + } + } + + #routernum, can be arrayref + for my $routernum ( $params->{'routernum'} ) { + push @from, 'LEFT JOIN addr_block USING ( blocknum )'; + if ( ref $routernum and grep { $_ } @$routernum ) { + my $where = join(',', map { /^(\d+)$/ ? $1 : () } @$routernum ); + push @where, "addr_block.routernum IN ($where)" if $where; + } + elsif ( $routernum =~ /^(\d+)$/ ) { + push @where, "addr_block.routernum = $1"; + } + } + + #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'"; + } + + #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 Class method which returns an SQL fragment to search for the given string. @@ -195,32 +346,94 @@ sub check { return $x unless ref($x); + my $nw_coords = $conf->exists('svc_broadband-require-nw-coordinates'); + my $lat_lower = $nw_coords ? 1 : -90; + my $lon_upper = $nw_coords ? -1 : 180; + + # remove delimiters + my $mac_addr = uc($self->get('mac_addr')); + $mac_addr =~ s/[-: ]//g; + $self->set('mac_addr', $mac_addr); + my $error = $self->ut_numbern('svcnum') - || $self->ut_foreign_key('blocknum', 'addr_block', 'blocknum') + || $self->ut_numbern('blocknum') + || $self->ut_foreign_keyn('sectornum', 'tower_sector', 'sectornum') || $self->ut_textn('description') - || $self->ut_number('speed_up') - || $self->ut_number('speed_down') + || $self->ut_numbern('speed_up') + || $self->ut_numbern('speed_down') || $self->ut_ipn('ip_addr') || $self->ut_hexn('mac_addr') || $self->ut_hexn('auth_key') - || $self->ut_coordn('latitude', -90, 90) - || $self->ut_coordn('longitude', -180, 180) + || $self->ut_coordn('latitude', $lat_lower, 90) + || $self->ut_coordn('longitude', -180, $lon_upper) || $self->ut_sfloatn('altitude') || $self->ut_textn('vlan_profile') + || $self->ut_textn('plan_id') ; return $error if $error; 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 ($self->blocknum) { + $error = $self->ut_foreign_key('blocknum', 'addr_block', 'blocknum'); + return $error if $error; + } + + if ($cust_pkg && $self->blocknum) { + 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"; + } + } + + $error = $self->_check_ip_addr; + return $error if $error; + + $self->SUPER::check; +} + +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'); #&& !$self->blocknum + + return "Must supply either address or block" + unless $self->blocknum; my $next_addr = $self->addr_block->next_free_addr; if ($next_addr) { $self->ip_addr($next_addr->addr); } else { return "No free addresses in addr_block (blocknum: ".$self->blocknum.")"; } + + } + + if (not($self->blocknum)) { + return "Must supply either address or block" + unless ($self->ip_addr and $self->ip_addr ne '0.0.0.0'); + my @block = grep { $_->NetAddr->contains($self->NetAddr) } + map { $_->addr_block } + $self->allowed_routers; + if (scalar(@block)) { + $self->blocknum($block[0]->blocknum); + }else{ + return "Address not with available block."; + } } # This should catch errors in the ip_addr. If it doesn't, @@ -241,9 +454,21 @@ sub check { return 'Router '.$router->routernum.' cannot provide svcpart '.$self->svcpart; } - $self->SUPER::check; + ''; +} + +sub _check_duplicate { + my $self = shift; + + return "MAC already in use" + if ( $self->mac_addr && + scalar( qsearch( 'svc_broadband', { 'mac_addr', $self->mac_addr } ) ) + ); + + ''; } + =item NetAddr Returns a NetAddr::IP object containing the IP address of this service. The netmask @@ -253,7 +478,17 @@ is /32. sub NetAddr { my $self = shift; - return new NetAddr::IP ($self->ip_addr); + new NetAddr::IP ($self->ip_addr); +} + +=item tower_sector + +=cut + +sub tower_sector { + my $self = shift; + return '' unless $self->sectornum; + qsearchs('tower_sector', { sectornum => $self->sectornum }); } =item addr_block @@ -264,8 +499,7 @@ Returns the FS::addr_block record (i.e. the address block) for this broadband se sub addr_block { my $self = shift; - - return qsearchs('addr_block', { blocknum => $self->blocknum }); + qsearchs('addr_block', { blocknum => $self->blocknum }); } =back @@ -278,14 +512,16 @@ Returns a list of allowed FS::router objects. sub allowed_routers { my $self = shift; - - return map { $_->router } qsearch('part_svc_router', { svcpart => $self->svcpart }); + map { $_->router } qsearch('part_svc_router', { svcpart => $self->svcpart }); } =head1 BUGS 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,