RT# 74435 - added check, to make sure batch format can handle refunds
[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 ( length($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 =cut
206
207 sub addr_block {
208   my $self = shift;
209   qsearch('addr_block', { routernum => $self->routernum });
210 }
211
212 =item auto_addr_block
213
214 Returns a list of address blocks on which auto-assignment of IP addresses
215 is enabled.
216
217 =cut
218
219 sub auto_addr_block {
220   my $self = shift;
221   return () if $self->manual_addr;
222   return qsearch('addr_block', { routernum => $self->routernum,
223                                  manual_flag => '' });
224 }
225
226 =item part_svc_router
227
228 Returns a list of FS::part_svc_router objects associated with this 
229 object.  This is unlikely to be useful for any purpose other than retrieving 
230 the associated FS::part_svc objects.  See below.
231
232 =cut
233
234 sub part_svc_router {
235   my $self = shift;
236   return qsearch('part_svc_router', { routernum => $self->routernum });
237 }
238
239 =item part_svc
240
241 Returns a list of FS::part_svc objects associated with this object.
242
243 =cut
244
245 sub part_svc {
246   my $self = shift;
247   return map { qsearchs('part_svc', { svcpart => $_->svcpart }) }
248       $self->part_svc_router;
249 }
250
251 =item agent
252
253 Returns the agent associated with this router, if any.
254
255 =cut
256
257 sub agent {
258   qsearchs('agent', { 'agentnum' => shift->agentnum });
259 }
260
261 =item cust_svc
262
263 Returns the cust_svc associated with this router, if any.  This should be
264 the service that I<provides connectivity to the router>, not any service 
265 connected I<through> the router.
266
267 =cut
268
269 sub cust_svc {
270   my $svcnum = shift->svcnum or return undef;
271   FS::cust_svc->by_key($svcnum);
272 }
273
274 =back
275
276 =head1 SEE ALSO
277
278 FS::svc_broadband, FS::router, FS::addr_block, FS::part_svc,
279 schema.html from the base documentation.
280
281 =cut
282
283 1;
284