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.
100 'name' => 'Wireless broadband',
101 'name_plural' => 'Wireless broadband services',
102 'longname_plural' => 'Fixed wireless broadband services',
103 'display_weight' => 50,
104 'cancel_weight' => 70,
105 'ip_field' => 'ip_addr',
106 'manual_require' => 1,
108 'svcnum' => 'Service',
109 'description' => 'Descriptive label',
111 'label' => 'Upload speed (Kbps)',
112 'type' => 'fcc_477_speed',
113 'def_info' => 'both upload and download speed must be set to FCC 477 information if using that modifier',
116 'label' => 'Download speed (Kbps)',
117 'type' => 'fcc_477_speed',
118 'def_info' => 'both upload and download speed must be set to FCC 477 information if using that modifier',
120 'ip_addr' => 'IP address',
122 'label' => 'Address block',
124 'select_table' => 'addr_block',
125 'select_key' => 'blocknum',
126 'select_label' => 'cidr',
127 'disable_inventory' => 1,
129 'plan_id' => 'Service Plan Id',
130 'performance_profile' => 'Peformance Profile',
131 'authkey' => 'Authentication key',
132 'mac_addr' => 'MAC address',
133 'latitude' => 'Latitude',
134 'longitude' => 'Longitude',
135 'altitude' => 'Altitude',
136 'vlan_profile' => 'VLAN profile',
137 'sectornum' => 'Tower/sector',
138 'routernum' => 'Router/block',
140 label => 'RADIUS groups',
141 type => 'select-radius_group.html',
142 #select_table => 'radius_group',
143 #select_key => 'groupnum',
144 #select_label => 'groupname',
146 disable_inventory => 1,
149 'radio_serialnum' => 'Radio Serial Number',
150 'radio_location' => 'Radio Location',
151 'poe_location' => 'POE Location',
154 'shared_svcnum' => { label => 'Shared Service',
155 type => 'search-svc_broadband',
156 disable_inventory => 1,
158 'serviceid' => 'Torrus serviceid', #but is should be hidden
159 'speed_test_up' => { 'label' => 'Speed test upload (Kbps)' },
160 'speed_test_down' => { 'label' => 'Speed test download (Kbps)' },
161 'speed_test_latency' => 'Speed test latency (ms)',
166 sub table { 'svc_broadband'; }
168 sub table_dupcheck_fields { ( 'ip_addr', 'mac_addr' ); }
172 Class method which returns a qsearch hash expression to search for parameters
173 specified in HASHREF.
179 =item unlinked - set to search for all unlinked services. Overrides all other options.
189 =item pkgpart - arrayref
191 =item routernum - arrayref
193 =item sectornum - arrayref
195 =item towernum - arrayref
204 my( $class, $params, $from, $where ) = @_;
206 #routernum, can be arrayref
207 for my $routernum ( $params->{'routernum'} ) {
208 # this no longer uses addr_block
209 if ( ref $routernum and grep { $_ } @$routernum ) {
210 my $in = join(',', map { /^(\d+)$/ ? $1 : () } @$routernum );
212 push @orwhere, "svc_broadband.routernum IN ($in)" if $in;
213 push @orwhere, "svc_broadband.routernum IS NULL"
214 if grep /^none$/, @$routernum;
215 push @$where, '( '.join(' OR ', @orwhere).' )';
217 elsif ( $routernum =~ /^(\d+)$/ ) {
218 push @$where, "svc_broadband.routernum = $1";
220 elsif ( $routernum eq 'none' ) {
221 push @$where, "svc_broadband.routernum IS NULL";
225 #this should probably move to svc_Tower_Mixin, or maybe we never should have
226 # done svc_acct # towers (or, as mark thought, never should have done
229 #sector and tower, as above
230 my @where_sector = $class->tower_sector_sql($params);
231 if ( @where_sector ) {
232 push @$where, @where_sector;
233 push @$from, 'LEFT JOIN tower_sector USING ( sectornum )';
237 if ( $params->{'ip_addr'} =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/ ) {
238 push @$where, "ip_addr = '$1'";
243 =item search_sql STRING
245 Class method which returns an SQL fragment to search for the given string.
250 my( $class, $string ) = @_;
251 if ( $string =~ /^(\d{1,3}\.){3}\d{1,3}$/ ) {
252 $class->search_sql_field('ip_addr', $string );
253 } elsif ( $string =~ /^([A-F0-9]{12})$/i ) {
254 $class->search_sql_field('mac_addr', uc($string));
255 } elsif ( $string =~ /^(([A-F0-9]{2}:){5}([A-F0-9]{2}))$/i ) {
257 $class->search_sql_field('mac_addr', uc($string) );
258 } elsif ( $string =~ /^(\d+)$/ ) {
259 my $table = $class->table;
260 "$table.svcnum = $1";
266 =item smart_search STRING
271 my( $class, $string ) = @_;
273 'table' => $class->table, #'svc_broadband',
275 'extra_sql' => 'WHERE '. $class->search_sql($string),
281 Returns the IP address, MAC address and description.
287 my $label = 'IP:'. ($self->ip_addr || 'Unknown');
288 $label .= ', MAC:'. $self->mac_addr
290 $label .= ' ('. $self->description. ')'
291 if $self->description;
295 =item insert [ , OPTION => VALUE ... ]
297 Adds this record to the database. If there is an error, returns the error,
298 otherwise returns false.
300 The additional fields pkgnum and svcpart (see FS::cust_svc) should be
301 defined. An FS::cust_svc record will be created and inserted.
303 Currently available options are: I<depend_jobnum>
305 If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
306 jobnums), all provisioning jobs will have a dependancy on the supplied
307 jobnum(s) (they will not run until the specific job(s) complete(s)).
311 # Standard FS::svc_Common::insert
315 Delete this record from the database.
319 # Standard FS::svc_Common::delete
321 =item replace OLD_RECORD
323 Replaces the OLD_RECORD with this one in the database. If there is an error,
324 returns the error, otherwise returns false.
326 # Standard FS::svc_Common::replace
330 Called by the suspend method of FS::cust_pkg (see FS::cust_pkg).
334 Called by the unsuspend method of FS::cust_pkg (see FS::cust_pkg).
338 Called by the cancel method of FS::cust_pkg (see FS::cust_pkg).
342 Checks all fields to make sure this is a valid broadband service. If there is
343 an error, returns the error, otherwise returns false. Called by the insert
350 my $x = $self->setfixed;
352 return $x unless ref($x);
355 my $mac_addr = uc($self->get('mac_addr'));
356 $mac_addr =~ s/[\W_]//g;
357 $self->set('mac_addr', $mac_addr);
360 $self->ut_numbern('svcnum')
361 || $self->ut_numbern('blocknum')
362 || $self->ut_foreign_keyn('routernum', 'router', 'routernum')
363 || $self->ut_foreign_keyn('sectornum', 'tower_sector', 'sectornum')
364 || $self->ut_textn('description')
365 || $self->ut_numbern('speed_up')
366 || $self->ut_numbern('speed_down')
367 || $self->ut_numbern('speed_test_up')
368 || $self->ut_numbern('speed_test_down')
369 || $self->ut_ipn('ip_addr')
370 || $self->ut_hexn('mac_addr')
371 || $self->ut_hexn('auth_key')
372 || $self->ut_coordn('latitude')
373 || $self->ut_coordn('longitude')
374 || $self->ut_sfloatn('altitude')
375 || $self->ut_textn('vlan_profile')
376 || $self->ut_textn('plan_id')
377 || $self->ut_alphan('radio_serialnum')
378 || $self->ut_textn('radio_location')
379 || $self->ut_textn('poe_location')
380 || $self->ut_snumbern('rssi')
381 || $self->ut_numbern('suid')
382 || $self->ut_foreign_keyn('shared_svcnum', 'svc_broadband', 'svcnum')
383 || $self->ut_textn('serviceid') #too lenient?
385 return $error if $error;
387 if(($self->speed_up || 0) < 0) { return 'speed_up must be positive'; }
388 if(($self->speed_down || 0) < 0) { return 'speed_down must be positive'; }
390 my $cust_svc = $self->svcnum
391 ? qsearchs('cust_svc', { 'svcnum' => $self->svcnum } )
396 $cust_pkg = $cust_svc->cust_pkg;
397 $svcpart = $cust_svc->svcpart;
399 $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $self->pkgnum } );
400 return "Invalid pkgnum" unless $cust_pkg;
401 $svcpart = $self->svcpart;
403 my $agentnum = $cust_pkg->cust_main->agentnum if $cust_pkg;
405 # assign IP address / router / block
406 $error = $self->svc_ip_check;
407 return $error if $error;
409 and !$conf->exists('svc_broadband-allow_null_ip_addr') ) {
410 return 'IP address is required';
413 if ( $cust_pkg && ! $self->latitude && ! $self->longitude ) {
414 my $l = $cust_pkg->cust_location_or_main;
415 if ( $l->ship_latitude && $l->ship_longitude ) {
416 $self->latitude( $l->ship_latitude );
417 $self->longitude( $l->ship_longitude );
418 } elsif ( $l->latitude && $l->longitude ) {
419 $self->latitude( $l->latitude );
420 $self->longitude( $l->longitude );
427 sub _check_duplicate {
429 # Not a reliable check because the table isn't locked, but
430 # that's why we have a unique index. This is just to give a
431 # friendlier error message.
433 @dup = $self->find_duplicates('global', 'mac_addr');
435 return "MAC address in use (svcnum ".$dup[0]->svcnum.")";
445 local($FS::svc_Common::noexport_hack) = 1;
447 # fix wrong-case MAC addresses
449 $dbh->do('UPDATE svc_broadband SET mac_addr = UPPER(mac_addr);')
452 # set routernum to addr_block.routernum
453 foreach my $self (qsearch('svc_broadband', {
454 blocknum => {op => '!=', value => ''},
457 my $addr_block = $self->addr_block;
458 if ( !$addr_block ) {
459 # super paranoid mode
460 warn "WARNING: svcnum ".$self->svcnum." is assigned to addr_block ".$self->blocknum.", which does not exist; skipped.\n";
463 my $ip_addr = $self->ip_addr;
464 my $routernum = $addr_block->routernum;
466 $self->set(routernum => $routernum);
467 my $error = $self->check;
468 # sanity check: don't allow this to change IP address or block
469 # (other than setting blocknum to null for a non-auto-assigned router)
470 if ( $self->ip_addr ne $ip_addr
471 or ($self->blocknum and $self->blocknum != $addr_block->blocknum)) {
472 warn "WARNING: Upgrading service ".$self->svcnum." would change its block/address; skipped.\n";
476 $error ||= $self->replace;
477 warn "WARNING: error assigning routernum $routernum to service ".$self->svcnum.
478 ":\n$error; skipped\n"
482 warn "svcnum ".$self->svcnum.
483 ": no routernum in address block ".$addr_block->cidr.", skipped\n";
487 # assign blocknums to services that should have them
488 my @all_blocks = qsearch('addr_block', { });
489 SVC: foreach my $self (
491 'select' => 'svc_broadband.*',
492 'table' => 'svc_broadband',
493 'addl_from' => 'JOIN router USING (routernum)',
495 'extra_sql' => 'WHERE svc_broadband.blocknum IS NULL '.
496 'AND router.manual_addr IS NULL',
500 next SVC if $self->ip_addr eq '';
501 my $NetAddr = $self->NetAddr;
502 # inefficient, but should only need to run once
503 foreach my $block (@all_blocks) {
504 if ($block->NetAddr->contains($NetAddr)) {
505 $self->set(blocknum => $block->blocknum);
506 my $error = $self->replace;
507 warn "WARNING: error assigning blocknum ".$block->blocknum.
508 " to service ".$self->svcnum."\n$error; skipped\n"
513 warn "WARNING: no block found containing ".$NetAddr->addr." for service ".
518 require FS::Misc::FixIPFormat;
519 FS::Misc::FixIPFormat::fix_bad_addresses_in_table(
520 'svc_broadband', 'svcnum', 'ip_addr',
530 The business with sb_field has been 'fixed', in a manner of speaking.
532 allowed_routers isn't agent virtualized because part_svc isn't agent
535 Having both routernum and blocknum as foreign keys is somewhat dubious.
539 FS::svc_Common, FS::Record, FS::addr_block,
540 FS::part_svc, schema.html from the base documentation.