svc_broadband MAC searches RT#2985
[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'    => 'Address block.',
101     },
102   };
103 }
104
105 sub table { 'svc_broadband'; }
106
107 =item search_sql STRING
108
109 Class method which returns an SQL fragment to search for the given string.
110
111 =cut
112
113 sub search_sql {
114   my( $class, $string ) = @_;
115   if ( $string =~ /^(\d{1,3}\.){3}\d{1,3}$/ ) {
116     $class->search_sql_field('ip_addr', $string );
117   }elsif ( $string =~ /^([a-fA-F0-9]{12})$/ ) {
118     $class->search_sql_field('mac_addr', uc($string));
119   }elsif ( $string =~ /^(([a-fA-F0-9]{1,2}:){5}([a-fA-F0-9]{1,2}))$/ ) {
120     $class->search_sql_field('mac_addr', uc("$2$3$4$5$6$7") );
121   } else {
122     '1 = 0'; #false
123   }
124 }
125
126 =item label
127
128 Returns the IP address.
129
130 =cut
131
132 sub label {
133   my $self = shift;
134   $self->ip_addr;
135 }
136
137 =item insert [ , OPTION => VALUE ... ]
138
139 Adds this record to the database.  If there is an error, returns the error,
140 otherwise returns false.
141
142 The additional fields pkgnum and svcpart (see FS::cust_svc) should be 
143 defined.  An FS::cust_svc record will be created and inserted.
144
145 Currently available options are: I<depend_jobnum>
146
147 If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
148 jobnums), all provisioning jobs will have a dependancy on the supplied
149 jobnum(s) (they will not run until the specific job(s) complete(s)).
150
151 =cut
152
153 # Standard FS::svc_Common::insert
154
155 =item delete
156
157 Delete this record from the database.
158
159 =cut
160
161 # Standard FS::svc_Common::delete
162
163 =item replace OLD_RECORD
164
165 Replaces the OLD_RECORD with this one in the database.  If there is an error,
166 returns the error, otherwise returns false.
167
168 =cut
169
170 # Standard FS::svc_Common::replace
171
172 =item suspend
173
174 Called by the suspend method of FS::cust_pkg (see FS::cust_pkg).
175
176 =item unsuspend
177
178 Called by the unsuspend method of FS::cust_pkg (see FS::cust_pkg).
179
180 =item cancel
181
182 Called by the cancel method of FS::cust_pkg (see FS::cust_pkg).
183
184 =item check
185
186 Checks all fields to make sure this is a valid broadband service.  If there is
187 an error, returns the error, otherwise returns false.  Called by the insert
188 and replace methods.
189
190 =cut
191
192 sub check {
193   my $self = shift;
194   my $x = $self->setfixed;
195
196   return $x unless ref($x);
197
198   my $error =
199     $self->ut_numbern('svcnum')
200     || $self->ut_foreign_key('blocknum', 'addr_block', 'blocknum')
201     || $self->ut_textn('description')
202     || $self->ut_number('speed_up')
203     || $self->ut_number('speed_down')
204     || $self->ut_ipn('ip_addr')
205     || $self->ut_hexn('mac_addr')
206     || $self->ut_hexn('auth_key')
207     || $self->ut_coordn('latitude', -90, 90)
208     || $self->ut_coordn('longitude', -180, 180)
209     || $self->ut_sfloatn('altitude')
210     || $self->ut_textn('vlan_profile')
211   ;
212   return $error if $error;
213
214   if($self->speed_up < 0) { return 'speed_up must be positive'; }
215   if($self->speed_down < 0) { return 'speed_down must be positive'; }
216
217   if (not($self->ip_addr) or $self->ip_addr eq '0.0.0.0') {
218     my $next_addr = $self->addr_block->next_free_addr;
219     if ($next_addr) {
220       $self->ip_addr($next_addr->addr);
221     } else {
222       return "No free addresses in addr_block (blocknum: ".$self->blocknum.")";
223     }
224   }
225
226   # This should catch errors in the ip_addr.  If it doesn't,
227   # they'll almost certainly not map into the block anyway.
228   my $self_addr = $self->NetAddr; #netmask is /32
229   return ('Cannot parse address: ' . $self->ip_addr) unless $self_addr;
230
231   my $block_addr = $self->addr_block->NetAddr;
232   unless ($block_addr->contains($self_addr)) {
233     return 'blocknum '.$self->blocknum.' does not contain address '.$self->ip_addr;
234   }
235
236   my $router = $self->addr_block->router 
237     or return 'Cannot assign address from unallocated block:'.$self->addr_block->blocknum;
238   if(grep { $_->routernum == $router->routernum} $self->allowed_routers) {
239   } # do nothing
240   else {
241     return 'Router '.$router->routernum.' cannot provide svcpart '.$self->svcpart;
242   }
243
244   $self->SUPER::check;
245 }
246
247 =item NetAddr
248
249 Returns a NetAddr::IP object containing the IP address of this service.  The netmask 
250 is /32.
251
252 =cut
253
254 sub NetAddr {
255   my $self = shift;
256   return new NetAddr::IP ($self->ip_addr);
257 }
258
259 =item addr_block
260
261 Returns the FS::addr_block record (i.e. the address block) for this broadband service.
262
263 =cut
264
265 sub addr_block {
266   my $self = shift;
267
268   return qsearchs('addr_block', { blocknum => $self->blocknum });
269 }
270
271 =back
272
273 =item allowed_routers
274
275 Returns a list of allowed FS::router objects.
276
277 =cut
278
279 sub allowed_routers {
280   my $self = shift;
281
282   return map { $_->router } qsearch('part_svc_router', { svcpart => $self->svcpart });
283 }
284
285 =head1 BUGS
286
287 The business with sb_field has been 'fixed', in a manner of speaking.
288
289 =head1 SEE ALSO
290
291 FS::svc_Common, FS::Record, FS::addr_block,
292 FS::part_svc, schema.html from the base documentation.
293
294 =cut
295
296 1;
297