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