use FS::svc_Common;
use FS::cust_svc;
use FS::addr_block;
+use FS::part_svc_router;
use NetAddr::IP;
@ISA = qw( FS::svc_Common );
interfaces thus saving address space. This has been found to work on most NAT
routers available.
+=item plan_id
+
=back
=head1 METHODS
=cut
+sub table_info {
+ {
+ 'name' => 'Broadband',
+ 'name_plural' => 'Broadband services',
+ 'longname_plural' => 'Fixed (username-less) broadband services',
+ 'display_weight' => 50,
+ 'cancel_weight' => 70,
+ 'fields' => {
+ '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',
+ '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',
+ },
+ };
+}
+
sub table { 'svc_broadband'; }
-=item insert
+sub table_dupcheck_fields { ( 'mac_addr' ); }
-Adds this record to the database. If there is an error, returns the error,
-otherwise returns false.
+=item search HASHREF
-The additional fields pkgnum and svcpart (see FS::cust_svc) should be
-defined. An FS::cust_svc record will be created and inserted.
+Class method which returns a qsearch hash expression to search for parameters
+specified in HASHREF.
-=cut
+Parameters:
-# Standard FS::svc_Common::insert
+=over 4
-=item delete
+=item unlinked - set to search for all unlinked services. Overrides all other options.
-Delete this record from the database.
+=item agentnum
+
+=item custnum
+
+=item svcpart
+
+=item ip_addr
+
+=item pkgpart - arrayref
+
+=item routernum - arrayref
+
+=item order_by
+
+=back
=cut
-# Standard FS::svc_Common::delete
+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, "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";
+ }
-=item replace OLD_RECORD
+ #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";
+ }
+ }
-Replaces the OLD_RECORD with this one in the database. If there is an error,
-returns the error, otherwise returns false.
+ #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.
=cut
-# Standard FS::svc_Common::replace
+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})$/ ) {
+ $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") );
+ } else {
+ '1 = 0'; #false
+ }
+}
-=item sb_field
+=item label
-Returns a list of FS::sb_field objects assigned to this object.
+Returns the IP address.
=cut
-sub sb_field {
+sub label {
my $self = shift;
-
- return qsearch( 'sb_field', { svcnum => $self->svcnum } );
+ $self->ip_addr;
}
-=item sb_field_hashref
+=item insert [ , OPTION => VALUE ... ]
-Returns a hashref of the FS::sb_field key/value pairs for this object.
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+The additional fields pkgnum and svcpart (see FS::cust_svc) should be
+defined. An FS::cust_svc record will be created and inserted.
+
+Currently available options are: I<depend_jobnum>
-Deprecated. Please don't use it.
+If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
+jobnums), all provisioning jobs will have a dependancy on the supplied
+jobnum(s) (they will not run until the specific job(s) complete(s)).
=cut
-# Kristian wrote this, but don't hold it against him. He was under a powerful
-# distracting influence whom he evidently found much more interesting than
-# svc_broadband.pm. I can't say I blame him.
+# Standard FS::svc_Common::insert
-sub sb_field_hashref {
- my $self = shift;
- my $svcpart = shift;
+=item delete
- if ((not $svcpart) && ($self->cust_svc)) {
- $svcpart = $self->cust_svc->svcpart;
- }
+Delete this record from the database.
- my $hashref = {};
+=cut
- map {
- my $sb_field = qsearchs('sb_field', { sbfieldpart => $_->sbfieldpart,
- svcnum => $self->svcnum });
- $hashref->{$_->getfield('name')} = $sb_field ? $sb_field->getfield('value') : '';
- } qsearch('part_sb_field', { svcpart => $svcpart });
+# Standard FS::svc_Common::delete
- return $hashref;
+=item replace OLD_RECORD
-}
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# Standard FS::svc_Common::replace
=item suspend
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;
+
my $error =
$self->ut_numbern('svcnum')
- || $self->ut_foreign_key('blocknum', 'addr_block', 'blocknum')
+ || $self->ut_numbern('blocknum')
+ || $self->ut_textn('description')
|| $self->ut_number('speed_up')
|| $self->ut_number('speed_down')
|| $self->ut_ipn('ip_addr')
+ || $self->ut_hexn('mac_addr')
+ || $self->ut_hexn('auth_key')
+ || $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') {
- $self->ip_addr($self->addr_block->next_free_addr->addr);
- if (not $self->ip_addr) {
+
+ 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,
return 'Router '.$router->routernum.' cannot provide svcpart '.$self->svcpart;
}
+ '';
+}
- ''; #no error
+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
sub NetAddr {
my $self = shift;
- return new NetAddr::IP ($self->ip_addr);
+ new NetAddr::IP ($self->ip_addr);
}
=item addr_block
sub addr_block {
my $self = shift;
-
- return qsearchs('addr_block', { blocknum => $self->blocknum });
+ qsearchs('addr_block', { blocknum => $self->blocknum });
}
=back
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
-I think there's one place in the code where we actually use sb_field_hashref.
-That's a bug in itself.
+The business with sb_field has been 'fixed', in a manner of speaking.
-The real problem with it is that we're still grappling with the question of how
-tightly xfields should be integrated with real fields. There are a few
-different directions we could go with it--we I<could> override several
-functions in Record so that xfields behave almost exactly like real fields (can
-be set with setfield(), appear in fields() and hash(), used as criteria in
-qsearch(), etc.).
+allowed_routers isn't agent virtualized because part_svc isn't agent
+virtualized
=head1 SEE ALSO
-FS::svc_Common, FS::Record, FS::addr_block, FS::sb_field,
+FS::svc_Common, FS::Record, FS::addr_block,
FS::part_svc, schema.html from the base documentation.
=cut