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