assign entire address blocks to services for RADIUS Framed-Route option, #20742
[freeside.git] / FS / FS / router.pm
1 package FS::router;
2
3 use strict;
4 use vars qw( @ISA );
5 use FS::Record qw( qsearchs qsearch dbh );
6 use FS::addr_block;
7
8 @ISA = qw( FS::Record FS::m2m_Common );
9
10 =head1 NAME
11
12 FS::router - Object methods for router records
13
14 =head1 SYNOPSIS
15
16   use FS::router;
17
18   $record = new FS::router \%hash;
19   $record = new FS::router { 'column' => 'value' };
20
21   $error = $record->insert;
22
23   $error = $new_record->replace($old_record);
24
25   $error = $record->delete;
26
27   $error = $record->check;
28
29 =head1 DESCRIPTION
30
31 An FS::router record describes a broadband router, such as a DSLAM or a wireless
32  access point.  FS::router inherits from FS::Record.  The following 
33 fields are currently supported:
34
35 =over 4
36
37 =item routernum - primary key
38
39 =item routername - descriptive name for the router
40
41 =item svcnum - svcnum of the owning FS::svc_broadband, if appropriate
42
43 =item manual_addr - set to 'Y' to allow services linked to this router 
44 to have any IP address, rather than one in an address block belonging 
45 to the router.
46
47 =back
48
49 =head1 METHODS
50
51 =over 4
52
53 =item new HASHREF
54
55 Create a new record.  To add the record to the database, see "insert".
56
57 =cut
58
59 sub table { 'router'; }
60
61 =item insert
62
63 Adds this record to the database.  If there is an error, returns the error,
64 otherwise returns false.
65
66 If the pseudo-field 'blocknum' is set to an L<FS::addr_block> number, then 
67 that address block will be assigned to this router.  Currently only one
68 block can be assigned this way.
69
70 =cut
71
72 sub insert {
73   my $oldAutoCommit = $FS::UID::AutoCommit;
74   local $FS::UID::AutoCommit = 0;
75   my $dbh = dbh;
76
77   my $self = shift;
78   my $error = $self->SUPER::insert(@_);
79   return $error if $error;
80   if ( $self->blocknum ) {
81     my $block = FS::addr_block->by_key($self->blocknum);
82     if ($block) {
83       if ($block->routernum) {
84         $error = "block ".$block->cidr." is already assigned to a router";
85       } else {
86         $block->set('routernum', $self->routernum);
87         $block->set('manual_flag', 'Y');
88         $error = $block->replace;
89       }
90     } else {
91       $error = "blocknum ".$self->blocknum." not found";
92     }
93     if ( $error ) {
94       $dbh->rollback if $oldAutoCommit;
95       return $error;
96     }
97   }
98   $dbh->commit if $oldAutoCommit;
99   return $error;
100 }
101
102 =item replace OLD_RECORD
103
104 Replaces OLD_RECORD with this one in the database.  If there is an error,
105 returns the error, otherwise returns false.
106
107 =cut
108
109 sub replace {
110   my $oldAutoCommit = $FS::UID::AutoCommit;
111   local $FS::UID::AutoCommit = 0;
112   my $dbh = dbh;
113
114   my $self = shift;
115   my $old = shift || $self->replace_old;
116   my $error = $self->SUPER::replace($old, @_);
117   return $error if $error;
118
119   if ( defined($self->blocknum) ) {
120     #warn "FS::router::replace: blocknum = ".$self->blocknum."\n";
121     # then release any blocks we're already holding
122     foreach my $block ($self->addr_block) {
123       $block->set('routernum', 0);
124       $block->set('manual_flag', '');
125       $error ||= $block->replace;
126     }
127     if ( !$error and $self->blocknum > 0 ) {
128       # and, if the new blocknum is a real blocknum, assign it
129       my $block = FS::addr_block->by_key($self->blocknum);
130       if ( $block ) {
131         $block->set('routernum', $self->routernum);
132         $block->set('manual_flag', '');
133         $error ||= $block->replace;
134       } else {
135         $error = "blocknum ".$self->blocknum." not found";
136       }
137     }
138     if ( $error ) {
139       $dbh->rollback if $oldAutoCommit;
140       return $error;
141     }
142   }
143   $dbh->commit if $oldAutoCommit;
144   return $error;
145 }
146
147 =item check
148
149 Checks all fields to make sure this is a valid record.  If there is an error,
150 returns the error, otherwise returns false.  Called by the insert and replace
151 methods.
152
153 =cut
154
155 sub check {
156   my $self = shift;
157
158   my $error =
159     $self->ut_numbern('routernum')
160     || $self->ut_text('routername')
161     || $self->ut_enum('manual_addr', [ '', 'Y' ])
162     || $self->ut_agentnum_acl('agentnum', 'Broadband global configuration')
163     || $self->ut_foreign_keyn('svcnum', 'cust_svc', 'svcnum')
164   ;
165   return $error if $error;
166
167   $self->SUPER::check;
168 }
169
170 =item delete
171
172 Deallocate all address blocks from this router and delete it.
173
174 =cut
175
176 sub delete {
177     my $self = shift;
178
179     my $oldAutoCommit = $FS::UID::AutoCommit;
180     local $FS::UID::AutoCommit = 0;
181     my $dbh = dbh;
182  
183     my $error;
184     foreach my $block ($self->addr_block) {
185       $block->set('manual_flag', '');
186       $block->set('routernum', 0);
187       $error ||= $block->replace;
188     }
189
190     $error ||= $self->SUPER::delete;
191     if ( $error ) {
192        $dbh->rollback if $oldAutoCommit;
193        return $error;
194     }
195   
196     $dbh->commit or die $dbh->errstr if $oldAutoCommit;
197     '';
198 }
199
200 =item addr_block
201
202 Returns a list of FS::addr_block objects (address blocks) associated
203 with this object.
204
205 =item auto_addr_block
206
207 Returns a list of address blocks on which auto-assignment of IP addresses
208 is enabled.
209
210 =cut
211
212 sub addr_block {
213   my $self = shift;
214   return qsearch('addr_block', { routernum => $self->routernum });
215 }
216
217 sub auto_addr_block {
218   my $self = shift;
219   return () if $self->manual_addr;
220   return qsearch('addr_block', { routernum => $self->routernum,
221                                  manual_flag => '' });
222 }
223
224 =item part_svc_router
225
226 Returns a list of FS::part_svc_router objects associated with this 
227 object.  This is unlikely to be useful for any purpose other than retrieving 
228 the associated FS::part_svc objects.  See below.
229
230 =cut
231
232 sub part_svc_router {
233   my $self = shift;
234   return qsearch('part_svc_router', { routernum => $self->routernum });
235 }
236
237 =item part_svc
238
239 Returns a list of FS::part_svc objects associated with this object.
240
241 =cut
242
243 sub part_svc {
244   my $self = shift;
245   return map { qsearchs('part_svc', { svcpart => $_->svcpart }) }
246       $self->part_svc_router;
247 }
248
249 =item agent
250
251 Returns the agent associated with this router, if any.
252
253 =cut
254
255 sub agent {
256   qsearchs('agent', { 'agentnum' => shift->agentnum });
257 }
258
259 =item cust_svc
260
261 Returns the cust_svc associated with this router, if any.  This should be
262 the service that I<provides connectivity to the router>, not any service 
263 connected I<through> the router.
264
265 =cut
266
267 sub cust_svc {
268   my $svcnum = shift->svcnum or return undef;
269   FS::cust_svc->by_key($svcnum);
270 }
271
272 =back
273
274 =head1 SEE ALSO
275
276 FS::svc_broadband, FS::router, FS::addr_block, FS::part_svc,
277 schema.html from the base documentation.
278
279 =cut
280
281 1;
282