1 package FS::svc_Common;
4 use vars qw( @ISA $noexport_hack $DEBUG $me );
5 use Carp qw( cluck carp croak ); #specify cluck have to specify them all..
6 use FS::Record qw( qsearch qsearchs fields dbh );
7 use FS::cust_main_Mixin;
12 use FS::inventory_item;
13 use FS::inventory_class;
15 @ISA = qw( FS::cust_main_Mixin FS::Record );
17 $me = '[FS::svc_Common]';
22 FS::svc_Common - Object method for all svc_ records
28 @ISA = qw( FS::svc_Common );
32 FS::svc_Common is intended as a base class for table-specific classes to
33 inherit from, i.e. FS::svc_acct. FS::svc_Common inherits from FS::Record.
39 =item search_sql_field FIELD STRING
41 Class method which returns an SQL fragment to search for STRING in FIELD.
43 It is now case-insensitive by default.
47 sub search_sql_field {
48 my( $class, $field, $string ) = @_;
49 my $table = $class->table;
50 my $q_string = dbh->quote($string);
51 "lc($table.$field) = lc($q_string)";
54 #fallback for services that don't provide a search...
56 #my( $class, $string ) = @_;
66 my $class = ref($proto) || $proto;
68 bless ($self, $class);
70 unless ( defined ( $self->table ) ) {
71 $self->{'Table'} = shift;
72 carp "warning: FS::Record::new called with table name ". $self->{'Table'};
75 #$self->{'Hash'} = shift;
77 $self->{'Hash'} = { map { $_ => $newhash->{$_} } qw(svcnum svcpart) };
79 $self->setdefault( $self->_fieldhandlers )
82 $self->{'Hash'}{$_} = $newhash->{$_}
83 foreach grep { defined($newhash->{$_}) && length($newhash->{$_}) }
86 foreach my $field ( grep !defined($self->{'Hash'}{$_}), $self->fields ) {
87 $self->{'Hash'}{$field}='';
90 $self->_rebless if $self->can('_rebless');
92 $self->{'modified'} = 0;
94 $self->_cache($self->{'Hash'}, shift) if $self->can('_cache') && @_;
100 sub _fieldhandlers { {}; }
104 # This restricts the fields based on part_svc_column and the svcpart of
105 # the service. There are four possible cases:
106 # 1. svcpart passed as part of the svc_x hash.
107 # 2. svcpart fetched via cust_svc based on svcnum.
108 # 3. No svcnum or svcpart. In this case, return ALL the fields with
109 # dbtable eq $self->table.
110 # 4. Called via "fields('svc_acct')" or something similar. In this case
111 # there is no $self object.
115 my @vfields = $self->SUPER::virtual_fields;
117 return @vfields unless (ref $self); # Case 4
119 if ($self->svcpart) { # Case 1
120 $svcpart = $self->svcpart;
121 } elsif ( $self->svcnum
122 && qsearchs('cust_svc',{'svcnum'=>$self->svcnum} )
124 $svcpart = $self->cust_svc->svcpart;
129 if ($svcpart) { #Cases 1 and 2
130 my %flags = map { $_->columnname, $_->columnflag } (
131 qsearch ('part_svc_column', { svcpart => $svcpart } )
133 return grep { not ( defined($flags{$_}) && $flags{$_} eq 'X') } @vfields;
142 svc_Common provides a fallback label subroutine that just returns the svcnum.
148 cluck "warning: ". ref($self). " not loaded or missing label method; ".
155 Checks the validity of fields in this record.
157 At present, this does nothing but call FS::Record::check (which, in turn,
158 does nothing but run virtual field checks).
167 =item insert [ , OPTION => VALUE ... ]
169 Adds this record to the database. If there is an error, returns the error,
170 otherwise returns false.
172 The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
173 defined. An FS::cust_svc record will be created and inserted.
175 Currently available options are: I<jobnums>, I<child_objects> and
178 If I<jobnum> is set to an array reference, the jobnums of any export jobs will
179 be added to the referenced array.
181 If I<child_objects> is set to an array reference of FS::tablename objects (for
182 example, FS::acct_snarf objects), they will have their svcnum field set and
183 will be inserted after this record, but before any exports are run. Each
184 element of the array can also optionally be a two-element array reference
185 containing the child object and the name of an alternate field to be filled in
186 with the newly-inserted svcnum, for example C<[ $svc_forward, 'srcsvc' ]>
188 If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
189 jobnums), all provisioning jobs will have a dependancy on the supplied
190 jobnum(s) (they will not run until the specific job(s) complete(s)).
197 warn "[$me] insert called with options ".
198 join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
202 local $FS::queue::jobnums = \@jobnums;
203 warn "[$me] insert: set \$FS::queue::jobnums to $FS::queue::jobnums\n"
205 my $objects = $options{'child_objects'} || [];
206 my $depend_jobnums = $options{'depend_jobnum'} || [];
207 $depend_jobnums = [ $depend_jobnums ] unless ref($depend_jobnums);
209 local $SIG{HUP} = 'IGNORE';
210 local $SIG{INT} = 'IGNORE';
211 local $SIG{QUIT} = 'IGNORE';
212 local $SIG{TERM} = 'IGNORE';
213 local $SIG{TSTP} = 'IGNORE';
214 local $SIG{PIPE} = 'IGNORE';
216 my $oldAutoCommit = $FS::UID::AutoCommit;
217 local $FS::UID::AutoCommit = 0;
220 my $svcnum = $self->svcnum;
221 my $cust_svc = $svcnum ? qsearchs('cust_svc',{'svcnum'=>$self->svcnum}) : '';
222 #unless ( $svcnum ) {
223 if ( !$svcnum or !$cust_svc ) {
224 $cust_svc = new FS::cust_svc ( {
225 #hua?# 'svcnum' => $svcnum,
226 'svcnum' => $self->svcnum,
227 'pkgnum' => $self->pkgnum,
228 'svcpart' => $self->svcpart,
230 my $error = $cust_svc->insert;
232 $dbh->rollback if $oldAutoCommit;
235 $svcnum = $self->svcnum($cust_svc->svcnum);
237 #$cust_svc = qsearchs('cust_svc',{'svcnum'=>$self->svcnum});
238 unless ( $cust_svc ) {
239 $dbh->rollback if $oldAutoCommit;
240 return "no cust_svc record found for svcnum ". $self->svcnum;
242 $self->pkgnum($cust_svc->pkgnum);
243 $self->svcpart($cust_svc->svcpart);
246 my $error = $self->set_auto_inventory
248 || $self->SUPER::insert;
250 $dbh->rollback if $oldAutoCommit;
254 foreach my $object ( @$objects ) {
256 if ( ref($object) eq 'ARRAY' ) {
257 ($obj, $field) = @$object;
262 $obj->$field($self->svcnum);
263 $error = $obj->insert;
265 $dbh->rollback if $oldAutoCommit;
271 unless ( $noexport_hack ) {
273 warn "[$me] insert: \$FS::queue::jobnums is $FS::queue::jobnums\n"
276 foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
277 my $error = $part_export->export_insert($self);
279 $dbh->rollback if $oldAutoCommit;
280 return "exporting to ". $part_export->exporttype.
281 " (transaction rolled back): $error";
285 foreach my $depend_jobnum ( @$depend_jobnums ) {
286 warn "[$me] inserting dependancies on supplied job $depend_jobnum\n"
288 foreach my $jobnum ( @jobnums ) {
289 my $queue = qsearchs('queue', { 'jobnum' => $jobnum } );
290 warn "[$me] inserting dependancy for job $jobnum on $depend_jobnum\n"
292 my $error = $queue->depend_insert($depend_jobnum);
294 $dbh->rollback if $oldAutoCommit;
295 return "error queuing job dependancy: $error";
302 if ( exists $options{'jobnums'} ) {
303 push @{ $options{'jobnums'} }, @jobnums;
306 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
313 Deletes this account from the database. If there is an error, returns the
314 error, otherwise returns false.
316 The corresponding FS::cust_svc record will be deleted as well.
324 local $SIG{HUP} = 'IGNORE';
325 local $SIG{INT} = 'IGNORE';
326 local $SIG{QUIT} = 'IGNORE';
327 local $SIG{TERM} = 'IGNORE';
328 local $SIG{TSTP} = 'IGNORE';
329 local $SIG{PIPE} = 'IGNORE';
331 my $oldAutoCommit = $FS::UID::AutoCommit;
332 local $FS::UID::AutoCommit = 0;
335 $error = $self->SUPER::delete
336 || $self->export('delete')
337 || $self->return_inventory
338 || $self->cust_svc->delete
341 $dbh->rollback if $oldAutoCommit;
345 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
350 =item replace OLD_RECORD
352 Replaces OLD_RECORD with this one. If there is an error, returns the error,
353 otherwise returns false.
358 my ($new, $old) = (shift, shift);
360 local $SIG{HUP} = 'IGNORE';
361 local $SIG{INT} = 'IGNORE';
362 local $SIG{QUIT} = 'IGNORE';
363 local $SIG{TERM} = 'IGNORE';
364 local $SIG{TSTP} = 'IGNORE';
365 local $SIG{PIPE} = 'IGNORE';
367 my $oldAutoCommit = $FS::UID::AutoCommit;
368 local $FS::UID::AutoCommit = 0;
371 # We absolutely have to have an old vs. new record to make this work.
372 $old = $new->replace_old unless defined($old);
374 my $error = $new->set_auto_inventory;
376 $dbh->rollback if $oldAutoCommit;
380 $error = $new->SUPER::replace($old);
382 $dbh->rollback if $oldAutoCommit;
387 unless ( $noexport_hack ) {
389 #not quite false laziness, but same pattern as FS::svc_acct::replace and
390 #FS::part_export::sqlradius::_export_replace. List::Compare or something
391 #would be useful but too much of a pain in the ass to deploy
393 my @old_part_export = $old->cust_svc->part_svc->part_export;
394 my %old_exportnum = map { $_->exportnum => 1 } @old_part_export;
395 my @new_part_export =
397 ? qsearchs('part_svc', { svcpart=>$new->svcpart } )->part_export
398 : $new->cust_svc->part_svc->part_export;
399 my %new_exportnum = map { $_->exportnum => 1 } @new_part_export;
401 foreach my $delete_part_export (
402 grep { ! $new_exportnum{$_->exportnum} } @old_part_export
404 my $error = $delete_part_export->export_delete($old);
406 $dbh->rollback if $oldAutoCommit;
407 return "error deleting, export to ". $delete_part_export->exporttype.
408 " (transaction rolled back): $error";
412 foreach my $replace_part_export (
413 grep { $old_exportnum{$_->exportnum} } @new_part_export
415 my $error = $replace_part_export->export_replace($new,$old);
417 $dbh->rollback if $oldAutoCommit;
418 return "error exporting to ". $replace_part_export->exporttype.
419 " (transaction rolled back): $error";
423 foreach my $insert_part_export (
424 grep { ! $old_exportnum{$_->exportnum} } @new_part_export
426 my $error = $insert_part_export->export_insert($new);
428 $dbh->rollback if $oldAutoCommit;
429 return "error inserting export to ". $insert_part_export->exporttype.
430 " (transaction rolled back): $error";
436 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
443 Sets any fixed fields for this service (see L<FS::part_svc>). If there is an
444 error, returns the error, otherwise returns the FS::part_svc object (use ref()
445 to test the return). Usually called by the check method.
451 $self->setx('F', @_);
456 Sets all fields to their defaults (see L<FS::part_svc>), overriding their
457 current values. If there is an error, returns the error, otherwise returns
458 the FS::part_svc object (use ref() to test the return).
464 $self->setx('D', @_ );
467 =item set_default_and_fixed
471 sub set_default_and_fixed {
473 $self->setx( [ 'D', 'F' ], @_ );
476 =item setx FLAG | FLAG_ARRAYREF , [ CALLBACK_HASHREF ]
478 Sets fields according to the passed in flag or arrayref of flags.
480 Optionally, a hashref of field names and callback coderefs can be passed.
481 If a coderef exists for a given field name, instead of setting the field,
482 the coderef is called with the column value (part_svc_column.columnvalue)
483 as the single parameter.
490 my @x = ref($x) ? @$x : ($x);
491 my $coderef = scalar(@_) ? shift : $self->_fieldhandlers;
494 $self->ut_numbern('svcnum')
496 return $error if $error;
498 my $part_svc = $self->part_svc;
499 return "Unkonwn svcpart" unless $part_svc;
501 #set default/fixed/whatever fields from part_svc
503 foreach my $part_svc_column (
504 grep { my $f = $_->columnflag; grep { $f eq $_ } @x } #columnflag in @x
505 $part_svc->all_part_svc_column
508 my $columnname = $part_svc_column->columnname;
509 my $columnvalue = $part_svc_column->columnvalue;
511 $columnvalue = &{ $coderef->{$columnname} }( $self, $columnvalue )
512 if exists( $coderef->{$columnname} );
513 $self->setfield( $columnname, $columnvalue );
526 if ( $self->get('svcpart') ) {
527 $svcpart = $self->get('svcpart');
528 } elsif ( $self->svcnum && qsearchs('cust_svc', {'svcnum'=>$self->svcnum}) ) {
529 my $cust_svc = $self->cust_svc;
530 return "Unknown svcnum" unless $cust_svc;
531 $svcpart = $cust_svc->svcpart;
534 qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
538 =item set_auto_inventory
540 Sets any fields which auto-populate from inventory (see L<FS::part_svc>).
541 If there is an error, returns the error, otherwise returns false.
545 sub set_auto_inventory {
549 $self->ut_numbern('svcnum')
551 return $error if $error;
553 my $part_svc = $self->part_svc;
554 return "Unkonwn svcpart" unless $part_svc;
556 local $SIG{HUP} = 'IGNORE';
557 local $SIG{INT} = 'IGNORE';
558 local $SIG{QUIT} = 'IGNORE';
559 local $SIG{TERM} = 'IGNORE';
560 local $SIG{TSTP} = 'IGNORE';
561 local $SIG{PIPE} = 'IGNORE';
563 my $oldAutoCommit = $FS::UID::AutoCommit;
564 local $FS::UID::AutoCommit = 0;
567 #set default/fixed/whatever fields from part_svc
568 my $table = $self->table;
569 foreach my $field ( grep { $_ ne 'svcnum' } $self->fields ) {
570 my $part_svc_column = $part_svc->part_svc_column($field);
571 if ( $part_svc_column->columnflag eq 'A' && $self->$field() eq '' ) {
573 my $classnum = $part_svc_column->columnvalue;
574 my $inventory_item = qsearchs({
575 'table' => 'inventory_item',
576 'hashref' => { 'classnum' => $classnum,
579 'extra_sql' => 'LIMIT 1 FOR UPDATE',
582 unless ( $inventory_item ) {
583 $dbh->rollback if $oldAutoCommit;
584 my $inventory_class =
585 qsearchs('inventory_class', { 'classnum' => $classnum } );
586 return "Can't find inventory_class.classnum $classnum"
587 unless $inventory_class;
588 return "Out of ". $inventory_class->classname. "s\n"; #Lingua:: BS
592 $inventory_item->svcnum( $self->svcnum );
593 my $ierror = $inventory_item->replace();
595 $dbh->rollback if $oldAutoCommit;
596 return "Error provisioning inventory: $ierror";
600 $self->setfield( $field, $inventory_item->item );
605 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
611 =item return_inventory
615 sub return_inventory {
618 local $SIG{HUP} = 'IGNORE';
619 local $SIG{INT} = 'IGNORE';
620 local $SIG{QUIT} = 'IGNORE';
621 local $SIG{TERM} = 'IGNORE';
622 local $SIG{TSTP} = 'IGNORE';
623 local $SIG{PIPE} = 'IGNORE';
625 my $oldAutoCommit = $FS::UID::AutoCommit;
626 local $FS::UID::AutoCommit = 0;
629 foreach my $inventory_item ( $self->inventory_item ) {
630 $inventory_item->svcnum('');
631 my $error = $inventory_item->replace();
633 $dbh->rollback if $oldAutoCommit;
634 return "Error returning inventory: $error";
638 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
645 Returns the inventory items associated with this svc_ record, as
646 FS::inventory_item objects (see L<FS::inventory_item>.
653 'table' => 'inventory_item',
654 'hashref' => { 'svcnum' => $self->svcnum, },
660 Returns the cust_svc record associated with this svc_ record, as a FS::cust_svc
661 object (see L<FS::cust_svc>).
667 qsearchs('cust_svc', { 'svcnum' => $self->svcnum } );
672 Runs export_suspend callbacks.
678 $self->export('suspend');
683 Runs export_unsuspend callbacks.
689 $self->export('unsuspend');
694 Runs export_links callbacks and returns the links.
701 $self->export('links', $return);
705 =item export HOOK [ EXPORT_ARGS ]
707 Runs the provided export hook (i.e. "suspend", "unsuspend") for this service.
712 my( $self, $method ) = ( shift, shift );
714 $method = "export_$method" unless $method =~ /^export_/;
716 local $SIG{HUP} = 'IGNORE';
717 local $SIG{INT} = 'IGNORE';
718 local $SIG{QUIT} = 'IGNORE';
719 local $SIG{TERM} = 'IGNORE';
720 local $SIG{TSTP} = 'IGNORE';
721 local $SIG{PIPE} = 'IGNORE';
723 my $oldAutoCommit = $FS::UID::AutoCommit;
724 local $FS::UID::AutoCommit = 0;
728 unless ( $noexport_hack ) {
729 foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
730 next unless $part_export->can($method);
731 my $error = $part_export->$method($self, @_);
733 $dbh->rollback if $oldAutoCommit;
734 return "error exporting $method event to ". $part_export->exporttype.
735 " (transaction rolled back): $error";
740 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
747 Sets or retrieves overlimit date.
753 $self->cust_svc->overlimit(@_);
758 Stub - returns false (no error) so derived classes don't need to define this
759 methods. Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
761 This method is called *before* the deletion step which actually deletes the
762 services. This method should therefore only be used for "pre-deletion"
763 cancellation steps, if necessary.
769 =item clone_suspended
771 Constructor used by FS::part_export::_export_suspend fallback. Stub returning
772 same object for svc_ classes which don't implement a suspension fallback
773 (everything except svc_acct at the moment). Document better.
777 sub clone_suspended {
781 =item clone_kludge_unsuspend
783 Constructor used by FS::part_export::_export_unsuspend fallback. Stub returning
784 same object for svc_ classes which don't implement a suspension fallback
785 (everything except svc_acct at the moment). Document better.
789 sub clone_kludge_unsuspend {
797 The setfixed method return value.
799 B<export> method isn't used by insert and replace methods yet.
803 L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>, schema.html
804 from the base documentation.