show credit balance on invoices, #11564
[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 sub _check_duplicate {
257   my $self = shift;
258
259   my $conf = new FS::Conf;
260   
261   $self->lock_table;
262
263   foreach my $field ('title', 'id') {
264     my $global_unique = $conf->config("global_unique-pbx_$field");
265     # can be 'disabled', 'enabled', or empty.
266     # if empty, check per exports; if not empty or disabled, check 
267     # globally.
268     next if $global_unique eq 'disabled';
269     my @dup = $self->find_duplicates(
270       ($global_unique ? 'global' : 'export') , $field
271     );
272     next if !@dup;
273     return "duplicate $field '".$self->getfield($field).
274            "': conflicts with svcnum ".$dup[0]->svcnum;
275   }
276   return '';
277 }
278
279 =item get_cdrs
280
281 Returns a set of Call Detail Records (see L<FS::cdr>) associated with this 
282 service.  By default, "associated with" means that the "charged_party" field of
283 the CDR matches the "title" field of the service.
284
285 =over 2
286
287 Accepts the following options:
288
289 =item for_update => 1: SELECT the CDRs "FOR UPDATE".
290
291 =item status => "" (or "done"): Return only CDRs with that processing status.
292
293 =item inbound => 1: No-op for svc_pbx CDR processing.
294
295 =item default_prefix => "XXX": Also accept the phone number of the service prepended 
296 with the chosen prefix.
297
298 =item disable_src => 1: No-op for svc_pbx CDR processing.
299
300 =item by_svcnum => 1: Select CDRs where the svcnum field matches, instead of 
301 title/charged_party.  Normally this field is set after processing.
302
303 =back
304
305 =cut
306
307 sub get_cdrs {
308   my($self, %options) = @_;
309   my %hash = ();
310   my @where = ();
311
312   my @fields = ( 'charged_party' );
313   $hash{'freesidestatus'} = $options{'status'}
314     if exists($options{'status'});
315   
316   my $for_update = $options{'for_update'} ? 'FOR UPDATE' : '';
317
318   if ( $options{'by_svcnum'} ) {
319     $hash{'svcnum'} = $self->svcnum;
320   }
321   else {
322     #matching by title
323     my $title = $self->title;
324
325     my $prefix = $options{'default_prefix'};
326
327     my @orwhere =  map " $_ = '$title'        ", @fields;
328     push @orwhere, map " $_ = '$prefix$title' ", @fields
329       if length($prefix);
330     if ( $prefix =~ /^\+(\d+)$/ ) {
331       push @orwhere, map " $_ = '$1$title' ", @fields
332     }
333
334     push @where, ' ( '. join(' OR ', @orwhere ). ' ) ';
335   }
336
337   if ( $options{'begin'} ) {
338     push @where, 'startdate >= '. $options{'begin'};
339   }
340   if ( $options{'end'} ) {
341     push @where, 'startdate < '.  $options{'end'};
342   }
343
344   my $extra_sql = ( keys(%hash) ? ' AND ' : ' WHERE ' ). join(' AND ', @where )
345     if @where;
346
347   my @cdrs =
348     qsearch( {
349       'table'      => 'cdr',
350       'hashref'    => \%hash,
351       'extra_sql'  => $extra_sql,
352       'order_by'   => "ORDER BY startdate $for_update",
353     } );
354
355   @cdrs;
356 }
357
358 =back
359
360 =head1 BUGS
361
362 =head1 SEE ALSO
363
364 L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>,
365 L<FS::cust_pkg>, schema.html from the base documentation.
366
367 =cut
368
369 1;
370