1 package FS::svc_broadband;
14 { no warnings 'redefine'; use NetAddr::IP; }
15 use FS::Record qw( qsearchs qsearch dbh );
18 use FS::part_svc_router;
21 $FS::UID::callback{'FS::svc_broadband'} = sub {
27 FS::svc_broadband - Object methods for svc_broadband records
31 use FS::svc_broadband;
33 $record = new FS::svc_broadband \%hash;
34 $record = new FS::svc_broadband { 'column' => 'value' };
36 $error = $record->insert;
38 $error = $new_record->replace($old_record);
40 $error = $record->delete;
42 $error = $record->check;
44 $error = $record->suspend;
46 $error = $record->unsuspend;
48 $error = $record->cancel;
52 An FS::svc_broadband object represents a 'broadband' Internet connection, such
53 as a DSL, cable modem, or fixed wireless link. These services are assumed to
54 have the following properties:
56 FS::svc_broadband inherits from FS::svc_Common. The following fields are
61 =item svcnum - primary key
63 =item blocknum - see FS::addr_block
66 speed_up - maximum upload speed, in bits per second. If set to zero, upload
67 speed will be unlimited. Exports that do traffic shaping should handle this
68 correctly, and not blindly set the upload speed to zero and kill the customer's
72 speed_down - maximum download speed, as above
74 =item ip_addr - the customer's IP address. If the customer needs more than one
75 IP address, set this to the address of the customer's router. As a result, the
76 customer's router will have the same address for both its internal and external
77 interfaces thus saving address space. This has been found to work on most NAT
90 Creates a new svc_broadband. To add the record to the database, see
93 Note that this stores the hash reference, not a distinct copy of the hash it
94 points to. You can ask the object for a copy with the I<hash> method.
99 my $conf = new FS::Conf;
100 my $ip_addr_required = $conf->exists('svc_broadband-allow_null_ip_addr') ? '' : '1';
102 'name' => 'Wireless broadband',
103 'name_plural' => 'Wireless broadband services',
104 'longname_plural' => 'Fixed wireless broadband services',
105 'display_weight' => 50,
106 'cancel_weight' => 70,
107 'ip_field' => 'ip_addr',
108 'manual_require' => 1,
110 'svcnum' => 'Service',
111 'description' => 'Descriptive label',
113 'label' => 'Upload speed (Kbps)',
114 'type' => 'fcc_477_speed',
115 'def_info' => 'both upload and download speed must be set to FCC 477 information if using that modifier',
118 'label' => 'Download speed (Kbps)',
119 'type' => 'fcc_477_speed',
120 'def_info' => 'both upload and download speed must be set to FCC 477 information if using that modifier',
123 'label' => 'IP address',
124 'required' => $ip_addr_required,
127 'label' => 'Address block',
129 'select_table' => 'addr_block',
130 'select_key' => 'blocknum',
131 'select_label' => 'cidr',
132 'disable_inventory' => 1,
134 'plan_id' => 'Service Plan Id',
135 'performance_profile' => 'Peformance Profile',
136 'authkey' => 'Authentication key',
137 'mac_addr' => 'MAC address',
138 'latitude' => 'Latitude',
139 'longitude' => 'Longitude',
140 'altitude' => 'Altitude',
141 'vlan_profile' => 'VLAN profile',
142 'sectornum' => 'Tower/sector',
143 'routernum' => 'Router/block',
145 label => 'RADIUS groups',
146 type => 'select-radius_group.html',
147 #select_table => 'radius_group',
148 #select_key => 'groupnum',
149 #select_label => 'groupname',
151 disable_inventory => 1,
154 'radio_serialnum' => 'Radio Serial Number',
155 'radio_location' => 'Radio Location',
156 'poe_location' => 'POE Location',
159 'shared_svcnum' => { label => 'Shared Service',
160 type => 'search-svc_broadband',
161 disable_inventory => 1,
163 'serviceid' => 'Torrus serviceid', #but is should be hidden
164 'speed_test_up' => { 'label' => 'Speed test upload (Kbps)' },
165 'speed_test_down' => { 'label' => 'Speed test download (Kbps)' },
166 'speed_test_latency' => 'Speed test latency (ms)',
171 sub table { 'svc_broadband'; }
173 sub table_dupcheck_fields { ( 'ip_addr', 'mac_addr' ); }
177 Class method which returns a qsearch hash expression to search for parameters
178 specified in HASHREF.
184 =item unlinked - set to search for all unlinked services. Overrides all other options.
194 =item pkgpart - arrayref
196 =item routernum - arrayref
198 =item sectornum - arrayref
200 =item towernum - arrayref
209 my( $class, $params, $from, $where ) = @_;
211 #routernum, can be arrayref
212 for my $routernum ( $params->{'routernum'} ) {
213 # this no longer uses addr_block
214 if ( ref $routernum and grep { $_ } @$routernum ) {
215 my $in = join(',', map { /^(\d+)$/ ? $1 : () } @$routernum );
217 push @orwhere, "svc_broadband.routernum IN ($in)" if $in;
218 push @orwhere, "svc_broadband.routernum IS NULL"
219 if grep /^none$/, @$routernum;
220 push @$where, '( '.join(' OR ', @orwhere).' )';
222 elsif ( $routernum =~ /^(\d+)$/ ) {
223 push @$where, "svc_broadband.routernum = $1";
225 elsif ( $routernum eq 'none' ) {
226 push @$where, "svc_broadband.routernum IS NULL";
230 #this should probably move to svc_Tower_Mixin, or maybe we never should have
231 # done svc_acct # towers (or, as mark thought, never should have done
234 #sector and tower, as above
235 my @where_sector = $class->tower_sector_sql($params);
236 if ( @where_sector ) {
237 push @$where, @where_sector;
238 push @$from, 'LEFT JOIN tower_sector USING ( sectornum )';
242 if ( $params->{'ip_addr'} =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/ ) {
243 push @$where, "ip_addr = '$1'";
248 =item search_sql STRING
250 Class method which returns an SQL fragment to search for the given string.
255 my( $class, $string ) = @_;
256 if ( $string =~ /^(\d{1,3}\.){3}\d{1,3}$/ ) {
257 $class->search_sql_field('ip_addr', $string );
258 } elsif ( $string =~ /^([A-F0-9]{12})$/i ) {
259 $class->search_sql_field('mac_addr', uc($string));
260 } elsif ( $string =~ /^(([A-F0-9]{2}:){5}([A-F0-9]{2}))$/i ) {
262 $class->search_sql_field('mac_addr', uc($string) );
263 } elsif ( $string =~ /^(\d+)$/ ) {
264 my $table = $class->table;
265 "$table.svcnum = $1";
271 =item smart_search STRING
276 my( $class, $string ) = @_;
278 'table' => $class->table, #'svc_broadband',
280 'extra_sql' => 'WHERE '. $class->search_sql($string),
286 Returns the IP address, MAC address and description.
292 my $label = 'IP:'. ($self->ip_addr || 'Unknown');
293 $label .= ', MAC:'. $self->mac_addr
295 $label .= ' ('. $self->description. ')'
296 if $self->description;
300 =item insert [ , OPTION => VALUE ... ]
302 Adds this record to the database. If there is an error, returns the error,
303 otherwise returns false.
305 The additional fields pkgnum and svcpart (see FS::cust_svc) should be
306 defined. An FS::cust_svc record will be created and inserted.
308 Currently available options are: I<depend_jobnum>
310 If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
311 jobnums), all provisioning jobs will have a dependancy on the supplied
312 jobnum(s) (they will not run until the specific job(s) complete(s)).
316 # Standard FS::svc_Common::insert
320 Delete this record from the database.
324 # Standard FS::svc_Common::delete
326 =item replace OLD_RECORD
328 Replaces the OLD_RECORD with this one in the database. If there is an error,
329 returns the error, otherwise returns false.
331 # Standard FS::svc_Common::replace
335 Called by the suspend method of FS::cust_pkg (see FS::cust_pkg).
339 Called by the unsuspend method of FS::cust_pkg (see FS::cust_pkg).
343 Called by the cancel method of FS::cust_pkg (see FS::cust_pkg).
347 Checks all fields to make sure this is a valid broadband service. If there is
348 an error, returns the error, otherwise returns false. Called by the insert
355 my $x = $self->setfixed;
357 return $x unless ref($x);
360 my $mac_addr = uc($self->get('mac_addr'));
361 $mac_addr =~ s/[\W_]//g;
362 $self->set('mac_addr', $mac_addr);
365 $self->ut_numbern('svcnum')
366 || $self->ut_numbern('blocknum')
367 || $self->ut_foreign_keyn('routernum', 'router', 'routernum')
368 || $self->ut_foreign_keyn('sectornum', 'tower_sector', 'sectornum')
369 || $self->ut_textn('description')
370 || $self->ut_numbern('speed_up')
371 || $self->ut_numbern('speed_down')
372 || $self->ut_numbern('speed_test_up')
373 || $self->ut_numbern('speed_test_down')
374 || $self->ut_ipn('ip_addr')
375 || $self->ut_hexn('mac_addr')
376 || $self->ut_hexn('auth_key')
377 || $self->ut_coordn('latitude')
378 || $self->ut_coordn('longitude')
379 || $self->ut_sfloatn('altitude')
380 || $self->ut_textn('vlan_profile')
381 || $self->ut_textn('plan_id')
382 || $self->ut_alphan('radio_serialnum')
383 || $self->ut_textn('radio_location')
384 || $self->ut_textn('poe_location')
385 || $self->ut_snumbern('rssi')
386 || $self->ut_numbern('suid')
387 || $self->ut_foreign_keyn('shared_svcnum', 'svc_broadband', 'svcnum')
388 || $self->ut_textn('serviceid') #too lenient?
390 return $error if $error;
392 if(($self->speed_up || 0) < 0) { return 'speed_up must be positive'; }
393 if(($self->speed_down || 0) < 0) { return 'speed_down must be positive'; }
395 my $cust_svc = $self->svcnum
396 ? qsearchs('cust_svc', { 'svcnum' => $self->svcnum } )
401 $cust_pkg = $cust_svc->cust_pkg;
402 $svcpart = $cust_svc->svcpart;
404 $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $self->pkgnum } );
405 return "Invalid pkgnum" unless $cust_pkg;
406 $svcpart = $self->svcpart;
408 my $agentnum = $cust_pkg->cust_main->agentnum if $cust_pkg;
410 # assign IP address / router / block
411 $error = $self->svc_ip_check;
412 return $error if $error;
414 and !$conf->exists('svc_broadband-allow_null_ip_addr') ) {
415 return 'IP address is required';
418 if ( $cust_pkg && ! $self->latitude && ! $self->longitude ) {
419 my $l = $cust_pkg->cust_location_or_main;
420 if ( $l->ship_latitude && $l->ship_longitude ) {
421 $self->latitude( $l->ship_latitude );
422 $self->longitude( $l->ship_longitude );
423 } elsif ( $l->latitude && $l->longitude ) {
424 $self->latitude( $l->latitude );
425 $self->longitude( $l->longitude );
432 sub _check_duplicate {
434 # Not a reliable check because the table isn't locked, but
435 # that's why we have a unique index. This is just to give a
436 # friendlier error message.
438 @dup = $self->find_duplicates('global', 'mac_addr');
440 return "MAC address in use (svcnum ".$dup[0]->svcnum.")";
450 local($FS::svc_Common::noexport_hack) = 1;
452 # fix wrong-case MAC addresses
454 $dbh->do('UPDATE svc_broadband SET mac_addr = UPPER(mac_addr);')
457 # set routernum to addr_block.routernum
458 foreach my $self (qsearch('svc_broadband', {
459 blocknum => {op => '!=', value => ''},
462 my $addr_block = $self->addr_block;
463 if ( !$addr_block ) {
464 # super paranoid mode
465 warn "WARNING: svcnum ".$self->svcnum." is assigned to addr_block ".$self->blocknum.", which does not exist; skipped.\n";
468 my $ip_addr = $self->ip_addr;
469 my $routernum = $addr_block->routernum;
471 $self->set(routernum => $routernum);
472 my $error = $self->check;
473 # sanity check: don't allow this to change IP address or block
474 # (other than setting blocknum to null for a non-auto-assigned router)
475 if ( $self->ip_addr ne $ip_addr
476 or ($self->blocknum and $self->blocknum != $addr_block->blocknum)) {
477 warn "WARNING: Upgrading service ".$self->svcnum." would change its block/address; skipped.\n";
481 $error ||= $self->replace;
482 warn "WARNING: error assigning routernum $routernum to service ".$self->svcnum.
483 ":\n$error; skipped\n"
487 warn "svcnum ".$self->svcnum.
488 ": no routernum in address block ".$addr_block->cidr.", skipped\n";
492 # assign blocknums to services that should have them
493 my @all_blocks = qsearch('addr_block', { });
494 SVC: foreach my $self (
496 'select' => 'svc_broadband.*',
497 'table' => 'svc_broadband',
498 'addl_from' => 'JOIN router USING (routernum)',
500 'extra_sql' => 'WHERE svc_broadband.blocknum IS NULL '.
501 'AND router.manual_addr IS NULL',
505 next SVC if $self->ip_addr eq '';
506 my $NetAddr = $self->NetAddr;
507 # inefficient, but should only need to run once
508 foreach my $block (@all_blocks) {
509 if ($block->NetAddr->contains($NetAddr)) {
510 $self->set(blocknum => $block->blocknum);
511 my $error = $self->replace;
512 warn "WARNING: error assigning blocknum ".$block->blocknum.
513 " to service ".$self->svcnum."\n$error; skipped\n"
518 warn "WARNING: no block found containing ".$NetAddr->addr." for service ".
523 require FS::Misc::FixIPFormat;
524 FS::Misc::FixIPFormat::fix_bad_addresses_in_table(
525 'svc_broadband', 'svcnum', 'ip_addr',
535 The business with sb_field has been 'fixed', in a manner of speaking.
537 allowed_routers isn't agent virtualized because part_svc isn't agent
540 Having both routernum and blocknum as foreign keys is somewhat dubious.
544 FS::svc_Common, FS::Record, FS::addr_block,
545 FS::part_svc, schema.html from the base documentation.