1 package FS::svc_Common;
4 use vars qw( @ISA $noexport_hack $DEBUG $me
5 $overlimit_missing_cust_svc_nonfatal_kludge );
6 use Carp qw( cluck carp croak confess ); #specify cluck have to specify them all
7 use Scalar::Util qw( blessed );
8 use FS::Record qw( qsearch qsearchs fields dbh );
9 use FS::cust_main_Mixin;
14 use FS::inventory_item;
15 use FS::inventory_class;
17 @ISA = qw( FS::cust_main_Mixin FS::Record );
19 $me = '[FS::svc_Common]';
22 $overlimit_missing_cust_svc_nonfatal_kludge = 0;
26 FS::svc_Common - Object method for all svc_ records
32 @ISA = qw( FS::svc_Common );
36 FS::svc_Common is intended as a base class for table-specific classes to
37 inherit from, i.e. FS::svc_acct. FS::svc_Common inherits from FS::Record.
43 =item search_sql_field FIELD STRING
45 Class method which returns an SQL fragment to search for STRING in FIELD.
47 It is now case-insensitive by default.
51 sub search_sql_field {
52 my( $class, $field, $string ) = @_;
53 my $table = $class->table;
54 my $q_string = dbh->quote($string);
55 "LOWER($table.$field) = LOWER($q_string)";
58 #fallback for services that don't provide a search...
60 #my( $class, $string ) = @_;
70 my $class = ref($proto) || $proto;
72 bless ($self, $class);
74 unless ( defined ( $self->table ) ) {
75 $self->{'Table'} = shift;
76 carp "warning: FS::Record::new called with table name ". $self->{'Table'};
79 #$self->{'Hash'} = shift;
81 $self->{'Hash'} = { map { $_ => $newhash->{$_} } qw(svcnum svcpart) };
83 $self->setdefault( $self->_fieldhandlers )
86 $self->{'Hash'}{$_} = $newhash->{$_}
87 foreach grep { defined($newhash->{$_}) && length($newhash->{$_}) }
90 foreach my $field ( grep !defined($self->{'Hash'}{$_}), $self->fields ) {
91 $self->{'Hash'}{$field}='';
94 $self->_rebless if $self->can('_rebless');
96 $self->{'modified'} = 0;
98 $self->_cache($self->{'Hash'}, shift) if $self->can('_cache') && @_;
104 sub _fieldhandlers { {}; }
108 # This restricts the fields based on part_svc_column and the svcpart of
109 # the service. There are four possible cases:
110 # 1. svcpart passed as part of the svc_x hash.
111 # 2. svcpart fetched via cust_svc based on svcnum.
112 # 3. No svcnum or svcpart. In this case, return ALL the fields with
113 # dbtable eq $self->table.
114 # 4. Called via "fields('svc_acct')" or something similar. In this case
115 # there is no $self object.
119 my @vfields = $self->SUPER::virtual_fields;
121 return @vfields unless (ref $self); # Case 4
123 if ($self->svcpart) { # Case 1
124 $svcpart = $self->svcpart;
125 } elsif ( $self->svcnum
126 && qsearchs('cust_svc',{'svcnum'=>$self->svcnum} )
128 $svcpart = $self->cust_svc->svcpart;
133 if ($svcpart) { #Cases 1 and 2
134 my %flags = map { $_->columnname, $_->columnflag } (
135 qsearch ('part_svc_column', { svcpart => $svcpart } )
137 return grep { not ( defined($flags{$_}) && $flags{$_} eq 'X') } @vfields;
146 svc_Common provides a fallback label subroutine that just returns the svcnum.
152 cluck "warning: ". ref($self). " not loaded or missing label method; ".
164 Checks the validity of fields in this record.
166 At present, this does nothing but call FS::Record::check (which, in turn,
167 does nothing but run virtual field checks).
176 =item insert [ , OPTION => VALUE ... ]
178 Adds this record to the database. If there is an error, returns the error,
179 otherwise returns false.
181 The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
182 defined. An FS::cust_svc record will be created and inserted.
184 Currently available options are: I<jobnums>, I<child_objects> and
187 If I<jobnum> is set to an array reference, the jobnums of any export jobs will
188 be added to the referenced array.
190 If I<child_objects> is set to an array reference of FS::tablename objects (for
191 example, FS::acct_snarf objects), they will have their svcnum field set and
192 will be inserted after this record, but before any exports are run. Each
193 element of the array can also optionally be a two-element array reference
194 containing the child object and the name of an alternate field to be filled in
195 with the newly-inserted svcnum, for example C<[ $svc_forward, 'srcsvc' ]>
197 If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
198 jobnums), all provisioning jobs will have a dependancy on the supplied
199 jobnum(s) (they will not run until the specific job(s) complete(s)).
201 If I<export_args> is set to an array reference, the referenced list will be
202 passed to export commands.
209 warn "[$me] insert called with options ".
210 join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
214 local $FS::queue::jobnums = \@jobnums;
215 warn "[$me] insert: set \$FS::queue::jobnums to $FS::queue::jobnums\n"
217 my $objects = $options{'child_objects'} || [];
218 my $depend_jobnums = $options{'depend_jobnum'} || [];
219 $depend_jobnums = [ $depend_jobnums ] unless ref($depend_jobnums);
221 local $SIG{HUP} = 'IGNORE';
222 local $SIG{INT} = 'IGNORE';
223 local $SIG{QUIT} = 'IGNORE';
224 local $SIG{TERM} = 'IGNORE';
225 local $SIG{TSTP} = 'IGNORE';
226 local $SIG{PIPE} = 'IGNORE';
228 my $oldAutoCommit = $FS::UID::AutoCommit;
229 local $FS::UID::AutoCommit = 0;
232 my $svcnum = $self->svcnum;
233 my $cust_svc = $svcnum ? qsearchs('cust_svc',{'svcnum'=>$self->svcnum}) : '';
234 #unless ( $svcnum ) {
235 if ( !$svcnum or !$cust_svc ) {
236 $cust_svc = new FS::cust_svc ( {
237 #hua?# 'svcnum' => $svcnum,
238 'svcnum' => $self->svcnum,
239 'pkgnum' => $self->pkgnum,
240 'svcpart' => $self->svcpart,
242 my $error = $cust_svc->insert;
244 $dbh->rollback if $oldAutoCommit;
247 $svcnum = $self->svcnum($cust_svc->svcnum);
249 #$cust_svc = qsearchs('cust_svc',{'svcnum'=>$self->svcnum});
250 unless ( $cust_svc ) {
251 $dbh->rollback if $oldAutoCommit;
252 return "no cust_svc record found for svcnum ". $self->svcnum;
254 $self->pkgnum($cust_svc->pkgnum);
255 $self->svcpart($cust_svc->svcpart);
258 my $error = $self->preinsert_hook_first
259 || $self->set_auto_inventory
261 || $self->_check_duplicate
262 || $self->preinsert_hook
263 || $self->SUPER::insert;
265 $dbh->rollback if $oldAutoCommit;
269 foreach my $object ( @$objects ) {
271 if ( ref($object) eq 'ARRAY' ) {
272 ($obj, $field) = @$object;
277 $obj->$field($self->svcnum);
278 $error = $obj->insert;
280 $dbh->rollback if $oldAutoCommit;
286 unless ( $noexport_hack ) {
288 warn "[$me] insert: \$FS::queue::jobnums is $FS::queue::jobnums\n"
291 my $export_args = $options{'export_args'} || [];
293 foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
294 my $error = $part_export->export_insert($self, @$export_args);
296 $dbh->rollback if $oldAutoCommit;
297 return "exporting to ". $part_export->exporttype.
298 " (transaction rolled back): $error";
302 foreach my $depend_jobnum ( @$depend_jobnums ) {
303 warn "[$me] inserting dependancies on supplied job $depend_jobnum\n"
305 foreach my $jobnum ( @jobnums ) {
306 my $queue = qsearchs('queue', { 'jobnum' => $jobnum } );
307 warn "[$me] inserting dependancy for job $jobnum on $depend_jobnum\n"
309 my $error = $queue->depend_insert($depend_jobnum);
311 $dbh->rollback if $oldAutoCommit;
312 return "error queuing job dependancy: $error";
319 if ( exists $options{'jobnums'} ) {
320 push @{ $options{'jobnums'} }, @jobnums;
323 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
329 sub preinsert_hook_first { ''; }
330 sub _check_duplcate { ''; }
331 sub preinsert_hook { ''; }
332 sub table_dupcheck_fields { (); }
334 =item delete [ , OPTION => VALUE ... ]
336 Deletes this account from the database. If there is an error, returns the
337 error, otherwise returns false.
339 The corresponding FS::cust_svc record will be deleted as well.
346 my $export_args = $options{'export_args'} || [];
348 local $SIG{HUP} = 'IGNORE';
349 local $SIG{INT} = 'IGNORE';
350 local $SIG{QUIT} = 'IGNORE';
351 local $SIG{TERM} = 'IGNORE';
352 local $SIG{TSTP} = 'IGNORE';
353 local $SIG{PIPE} = 'IGNORE';
355 my $oldAutoCommit = $FS::UID::AutoCommit;
356 local $FS::UID::AutoCommit = 0;
359 my $error = $self->SUPER::delete
360 || $self->export('delete', @$export_args)
361 || $self->return_inventory
362 || $self->cust_svc->delete
365 $dbh->rollback if $oldAutoCommit;
369 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
374 =item replace [ OLD_RECORD ] [ HASHREF | OPTION => VALUE ]
376 Replaces OLD_RECORD with this one. If there is an error, returns the error,
377 otherwise returns false.
379 Currently available options are: I<export_args> and I<depend_jobnum>.
381 If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
382 jobnums), all provisioning jobs will have a dependancy on the supplied
383 jobnum(s) (they will not run until the specific job(s) complete(s)).
385 If I<export_args> is set to an array reference, the referenced list will be
386 passed to export commands.
393 my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
398 ( ref($_[0]) eq 'HASH' )
403 local $FS::queue::jobnums = \@jobnums;
404 warn "[$me] replace: set \$FS::queue::jobnums to $FS::queue::jobnums\n"
406 my $depend_jobnums = $options->{'depend_jobnum'} || [];
407 $depend_jobnums = [ $depend_jobnums ] unless ref($depend_jobnums);
409 local $SIG{HUP} = 'IGNORE';
410 local $SIG{INT} = 'IGNORE';
411 local $SIG{QUIT} = 'IGNORE';
412 local $SIG{TERM} = 'IGNORE';
413 local $SIG{TSTP} = 'IGNORE';
414 local $SIG{PIPE} = 'IGNORE';
416 my $oldAutoCommit = $FS::UID::AutoCommit;
417 local $FS::UID::AutoCommit = 0;
420 my $error = $new->set_auto_inventory($old);
422 $dbh->rollback if $oldAutoCommit;
426 #redundant, but so any duplicate fields are maniuplated as appropriate
427 # (svc_phone.phonenum)
428 $error = $new->check;
430 $dbh->rollback if $oldAutoCommit;
434 #if ( $old->username ne $new->username || $old->domsvc != $new->domsvc ) {
435 if ( grep { $old->$_ ne $new->$_ } $new->table_dupcheck_fields ) {
437 $new->svcpart( $new->cust_svc->svcpart ) unless $new->svcpart;
438 $error = $new->_check_duplicate;
440 $dbh->rollback if $oldAutoCommit;
445 $error = $new->SUPER::replace($old);
447 $dbh->rollback if $oldAutoCommit;
452 unless ( $noexport_hack ) {
454 warn "[$me] replace: \$FS::queue::jobnums is $FS::queue::jobnums\n"
457 my $export_args = $options->{'export_args'} || [];
459 #not quite false laziness, but same pattern as FS::svc_acct::replace and
460 #FS::part_export::sqlradius::_export_replace. List::Compare or something
461 #would be useful but too much of a pain in the ass to deploy
463 my @old_part_export = $old->cust_svc->part_svc->part_export;
464 my %old_exportnum = map { $_->exportnum => 1 } @old_part_export;
465 my @new_part_export =
467 ? qsearchs('part_svc', { svcpart=>$new->svcpart } )->part_export
468 : $new->cust_svc->part_svc->part_export;
469 my %new_exportnum = map { $_->exportnum => 1 } @new_part_export;
471 foreach my $delete_part_export (
472 grep { ! $new_exportnum{$_->exportnum} } @old_part_export
474 my $error = $delete_part_export->export_delete($old, @$export_args);
476 $dbh->rollback if $oldAutoCommit;
477 return "error deleting, export to ". $delete_part_export->exporttype.
478 " (transaction rolled back): $error";
482 foreach my $replace_part_export (
483 grep { $old_exportnum{$_->exportnum} } @new_part_export
486 $replace_part_export->export_replace( $new, $old, @$export_args);
488 $dbh->rollback if $oldAutoCommit;
489 return "error exporting to ". $replace_part_export->exporttype.
490 " (transaction rolled back): $error";
494 foreach my $insert_part_export (
495 grep { ! $old_exportnum{$_->exportnum} } @new_part_export
497 my $error = $insert_part_export->export_insert($new, @$export_args );
499 $dbh->rollback if $oldAutoCommit;
500 return "error inserting export to ". $insert_part_export->exporttype.
501 " (transaction rolled back): $error";
505 foreach my $depend_jobnum ( @$depend_jobnums ) {
506 warn "[$me] inserting dependancies on supplied job $depend_jobnum\n"
508 foreach my $jobnum ( @jobnums ) {
509 my $queue = qsearchs('queue', { 'jobnum' => $jobnum } );
510 warn "[$me] inserting dependancy for job $jobnum on $depend_jobnum\n"
512 my $error = $queue->depend_insert($depend_jobnum);
514 $dbh->rollback if $oldAutoCommit;
515 return "error queuing job dependancy: $error";
522 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
528 Sets any fixed fields for this service (see L<FS::part_svc>). If there is an
529 error, returns the error, otherwise returns the FS::part_svc object (use ref()
530 to test the return). Usually called by the check method.
536 $self->setx('F', @_);
541 Sets all fields to their defaults (see L<FS::part_svc>), overriding their
542 current values. If there is an error, returns the error, otherwise returns
543 the FS::part_svc object (use ref() to test the return).
549 $self->setx('D', @_ );
552 =item set_default_and_fixed
556 sub set_default_and_fixed {
558 $self->setx( [ 'D', 'F' ], @_ );
561 =item setx FLAG | FLAG_ARRAYREF , [ CALLBACK_HASHREF ]
563 Sets fields according to the passed in flag or arrayref of flags.
565 Optionally, a hashref of field names and callback coderefs can be passed.
566 If a coderef exists for a given field name, instead of setting the field,
567 the coderef is called with the column value (part_svc_column.columnvalue)
568 as the single parameter.
575 my @x = ref($x) ? @$x : ($x);
576 my $coderef = scalar(@_) ? shift : $self->_fieldhandlers;
579 $self->ut_numbern('svcnum')
581 return $error if $error;
583 my $part_svc = $self->part_svc;
584 return "Unknown svcpart" unless $part_svc;
586 #set default/fixed/whatever fields from part_svc
588 foreach my $part_svc_column (
589 grep { my $f = $_->columnflag; grep { $f eq $_ } @x } #columnflag in @x
590 $part_svc->all_part_svc_column
593 my $columnname = $part_svc_column->columnname;
594 my $columnvalue = $part_svc_column->columnvalue;
596 $columnvalue = &{ $coderef->{$columnname} }( $self, $columnvalue )
597 if exists( $coderef->{$columnname} );
598 $self->setfield( $columnname, $columnvalue );
611 if ( $self->get('svcpart') ) {
612 $svcpart = $self->get('svcpart');
613 } elsif ( $self->svcnum && qsearchs('cust_svc', {'svcnum'=>$self->svcnum}) ) {
614 my $cust_svc = $self->cust_svc;
615 return "Unknown svcnum" unless $cust_svc;
616 $svcpart = $cust_svc->svcpart;
619 qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
625 Returns the FS::svc_pbx record for this service, if any (see L<FS::svc_pbx>).
627 Only makes sense if the service has a pbxsvc field (currently, svc_phone and
632 # XXX FS::h_svc_{acct,phone} could have a history-aware svc_pbx override
636 return '' unless $self->pbxsvc;
637 qsearchs( 'svc_pbx', { 'svcnum' => $self->pbxsvc } );
642 Returns the title of the FS::svc_pbx record associated with this service, if
645 Only makes sense if the service has a pbxsvc field (currently, svc_phone and
652 my $svc_pbx = $self->svc_pbx or return '';
656 =item pbx_select_hash %OPTIONS
658 Can be called as an object method or a class method.
660 Returns a hash SVCNUM => TITLE ... representing the PBXes this customer
661 that may be associated with this service.
663 Currently available options are: I<pkgnum> I<svcpart>
665 Only makes sense if the service has a pbxsvc field (currently, svc_phone and
670 #false laziness w/svc_acct::domain_select_hash
671 sub pbx_select_hash {
672 my ($self, %options) = @_;
678 $part_svc = $self->part_svc;
679 $cust_pkg = $self->cust_svc->cust_pkg
683 $part_svc = qsearchs('part_svc', { 'svcpart' => $options{svcpart} })
684 if $options{'svcpart'};
686 $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $options{pkgnum} })
687 if $options{'pkgnum'};
689 if ($part_svc && ( $part_svc->part_svc_column('pbxsvc')->columnflag eq 'S'
690 || $part_svc->part_svc_column('pbxsvc')->columnflag eq 'F')) {
691 %pbxes = map { $_->svcnum => $_->title }
692 map { qsearchs('svc_pbx', { 'svcnum' => $_ }) }
693 split(',', $part_svc->part_svc_column('pbxsvc')->columnvalue);
694 } elsif ($cust_pkg) { # && !$conf->exists('svc_acct-alldomains') ) {
695 %pbxes = map { $_->svcnum => $_->title }
696 map { qsearchs('svc_pbx', { 'svcnum' => $_->svcnum }) }
697 map { qsearch('cust_svc', { 'pkgnum' => $_->pkgnum } ) }
698 qsearch('cust_pkg', { 'custnum' => $cust_pkg->custnum });
701 %pbxes = map { $_->svcnum => $_->title } qsearch('svc_pbx', {} );
704 if ($part_svc && $part_svc->part_svc_column('pbxsvc')->columnflag eq 'D') {
705 my $svc_pbx = qsearchs('svc_pbx',
706 { 'svcnum' => $part_svc->part_svc_column('pbxsvc')->columnvalue } );
708 $pbxes{$svc_pbx->svcnum} = $svc_pbx->title;
710 warn "unknown svc_pbx.svcnum for part_svc_column pbxsvc: ".
711 $part_svc->part_svc_column('pbxsvc')->columnvalue;
720 =item set_auto_inventory
722 Sets any fields which auto-populate from inventory (see L<FS::part_svc>), and
723 also check any manually populated inventory fields.
725 If there is an error, returns the error, otherwise returns false.
729 sub set_auto_inventory {
731 my $old = @_ ? shift : '';
734 $self->ut_numbern('svcnum')
736 return $error if $error;
738 my $part_svc = $self->part_svc;
739 return "Unkonwn svcpart" unless $part_svc;
741 local $SIG{HUP} = 'IGNORE';
742 local $SIG{INT} = 'IGNORE';
743 local $SIG{QUIT} = 'IGNORE';
744 local $SIG{TERM} = 'IGNORE';
745 local $SIG{TSTP} = 'IGNORE';
746 local $SIG{PIPE} = 'IGNORE';
748 my $oldAutoCommit = $FS::UID::AutoCommit;
749 local $FS::UID::AutoCommit = 0;
752 #set default/fixed/whatever fields from part_svc
753 my $table = $self->table;
754 foreach my $field ( grep { $_ ne 'svcnum' } $self->fields ) {
756 my $part_svc_column = $part_svc->part_svc_column($field);
757 my $columnflag = $part_svc_column->columnflag;
758 next unless $columnflag =~ /^[AM]$/;
760 next if $columnflag eq 'A' && $self->$field() ne '';
762 my $classnum = $part_svc_column->columnvalue;
763 my %hash = ( 'classnum' => $classnum );
765 if ( $columnflag eq 'A' && $self->$field() eq '' ) {
766 $hash{'svcnum'} = '';
767 } elsif ( $columnflag eq 'M' ) {
768 return "Select inventory item for $field" unless $self->getfield($field);
769 $hash{'item'} = $self->getfield($field);
772 my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql(
774 'table' => 'inventory_item',
777 my $inventory_item = qsearchs({
778 'table' => 'inventory_item',
780 'extra_sql' => "AND $agentnums_sql",
781 'order_by' => 'ORDER BY ( agentnum IS NULL ) '. #agent inventory first
782 ' LIMIT 1 FOR UPDATE',
785 unless ( $inventory_item ) {
786 $dbh->rollback if $oldAutoCommit;
787 my $inventory_class =
788 qsearchs('inventory_class', { 'classnum' => $classnum } );
789 return "Can't find inventory_class.classnum $classnum"
790 unless $inventory_class;
791 return "Out of ". $inventory_class->classname. "s\n"; #Lingua:: BS
795 next if $columnflag eq 'M' && $inventory_item->svcnum == $self->svcnum;
797 $self->setfield( $field, $inventory_item->item );
798 #if $columnflag eq 'A' && $self->$field() eq '';
800 $inventory_item->svcnum( $self->svcnum );
801 my $ierror = $inventory_item->replace();
803 $dbh->rollback if $oldAutoCommit;
804 return "Error provisioning inventory: $ierror";
807 if ( $old && $old->$field() && $old->$field() ne $self->$field() ) {
808 my $old_inv = qsearchs({
809 'table' => 'inventory_item',
810 'hashref' => { 'classnum' => $classnum,
811 'svcnum' => $old->svcnum,
812 'item' => $old->$field(),
816 $old_inv->svcnum('');
817 my $oerror = $old_inv->replace;
819 $dbh->rollback if $oldAutoCommit;
820 return "Error unprovisioning inventory: $oerror";
827 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
833 =item return_inventory
837 sub return_inventory {
840 local $SIG{HUP} = 'IGNORE';
841 local $SIG{INT} = 'IGNORE';
842 local $SIG{QUIT} = 'IGNORE';
843 local $SIG{TERM} = 'IGNORE';
844 local $SIG{TSTP} = 'IGNORE';
845 local $SIG{PIPE} = 'IGNORE';
847 my $oldAutoCommit = $FS::UID::AutoCommit;
848 local $FS::UID::AutoCommit = 0;
851 foreach my $inventory_item ( $self->inventory_item ) {
852 $inventory_item->svcnum('');
853 my $error = $inventory_item->replace();
855 $dbh->rollback if $oldAutoCommit;
856 return "Error returning inventory: $error";
860 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
867 Returns the inventory items associated with this svc_ record, as
868 FS::inventory_item objects (see L<FS::inventory_item>.
875 'table' => 'inventory_item',
876 'hashref' => { 'svcnum' => $self->svcnum, },
882 Returns the cust_svc record associated with this svc_ record, as a FS::cust_svc
883 object (see L<FS::cust_svc>).
889 qsearchs('cust_svc', { 'svcnum' => $self->svcnum } );
894 Runs export_suspend callbacks.
901 my $export_args = $options{'export_args'} || [];
902 $self->export('suspend', @$export_args);
907 Runs export_unsuspend callbacks.
914 my $export_args = $options{'export_args'} || [];
915 $self->export('unsuspend', @$export_args);
920 Runs export_links callbacks and returns the links.
927 $self->export('links', $return);
931 =item export_getsettings
933 Runs export_getsettings callbacks and returns the two hashrefs.
937 sub export_getsettings {
941 my $error = $self->export('getsettings', \%settings, \%defaults);
943 #XXX bubble this up better
944 warn "error running export_getsetings: $error";
947 ( \%settings, \%defaults );
950 =item export HOOK [ EXPORT_ARGS ]
952 Runs the provided export hook (i.e. "suspend", "unsuspend") for this service.
957 my( $self, $method ) = ( shift, shift );
959 $method = "export_$method" unless $method =~ /^export_/;
961 local $SIG{HUP} = 'IGNORE';
962 local $SIG{INT} = 'IGNORE';
963 local $SIG{QUIT} = 'IGNORE';
964 local $SIG{TERM} = 'IGNORE';
965 local $SIG{TSTP} = 'IGNORE';
966 local $SIG{PIPE} = 'IGNORE';
968 my $oldAutoCommit = $FS::UID::AutoCommit;
969 local $FS::UID::AutoCommit = 0;
973 unless ( $noexport_hack ) {
974 foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
975 next unless $part_export->can($method);
976 my $error = $part_export->$method($self, @_);
978 $dbh->rollback if $oldAutoCommit;
979 return "error exporting $method event to ". $part_export->exporttype.
980 " (transaction rolled back): $error";
985 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
992 Sets or retrieves overlimit date.
998 #$self->cust_svc->overlimit(@_);
999 my $cust_svc = $self->cust_svc;
1000 unless ( $cust_svc ) { #wtf?
1001 my $error = "$me overlimit: missing cust_svc record for svc_acct svcnum ".
1003 if ( $overlimit_missing_cust_svc_nonfatal_kludge ) {
1004 cluck "$error; continuing anyway as requested";
1010 $cust_svc->overlimit(@_);
1015 Stub - returns false (no error) so derived classes don't need to define this
1016 methods. Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
1018 This method is called *before* the deletion step which actually deletes the
1019 services. This method should therefore only be used for "pre-deletion"
1020 cancellation steps, if necessary.
1026 =item clone_suspended
1028 Constructor used by FS::part_export::_export_suspend fallback. Stub returning
1029 same object for svc_ classes which don't implement a suspension fallback
1030 (everything except svc_acct at the moment). Document better.
1034 sub clone_suspended {
1038 =item clone_kludge_unsuspend
1040 Constructor used by FS::part_export::_export_unsuspend fallback. Stub returning
1041 same object for svc_ classes which don't implement a suspension fallback
1042 (everything except svc_acct at the moment). Document better.
1046 sub clone_kludge_unsuspend {
1050 =item find_duplicates MODE FIELDS...
1052 Method used by _check_duplicate routines to find services with duplicate
1053 values in specified fields. Set MODE to 'global' to search across all
1054 services, or 'export' to limit to those that share one or more exports
1055 with this service. FIELDS is a list of field names; only services
1056 matching in all fields will be returned. Empty fields will be skipped.
1060 sub find_duplicates {
1065 my %search = map { $_ => $self->getfield($_) }
1066 grep { length($self->getfield($_)) } @fields;
1067 return () if !%search;
1068 my @dup = grep { ! $self->svcnum or $_->svcnum != $self->svcnum }
1069 qsearch( $self->table, \%search );
1071 return @dup if $mode eq 'global';
1072 die "incorrect find_duplicates mode '$mode'" if $mode ne 'export';
1074 my $exports = FS::part_export::export_info($self->table);
1075 my %conflict_svcparts;
1076 my $part_svc = $self->part_svc;
1077 foreach my $part_export ( $part_svc->part_export ) {
1078 %conflict_svcparts = map { $_->svcpart => 1 } $part_export->export_svc;
1080 return grep { $conflict_svcparts{$_->cust_svc->svcpart} } @dup;
1090 The setfixed method return value.
1092 B<export> method isn't used by insert and replace methods yet.
1096 L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>, schema.html
1097 from the base documentation.