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