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