CDR matching by svcnum, RT#10044
[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', #optional,
85     'longname_plural' => 'PBXs', #optional
86     'sorts' => 'svcnum', # optional sort field (or arrayref of sort fields, main first)
87     'display_weight' => 70,
88     'cancel_weight'  => 90,
89     'fields' => {
90       'id'    => 'ID',
91       'title' => 'Name',
92       'max_extensions' => 'Maximum number of User Extensions',
93       'max_simultaneous' => 'Maximum number of simultaneous users',
94 #      'field'         => 'Description',
95 #      'another_field' => { 
96 #                           'label'     => 'Description',
97 #                          'def_label' => 'Description for service definitions',
98 #                          'type'      => 'text',
99 #                          'disable_default'   => 1, #disable switches
100 #                          'disable_fixed'     => 1, #
101 #                          'disable_inventory' => 1, #
102 #                        },
103 #      'foreign_key'   => { 
104 #                           'label'        => 'Description',
105 #                          'def_label'    => 'Description for service defs',
106 #                          'type'         => 'select',
107 #                          'select_table' => 'foreign_table',
108 #                          'select_key'   => 'key_field_in_table',
109 #                          'select_label' => 'label_field_in_table',
110 #                        },
111
112     },
113   };
114 }
115
116 =item search_sql STRING
117
118 Class method which returns an SQL fragment to search for the given string.
119
120 =cut
121
122 #XXX
123 #or something more complicated if necessary
124 #sub search_sql {
125 #  my($class, $string) = @_;
126 #  $class->search_sql_field('title', $string);
127 #}
128
129 =item label
130
131 Returns the title field for this PBX tenant.
132
133 =cut
134
135 sub label {
136   my $self = shift;
137   $self->title;
138 }
139
140 =item insert
141
142 Adds this record to the database.  If there is an error, returns the error,
143 otherwise returns false.
144
145 The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be 
146 defined.  An FS::cust_svc record will be created and inserted.
147
148 =cut
149
150 sub insert {
151   my $self = shift;
152   my $error;
153
154   $error = $self->SUPER::insert;
155   return $error if $error;
156
157   '';
158 }
159
160 =item delete
161
162 Delete this record from the database.
163
164 =cut
165
166 sub delete {
167   my $self = shift;
168
169   local $SIG{HUP} = 'IGNORE';
170   local $SIG{INT} = 'IGNORE';
171   local $SIG{QUIT} = 'IGNORE';
172   local $SIG{TERM} = 'IGNORE';
173   local $SIG{TSTP} = 'IGNORE';
174   local $SIG{PIPE} = 'IGNORE';
175
176   my $oldAutoCommit = $FS::UID::AutoCommit;
177   local $FS::UID::AutoCommit = 0;
178   my $dbh = dbh;
179
180   foreach my $svc_phone (qsearch('svc_phone', { 'pbxsvc' => $self->svcnum } )) {
181     $svc_phone->pbxsvc('');
182     my $error = $svc_phone->replace;
183     if ( $error ) {
184       $dbh->rollback if $oldAutoCommit;
185       return $error;
186     }
187   }
188
189   foreach my $svc_acct  (qsearch('svc_acct',  { 'pbxsvc' => $self->svcnum } )) {
190     my $error = $svc_acct->delete;
191     if ( $error ) {
192       $dbh->rollback if $oldAutoCommit;
193       return $error;
194     }
195   }
196
197   my $error = $self->SUPER::delete;
198   if ( $error ) {
199     $dbh->rollback if $oldAutoCommit;
200     return $error;
201   }
202
203   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
204   '';
205 }
206
207
208 =item replace OLD_RECORD
209
210 Replaces the OLD_RECORD with this one in the database.  If there is an error,
211 returns the error, otherwise returns false.
212
213 =cut
214
215 #sub replace {
216 #  my ( $new, $old ) = ( shift, shift );
217 #  my $error;
218 #
219 #  $error = $new->SUPER::replace($old);
220 #  return $error if $error;
221 #
222 #  '';
223 #}
224
225 =item suspend
226
227 Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
228
229 =item unsuspend
230
231 Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
232
233 =item cancel
234
235 Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
236
237 =item check
238
239 Checks all fields to make sure this is a valid PBX tenant.  If there is
240 an error, returns the error, otherwise returns false.  Called by the insert
241 and repalce methods.
242
243 =cut
244
245 sub check {
246   my $self = shift;
247
248   my $x = $self->setfixed;
249   return $x unless ref($x);
250   my $part_svc = $x;
251
252
253   $self->SUPER::check;
254 }
255
256 #XXX this is a way-too simplistic implementation
257 # at the very least, title should be unique across exports that need that or
258 # controlled by a conf setting or something
259 sub _check_duplicate {
260   my $self = shift;
261
262   my $conf = new FS::Conf;
263   return '' if $conf->config('global_unique-pbx_title') eq 'disabled';
264
265   $self->lock_table;
266
267   if ( qsearchs( 'svc_pbx', { 'title' => $self->title } ) ) {
268     return "Name in use";
269   } else {
270     return '';
271   }
272 }
273
274 =item get_cdrs
275
276 Returns a set of Call Detail Records (see L<FS::cdr>) associated with this 
277 service.  By default, "associated with" means that the "charged_party" field of
278 the CDR matches the "title" field of the service.
279
280 =over 2
281
282 Accepts the following options:
283
284 =item for_update => 1: SELECT the CDRs "FOR UPDATE".
285
286 =item status => "" (or "done"): Return only CDRs with that processing status.
287
288 =item inbound => 1: No-op for svc_pbx CDR processing.
289
290 =item default_prefix => "XXX": Also accept the phone number of the service prepended 
291 with the chosen prefix.
292
293 =item disable_src => 1: No-op for svc_pbx CDR processing.
294
295 =item by_svcnum => 1: Select CDRs where the svcnum field matches, instead of 
296 title/charged_party.  Normally this field is set after processing.
297
298 =back
299
300 =cut
301
302 sub get_cdrs {
303   my($self, %options) = @_;
304   my %hash = ();
305   my @where = ();
306
307   my @fields = ( 'charged_party' );
308   $hash{'freesidestatus'} = $options{'status'}
309     if exists($options{'status'});
310   
311   my $for_update = $options{'for_update'} ? 'FOR UPDATE' : '';
312
313   if ( $options{'by_svcnum'} ) {
314     $hash{'svcnum'} = $self->svcnum;
315   }
316   else {
317     #matching by title
318     my $title = $self->title;
319
320     my $prefix = $options{'default_prefix'};
321
322     my @orwhere =  map " $_ = '$title'        ", @fields;
323     push @orwhere, map " $_ = '$prefix$title' ", @fields
324       if length($prefix);
325     if ( $prefix =~ /^\+(\d+)$/ ) {
326       push @orwhere, map " $_ = '$1$title' ", @fields
327     }
328
329     push @where, ' ( '. join(' OR ', @orwhere ). ' ) ';
330   }
331
332   if ( $options{'begin'} ) {
333     push @where, 'startdate >= '. $options{'begin'};
334   }
335   if ( $options{'end'} ) {
336     push @where, 'startdate < '.  $options{'end'};
337   }
338
339   my $extra_sql = ( keys(%hash) ? ' AND ' : ' WHERE ' ). join(' AND ', @where )
340     if @where;
341
342   my @cdrs =
343     qsearch( {
344       'table'      => 'cdr',
345       'hashref'    => \%hash,
346       'extra_sql'  => $extra_sql,
347       'order_by'   => "ORDER BY startdate $for_update",
348     } );
349
350   @cdrs;
351 }
352
353 =back
354
355 =head1 BUGS
356
357 =head1 SEE ALSO
358
359 L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>,
360 L<FS::cust_pkg>, schema.html from the base documentation.
361
362 =cut
363
364 1;
365