merging RT 4.0.6
[freeside.git] / FS / FS / svc_pbx.pm
1 package FS::svc_pbx;
2
3 use strict;
4 use base qw( FS::svc_External_Common );
5 use FS::Record qw( qsearch qsearchs dbh );
6 use FS::PagedSearch qw( psearch );
7 use FS::Conf;
8 use FS::cust_svc;
9 use FS::svc_phone;
10 use FS::svc_acct;
11
12 =head1 NAME
13
14 FS::svc_pbx - Object methods for svc_pbx records
15
16 =head1 SYNOPSIS
17
18   use FS::svc_pbx;
19
20   $record = new FS::svc_pbx \%hash;
21   $record = new FS::svc_pbx { 'column' => 'value' };
22
23   $error = $record->insert;
24
25   $error = $new_record->replace($old_record);
26
27   $error = $record->delete;
28
29   $error = $record->check;
30
31   $error = $record->suspend;
32
33   $error = $record->unsuspend;
34
35   $error = $record->cancel;
36
37 =head1 DESCRIPTION
38
39 An FS::svc_pbx object represents a PBX tenant.  FS::svc_pbx inherits from
40 FS::svc_Common.  The following fields are currently supported:
41
42 =over 4
43
44 =item svcnum
45
46 Primary key (assigned automatcially for new accounts)
47
48 =item id
49
50 (Unique?) number of external record
51
52 =item title
53
54 PBX name
55
56 =item max_extensions
57
58 Maximum number of extensions
59
60 =item max_simultaneous
61
62 Maximum number of simultaneous users
63
64 =back
65
66 =head1 METHODS
67
68 =over 4
69
70 =item new HASHREF
71
72 Creates a new PBX tenant.  To add the PBX tenant to the database, see
73 L<"insert">.
74
75 Note that this stores the hash reference, not a distinct copy of the hash it
76 points to.  You can ask the object for a copy with the I<hash> method.
77
78 =cut
79
80 sub table { 'svc_pbx'; }
81
82 sub table_info {
83   {
84     'name' => 'PBX',
85     'name_plural' => 'PBXs',
86     'lcname_plural' => 'PBXs',
87     'longname_plural' => 'PBXs',
88     'sorts' => 'svcnum', # optional sort field (or arrayref of sort fields, main first)
89     'display_weight' => 70,
90     'cancel_weight'  => 90,
91     'fields' => {
92       'id'    => 'ID',
93       'title' => 'Name',
94       'max_extensions' => 'Maximum number of User Extensions',
95       'max_simultaneous' => 'Maximum number of simultaneous users',
96     },
97   };
98 }
99
100 =item search_sql STRING
101
102 Class method which returns an SQL fragment to search for the given string.
103
104 =cut
105
106 #XXX
107 #or something more complicated if necessary
108 #sub search_sql {
109 #  my($class, $string) = @_;
110 #  $class->search_sql_field('title', $string);
111 #}
112
113 =item label
114
115 Returns the title field for this PBX tenant.
116
117 =cut
118
119 sub label {
120   my $self = shift;
121   $self->title;
122 }
123
124 =item insert
125
126 Adds this record to the database.  If there is an error, returns the error,
127 otherwise returns false.
128
129 The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be 
130 defined.  An FS::cust_svc record will be created and inserted.
131
132 =cut
133
134 sub insert {
135   my $self = shift;
136   my $error;
137
138   $error = $self->SUPER::insert;
139   return $error if $error;
140
141   '';
142 }
143
144 =item delete
145
146 Delete this record from the database.
147
148 =cut
149
150 sub delete {
151   my $self = shift;
152
153   local $SIG{HUP} = 'IGNORE';
154   local $SIG{INT} = 'IGNORE';
155   local $SIG{QUIT} = 'IGNORE';
156   local $SIG{TERM} = 'IGNORE';
157   local $SIG{TSTP} = 'IGNORE';
158   local $SIG{PIPE} = 'IGNORE';
159
160   my $oldAutoCommit = $FS::UID::AutoCommit;
161   local $FS::UID::AutoCommit = 0;
162   my $dbh = dbh;
163
164   foreach my $svc_phone (qsearch('svc_phone', { 'pbxsvc' => $self->svcnum } )) {
165     $svc_phone->pbxsvc('');
166     my $error = $svc_phone->replace;
167     if ( $error ) {
168       $dbh->rollback if $oldAutoCommit;
169       return $error;
170     }
171   }
172
173   foreach my $svc_acct  (qsearch('svc_acct',  { 'pbxsvc' => $self->svcnum } )) {
174     my $error = $svc_acct->delete;
175     if ( $error ) {
176       $dbh->rollback if $oldAutoCommit;
177       return $error;
178     }
179   }
180
181   my $error = $self->SUPER::delete;
182   if ( $error ) {
183     $dbh->rollback if $oldAutoCommit;
184     return $error;
185   }
186
187   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
188   '';
189 }
190
191
192 =item replace OLD_RECORD
193
194 Replaces the OLD_RECORD with this one in the database.  If there is an error,
195 returns the error, otherwise returns false.
196
197 =cut
198
199 #sub replace {
200 #  my ( $new, $old ) = ( shift, shift );
201 #  my $error;
202 #
203 #  $error = $new->SUPER::replace($old);
204 #  return $error if $error;
205 #
206 #  '';
207 #}
208
209 =item suspend
210
211 Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
212
213 =item unsuspend
214
215 Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
216
217 =item cancel
218
219 Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
220
221 =item check
222
223 Checks all fields to make sure this is a valid PBX tenant.  If there is
224 an error, returns the error, otherwise returns false.  Called by the insert
225 and repalce methods.
226
227 =cut
228
229 sub check {
230   my $self = shift;
231
232   my $x = $self->setfixed;
233   return $x unless ref($x);
234   my $part_svc = $x;
235
236
237   $self->SUPER::check;
238 }
239
240 sub _check_duplicate {
241   my $self = shift;
242
243   my $conf = new FS::Conf;
244   
245   $self->lock_table;
246
247   foreach my $field ('title', 'id') {
248     my $global_unique = $conf->config("global_unique-pbx_$field");
249     # can be 'disabled', 'enabled', or empty.
250     # if empty, check per exports; if not empty or disabled, check 
251     # globally.
252     next if $global_unique eq 'disabled';
253     my @dup = $self->find_duplicates(
254       ($global_unique ? 'global' : 'export') , $field
255     );
256     next if !@dup;
257     return "duplicate $field '".$self->getfield($field).
258            "': conflicts with svcnum ".$dup[0]->svcnum;
259   }
260   return '';
261 }
262
263 =item psearch_cdrs OPTIONS
264
265 Returns a paged search (L<FS::PagedSearch>) for Call Detail Records 
266 associated with this service.  By default, "associated with" means that 
267 the "charged_party" field of the CDR matches the "title" field of the 
268 service.  To access the CDRs themselves, call "->fetch" on the resulting
269 object.
270
271 =over 2
272
273 Accepts the following options:
274
275 =item for_update => 1: SELECT the CDRs "FOR UPDATE".
276
277 =item status => "" (or "done"): Return only CDRs with that processing status.
278
279 =item inbound => 1: No-op for svc_pbx CDR processing.
280
281 =item default_prefix => "XXX": Also accept the phone number of the service prepended 
282 with the chosen prefix.
283
284 =item disable_src => 1: No-op for svc_pbx CDR processing.
285
286 =item by_svcnum => 1: Select CDRs where the svcnum field matches, instead of 
287 title/charged_party.  Normally this field is set after processing.
288
289 =item by_ip_addr => 'src' or 'dst': Select CDRs where the src_ip_addr or 
290 dst_ip_addr field matches title.  In this case, some special logic is applied
291 to allow title to indicate a range of IP addresses.
292
293 =item begin, end: Start and end of date range, as unix timestamp.
294
295 =item cdrtypenum: Only return CDRs with this type number.
296
297 =back
298
299 =cut
300
301 sub psearch_cdrs {
302   my($self, %options) = @_;
303   my %hash = ();
304   my @where = ();
305
306   my @fields = ( 'charged_party' );
307   $hash{'freesidestatus'} = $options{'status'}
308     if exists($options{'status'});
309
310   if ($options{'cdrtypenum'}) {
311     $hash{'cdrtypenum'} = $options{'cdrtypenum'};
312   }
313
314   my $for_update = $options{'for_update'} ? 'FOR UPDATE' : '';
315
316   if ( $options{'by_svcnum'} ) {
317     $hash{'svcnum'} = $self->svcnum;
318   }
319   elsif ( $options{'by_ip_addr'} =~ /^src|dst$/) {
320     my $field = 'cdr.'.$options{'by_ip_addr'}.'_ip_addr';
321     push @where, FS::cdr->ip_addr_sql($field, $self->title);
322   }
323   else {
324     #matching by title
325     my $title = $self->title;
326
327     my $prefix = $options{'default_prefix'};
328
329     my @orwhere =  map " $_ = '$title'        ", @fields;
330     push @orwhere, map " $_ = '$prefix$title' ", @fields
331       if length($prefix);
332     if ( $prefix =~ /^\+(\d+)$/ ) {
333       push @orwhere, map " $_ = '$1$title' ", @fields
334     }
335
336     push @where, ' ( '. join(' OR ', @orwhere ). ' ) ';
337   }
338
339   if ( $options{'begin'} ) {
340     push @where, 'startdate >= '. $options{'begin'};
341   }
342   if ( $options{'end'} ) {
343     push @where, 'startdate < '.  $options{'end'};
344   }
345
346   my $extra_sql = ( keys(%hash) ? ' AND ' : ' WHERE ' ). join(' AND ', @where )
347     if @where;
348
349   psearch( {
350       'table'      => 'cdr',
351       'hashref'    => \%hash,
352       'extra_sql'  => $extra_sql,
353       'order_by'   => "ORDER BY startdate $for_update",
354   } );
355 }
356
357 =item get_cdrs (DEPRECATED)
358
359 Like psearch_cdrs, but returns all the L<FS::cdr> objects at once, in a 
360 single list.  Arguments are the same as for psearch_cdrs.  This can take
361 an unreasonably large amount of memory and is best avoided.
362
363 =cut
364
365 sub get_cdrs {
366   my $self = shift;
367   my $psearch = $self->psearch_cdrs($_);
368   qsearch ( $psearch->{query} )
369 }
370
371 =back
372
373 =head1 BUGS
374
375 =head1 SEE ALSO
376
377 L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>,
378 L<FS::cust_pkg>, schema.html from the base documentation.
379
380 =cut
381
382 1;
383