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