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