d9a25b487aa98576ac420f26f9a86f1fff7beeb7
[freeside.git] / FS / FS / svc_broadband.pm
1 package FS::svc_broadband;
2
3 use strict;
4 use vars qw(@ISA $conf);
5 use FS::Record qw( qsearchs qsearch dbh );
6 use FS::svc_Common;
7 use FS::cust_svc;
8 use FS::addr_block;
9 use FS::part_svc_router;
10 use NetAddr::IP;
11
12 @ISA = qw( FS::svc_Common );
13
14 $FS::UID::callback{'FS::svc_broadband'} = sub { 
15   $conf = new FS::Conf;
16 };
17
18 =head1 NAME
19
20 FS::svc_broadband - Object methods for svc_broadband records
21
22 =head1 SYNOPSIS
23
24   use FS::svc_broadband;
25
26   $record = new FS::svc_broadband \%hash;
27   $record = new FS::svc_broadband { 'column' => 'value' };
28
29   $error = $record->insert;
30
31   $error = $new_record->replace($old_record);
32
33   $error = $record->delete;
34
35   $error = $record->check;
36
37   $error = $record->suspend;
38
39   $error = $record->unsuspend;
40
41   $error = $record->cancel;
42
43 =head1 DESCRIPTION
44
45 An FS::svc_broadband object represents a 'broadband' Internet connection, such
46 as a DSL, cable modem, or fixed wireless link.  These services are assumed to
47 have the following properties:
48
49 FS::svc_broadband inherits from FS::svc_Common.  The following fields are
50 currently supported:
51
52 =over 4
53
54 =item svcnum - primary key
55
56 =item blocknum - see FS::addr_block
57
58 =item
59 speed_up - maximum upload speed, in bits per second.  If set to zero, upload
60 speed will be unlimited.  Exports that do traffic shaping should handle this
61 correctly, and not blindly set the upload speed to zero and kill the customer's
62 connection.
63
64 =item
65 speed_down - maximum download speed, as above
66
67 =item ip_addr - the customer's IP address.  If the customer needs more than one
68 IP address, set this to the address of the customer's router.  As a result, the
69 customer's router will have the same address for both its internal and external
70 interfaces thus saving address space.  This has been found to work on most NAT
71 routers available.
72
73 =back
74
75 =head1 METHODS
76
77 =over 4
78
79 =item new HASHREF
80
81 Creates a new svc_broadband.  To add the record to the database, see
82 "insert".
83
84 Note that this stores the hash reference, not a distinct copy of the hash it
85 points to.  You can ask the object for a copy with the I<hash> method.
86
87 =cut
88
89 sub table_info {
90   {
91     'name' => 'Broadband',
92     'name_plural' => 'Broadband services',
93     'longname_plural' => 'Fixed (username-less) broadband services',
94     'display_weight' => 50,
95     'cancel_weight'  => 70,
96     'fields' => {
97       'description' => 'Descriptive label for this particular device.',
98       'speed_down'  => 'Maximum download speed for this service in Kbps.  0 denotes unlimited.',
99       'speed_up'    => 'Maximum upload speed for this service in Kbps.  0 denotes unlimited.',
100       'ip_addr'     => 'IP address.  Leave blank for automatic assignment.',
101       'blocknum'    => { 'label' => 'Address block',
102                          'type'  => 'select',
103                          'select_table' => 'addr_block',
104                          'select_key'   => 'blocknum',
105                          'select_label' => 'cidr',
106                          'disable_inventory' => 1,
107                        },
108     },
109   };
110 }
111
112 sub table { 'svc_broadband'; }
113
114 =item search_sql STRING
115
116 Class method which returns an SQL fragment to search for the given string.
117
118 =cut
119
120 sub search_sql {
121   my( $class, $string ) = @_;
122   if ( $string =~ /^(\d{1,3}\.){3}\d{1,3}$/ ) {
123     $class->search_sql_field('ip_addr', $string );
124   }elsif ( $string =~ /^([a-fA-F0-9]{12})$/ ) {
125     $class->search_sql_field('mac_addr', uc($string));
126   }elsif ( $string =~ /^(([a-fA-F0-9]{1,2}:){5}([a-fA-F0-9]{1,2}))$/ ) {
127     $class->search_sql_field('mac_addr', uc("$2$3$4$5$6$7") );
128   } else {
129     '1 = 0'; #false
130   }
131 }
132
133 =item label
134
135 Returns the IP address.
136
137 =cut
138
139 sub label {
140   my $self = shift;
141   $self->ip_addr;
142 }
143
144 =item insert [ , OPTION => VALUE ... ]
145
146 Adds this record to the database.  If there is an error, returns the error,
147 otherwise returns false.
148
149 The additional fields pkgnum and svcpart (see FS::cust_svc) should be 
150 defined.  An FS::cust_svc record will be created and inserted.
151
152 Currently available options are: I<depend_jobnum>
153
154 If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
155 jobnums), all provisioning jobs will have a dependancy on the supplied
156 jobnum(s) (they will not run until the specific job(s) complete(s)).
157
158 =cut
159
160 # Standard FS::svc_Common::insert
161
162 =item delete
163
164 Delete this record from the database.
165
166 =cut
167
168 # Standard FS::svc_Common::delete
169
170 =item replace OLD_RECORD
171
172 Replaces the OLD_RECORD with this one in the database.  If there is an error,
173 returns the error, otherwise returns false.
174
175 =cut
176
177 # Standard FS::svc_Common::replace
178
179 =item suspend
180
181 Called by the suspend method of FS::cust_pkg (see FS::cust_pkg).
182
183 =item unsuspend
184
185 Called by the unsuspend method of FS::cust_pkg (see FS::cust_pkg).
186
187 =item cancel
188
189 Called by the cancel method of FS::cust_pkg (see FS::cust_pkg).
190
191 =item check
192
193 Checks all fields to make sure this is a valid broadband service.  If there is
194 an error, returns the error, otherwise returns false.  Called by the insert
195 and replace methods.
196
197 =cut
198
199 sub check {
200   my $self = shift;
201   my $x = $self->setfixed;
202
203   return $x unless ref($x);
204
205   my $error =
206     $self->ut_numbern('svcnum')
207     || $self->ut_numbern('blocknum')
208     || $self->ut_textn('description')
209     || $self->ut_number('speed_up')
210     || $self->ut_number('speed_down')
211     || $self->ut_ipn('ip_addr')
212     || $self->ut_hexn('mac_addr')
213     || $self->ut_hexn('auth_key')
214     || $self->ut_coordn('latitude', -90, 90)
215     || $self->ut_coordn('longitude', -180, 180)
216     || $self->ut_sfloatn('altitude')
217     || $self->ut_textn('vlan_profile')
218   ;
219   return $error if $error;
220
221   #redundant, but better error message
222   return "MAC already in use"
223     if scalar( qsearch( 'svc_broadband', { 'mac_addr', $self->mac_addr } ) );
224
225   if($self->speed_up < 0) { return 'speed_up must be positive'; }
226   if($self->speed_down < 0) { return 'speed_down must be positive'; }
227
228   my $cust_svc = $self->svcnum
229                  ? qsearchs('cust_svc', { 'svcnum' => $self->svcnum } )
230                  : '';
231   my $cust_pkg;
232   if ($cust_svc) {
233     $cust_pkg = $cust_svc->cust_pkg;
234   }else{
235     $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $self->pkgnum } );
236     return "Invalid pkgnum" unless $cust_pkg;
237   }
238     
239   if ($self->blocknum) {
240     $error = $self->ut_foreign_key('blocknum', 'addr_block', 'blocknum');
241     return $error if $error;
242   }
243
244   if ($cust_pkg && $self->blocknum) {
245     my $addr_agentnum = $self->addr_block->agentnum;
246     if ($addr_agentnum && $addr_agentnum != $cust_pkg->cust_main->agentnum) {
247       return "Address block does not service this customer";
248     }
249   }
250
251   if (not($self->ip_addr) or $self->ip_addr eq '0.0.0.0') {
252     return "Must supply either address or block"
253       unless $self->blocknum;
254     my $next_addr = $self->addr_block->next_free_addr;
255     if ($next_addr) {
256       $self->ip_addr($next_addr->addr);
257     } else {
258       return "No free addresses in addr_block (blocknum: ".$self->blocknum.")";
259     }
260   }
261
262   if (not($self->blocknum)) {
263     return "Must supply either address or block"
264       unless ($self->ip_addr and $self->ip_addr ne '0.0.0.0');
265     my @block = grep { $_->NetAddr->contains($self->NetAddr) }
266                  map { $_->addr_block }
267                  $self->allowed_routers;
268     if (scalar(@block)) {
269       $self->blocknum($block[0]->blocknum);
270     }else{
271       return "Address not with available block.";
272     }
273   }
274
275   # This should catch errors in the ip_addr.  If it doesn't,
276   # they'll almost certainly not map into the block anyway.
277   my $self_addr = $self->NetAddr; #netmask is /32
278   return ('Cannot parse address: ' . $self->ip_addr) unless $self_addr;
279
280   my $block_addr = $self->addr_block->NetAddr;
281   unless ($block_addr->contains($self_addr)) {
282     return 'blocknum '.$self->blocknum.' does not contain address '.$self->ip_addr;
283   }
284
285   my $router = $self->addr_block->router 
286     or return 'Cannot assign address from unallocated block:'.$self->addr_block->blocknum;
287   if(grep { $_->routernum == $router->routernum} $self->allowed_routers) {
288   } # do nothing
289   else {
290     return 'Router '.$router->routernum.' cannot provide svcpart '.$self->svcpart;
291   }
292
293   $self->SUPER::check;
294 }
295
296 =item NetAddr
297
298 Returns a NetAddr::IP object containing the IP address of this service.  The netmask 
299 is /32.
300
301 =cut
302
303 sub NetAddr {
304   my $self = shift;
305   new NetAddr::IP ($self->ip_addr);
306 }
307
308 =item addr_block
309
310 Returns the FS::addr_block record (i.e. the address block) for this broadband service.
311
312 =cut
313
314 sub addr_block {
315   my $self = shift;
316   qsearchs('addr_block', { blocknum => $self->blocknum });
317 }
318
319 =back
320
321 =item allowed_routers
322
323 Returns a list of allowed FS::router objects.
324
325 =cut
326
327 sub allowed_routers {
328   my $self = shift;
329   map { $_->router } qsearch('part_svc_router', { svcpart => $self->svcpart });
330 }
331
332 =head1 BUGS
333
334 The business with sb_field has been 'fixed', in a manner of speaking.
335
336 allowed_routers isn't agent virtualized because part_svc isn't agent
337 virtualized
338
339 =head1 SEE ALSO
340
341 FS::svc_Common, FS::Record, FS::addr_block,
342 FS::part_svc, schema.html from the base documentation.
343
344 =cut
345
346 1;
347