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