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