45f6c360168b88e6c9fbdbeb67d5c186daa14334
[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 { 'svc_broadband'; }
89
90 =item insert
91
92 Adds this record to the database.  If there is an error, returns the error,
93 otherwise returns false.
94
95 The additional fields pkgnum and svcpart (see FS::cust_svc) should be 
96 defined.  An FS::cust_svc record will be created and inserted.
97
98 =cut
99
100 # Standard FS::svc_Common::insert
101
102 =item delete
103
104 Delete this record from the database.
105
106 =cut
107
108 # Standard FS::svc_Common::delete
109
110 =item replace OLD_RECORD
111
112 Replaces the OLD_RECORD with this one in the database.  If there is an error,
113 returns the error, otherwise returns false.
114
115 =cut
116
117 # Standard FS::svc_Common::replace
118
119 =item sb_field
120
121 Returns a list of FS::sb_field objects assigned to this object.
122
123 =cut
124
125 sub sb_field {
126   my $self = shift;
127
128   return qsearch( 'sb_field', { svcnum => $self->svcnum } );
129 }
130
131 =item sb_field_hashref
132
133 Returns a hashref of the FS::sb_field key/value pairs for this object.
134
135 Deprecated.  Please don't use it.
136
137 =cut
138
139 # Kristian wrote this, but don't hold it against him.  He was under a powerful
140 # distracting influence whom he evidently found much more interesting than
141 # svc_broadband.pm.  I can't say I blame him.
142
143 sub sb_field_hashref {
144   my $self = shift;
145   my $svcpart = shift;
146
147   if ((not $svcpart) && ($self->cust_svc)) {
148     $svcpart = $self->cust_svc->svcpart;
149   }
150
151   my $hashref = {};
152
153   map {
154     my $sb_field = qsearchs('sb_field', { sbfieldpart => $_->sbfieldpart,
155                                           svcnum => $self->svcnum });
156     $hashref->{$_->getfield('name')} = $sb_field ? $sb_field->getfield('value') : '';
157   } qsearch('part_sb_field', { svcpart => $svcpart });
158
159   return $hashref;
160
161 }
162
163 =item suspend
164
165 Called by the suspend method of FS::cust_pkg (see FS::cust_pkg).
166
167 =item unsuspend
168
169 Called by the unsuspend method of FS::cust_pkg (see FS::cust_pkg).
170
171 =item cancel
172
173 Called by the cancel method of FS::cust_pkg (see FS::cust_pkg).
174
175 =item check
176
177 Checks all fields to make sure this is a valid broadband service.  If there is
178 an error, returns the error, otherwise returns false.  Called by the insert
179 and replace methods.
180
181 =cut
182
183 sub check {
184   my $self = shift;
185   my $x = $self->setfixed;
186
187   return $x unless ref($x);
188
189   my $error =
190     $self->ut_numbern('svcnum')
191     || $self->ut_foreign_key('blocknum', 'addr_block', 'blocknum')
192     || $self->ut_number('speed_up')
193     || $self->ut_number('speed_down')
194     || $self->ut_ipn('ip_addr')
195   ;
196   return $error if $error;
197
198   if($self->speed_up < 0) { return 'speed_up must be positive'; }
199   if($self->speed_down < 0) { return 'speed_down must be positive'; }
200
201   if (not($self->ip_addr) or $self->ip_addr eq '0.0.0.0') {
202     $self->ip_addr($self->addr_block->next_free_addr->addr);
203     if (not $self->ip_addr) {
204       return "No free addresses in addr_block (blocknum: ".$self->blocknum.")";
205     }
206   }
207
208   # This should catch errors in the ip_addr.  If it doesn't,
209   # they'll almost certainly not map into the block anyway.
210   my $self_addr = $self->NetAddr; #netmask is /32
211   return ('Cannot parse address: ' . $self->ip_addr) unless $self_addr;
212
213   my $block_addr = $self->addr_block->NetAddr;
214   unless ($block_addr->contains($self_addr)) {
215     return 'blocknum '.$self->blocknum.' does not contain address '.$self->ip_addr;
216   }
217
218   my $router = $self->addr_block->router 
219     or return 'Cannot assign address from unallocated block:'.$self->addr_block->blocknum;
220   if(grep { $_->routernum == $router->routernum} $self->allowed_routers) {
221   } # do nothing
222   else {
223     return 'Router '.$router->routernum.' cannot provide svcpart '.$self->svcpart;
224   }
225
226
227   ''; #no error
228 }
229
230 =item NetAddr
231
232 Returns a NetAddr::IP object containing the IP address of this service.  The netmask 
233 is /32.
234
235 =cut
236
237 sub NetAddr {
238   my $self = shift;
239   return new NetAddr::IP ($self->ip_addr);
240 }
241
242 =item addr_block
243
244 Returns the FS::addr_block record (i.e. the address block) for this broadband service.
245
246 =cut
247
248 sub addr_block {
249   my $self = shift;
250
251   return qsearchs('addr_block', { blocknum => $self->blocknum });
252 }
253
254 =back
255
256 =item allowed_routers
257
258 Returns a list of allowed FS::router objects.
259
260 =cut
261
262 sub allowed_routers {
263   my $self = shift;
264
265   return map { $_->router } qsearch('part_svc_router', { svcpart => $self->svcpart });
266 }
267
268 =head1 BUGS
269
270 I think there's one place in the code where we actually use sb_field_hashref.
271 That's a bug in itself.
272
273 The real problem with it is that we're still grappling with the question of how
274 tightly xfields should be integrated with real fields.  There are a few
275 different directions we could go with it--we I<could> override several
276 functions in Record so that xfields behave almost exactly like real fields (can
277 be set with setfield(), appear in fields() and hash(), used as criteria in
278 qsearch(), etc.).
279
280 =head1 SEE ALSO
281
282 FS::svc_Common, FS::Record, FS::addr_block, FS::sb_field,
283 FS::part_svc, schema.html from the base documentation.
284
285 =cut
286
287 1;
288