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 { (); }
333 sub predelete_hook { ''; }
334 sub predelete_hook_first { ''; }
336 =item delete [ , OPTION => VALUE ... ]
338 Deletes this account from the database. If there is an error, returns the
339 error, otherwise returns false.
341 The corresponding FS::cust_svc record will be deleted as well.
348 my $export_args = $options{'export_args'} || [];
350 local $SIG{HUP} = 'IGNORE';
351 local $SIG{INT} = 'IGNORE';
352 local $SIG{QUIT} = 'IGNORE';
353 local $SIG{TERM} = 'IGNORE';
354 local $SIG{TSTP} = 'IGNORE';
355 local $SIG{PIPE} = 'IGNORE';
357 my $oldAutoCommit = $FS::UID::AutoCommit;
358 local $FS::UID::AutoCommit = 0;
361 my $error = $self->predelete_hook_first
362 || $self->SUPER::delete
363 || $self->export('delete', @$export_args)
364 || $self->return_inventory
365 || $self->predelete_hook
366 || $self->cust_svc->delete
369 $dbh->rollback if $oldAutoCommit;
373 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
380 Currently this will only run expire exports if any are attached
385 my($self,$date) = (shift,shift);
387 return 'Expire date must be specified' unless $date;
389 local $SIG{HUP} = 'IGNORE';
390 local $SIG{INT} = 'IGNORE';
391 local $SIG{QUIT} = 'IGNORE';
392 local $SIG{TERM} = 'IGNORE';
393 local $SIG{TSTP} = 'IGNORE';
394 local $SIG{PIPE} = 'IGNORE';
396 my $oldAutoCommit = $FS::UID::AutoCommit;
397 local $FS::UID::AutoCommit = 0;
400 my $export_args = [$date];
401 my $error = $self->export('expire', @$export_args);
403 $dbh->rollback if $oldAutoCommit;
407 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
412 =item replace [ OLD_RECORD ] [ HASHREF | OPTION => VALUE ]
414 Replaces OLD_RECORD with this one. If there is an error, returns the error,
415 otherwise returns false.
417 Currently available options are: I<export_args> and I<depend_jobnum>.
419 If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
420 jobnums), all provisioning jobs will have a dependancy on the supplied
421 jobnum(s) (they will not run until the specific job(s) complete(s)).
423 If I<export_args> is set to an array reference, the referenced list will be
424 passed to export commands.
431 my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
436 ( ref($_[0]) eq 'HASH' )
441 local $FS::queue::jobnums = \@jobnums;
442 warn "[$me] replace: set \$FS::queue::jobnums to $FS::queue::jobnums\n"
444 my $depend_jobnums = $options->{'depend_jobnum'} || [];
445 $depend_jobnums = [ $depend_jobnums ] unless ref($depend_jobnums);
447 local $SIG{HUP} = 'IGNORE';
448 local $SIG{INT} = 'IGNORE';
449 local $SIG{QUIT} = 'IGNORE';
450 local $SIG{TERM} = 'IGNORE';
451 local $SIG{TSTP} = 'IGNORE';
452 local $SIG{PIPE} = 'IGNORE';
454 my $oldAutoCommit = $FS::UID::AutoCommit;
455 local $FS::UID::AutoCommit = 0;
458 my $error = $new->set_auto_inventory($old);
460 $dbh->rollback if $oldAutoCommit;
464 #redundant, but so any duplicate fields are maniuplated as appropriate
465 # (svc_phone.phonenum)
466 $error = $new->check;
468 $dbh->rollback if $oldAutoCommit;
472 #if ( $old->username ne $new->username || $old->domsvc != $new->domsvc ) {
473 if ( grep { $old->$_ ne $new->$_ } $new->table_dupcheck_fields ) {
475 $new->svcpart( $new->cust_svc->svcpart ) unless $new->svcpart;
476 $error = $new->_check_duplicate;
478 $dbh->rollback if $oldAutoCommit;
483 $error = $new->SUPER::replace($old);
485 $dbh->rollback if $oldAutoCommit;
490 unless ( $noexport_hack ) {
492 warn "[$me] replace: \$FS::queue::jobnums is $FS::queue::jobnums\n"
495 my $export_args = $options->{'export_args'} || [];
497 #not quite false laziness, but same pattern as FS::svc_acct::replace and
498 #FS::part_export::sqlradius::_export_replace. List::Compare or something
499 #would be useful but too much of a pain in the ass to deploy
501 my @old_part_export = $old->cust_svc->part_svc->part_export;
502 my %old_exportnum = map { $_->exportnum => 1 } @old_part_export;
503 my @new_part_export =
505 ? qsearchs('part_svc', { svcpart=>$new->svcpart } )->part_export
506 : $new->cust_svc->part_svc->part_export;
507 my %new_exportnum = map { $_->exportnum => 1 } @new_part_export;
509 foreach my $delete_part_export (
510 grep { ! $new_exportnum{$_->exportnum} } @old_part_export
512 my $error = $delete_part_export->export_delete($old, @$export_args);
514 $dbh->rollback if $oldAutoCommit;
515 return "error deleting, export to ". $delete_part_export->exporttype.
516 " (transaction rolled back): $error";
520 foreach my $replace_part_export (
521 grep { $old_exportnum{$_->exportnum} } @new_part_export
524 $replace_part_export->export_replace( $new, $old, @$export_args);
526 $dbh->rollback if $oldAutoCommit;
527 return "error exporting to ". $replace_part_export->exporttype.
528 " (transaction rolled back): $error";
532 foreach my $insert_part_export (
533 grep { ! $old_exportnum{$_->exportnum} } @new_part_export
535 my $error = $insert_part_export->export_insert($new, @$export_args );
537 $dbh->rollback if $oldAutoCommit;
538 return "error inserting export to ". $insert_part_export->exporttype.
539 " (transaction rolled back): $error";
543 foreach my $depend_jobnum ( @$depend_jobnums ) {
544 warn "[$me] inserting dependancies on supplied job $depend_jobnum\n"
546 foreach my $jobnum ( @jobnums ) {
547 my $queue = qsearchs('queue', { 'jobnum' => $jobnum } );
548 warn "[$me] inserting dependancy for job $jobnum on $depend_jobnum\n"
550 my $error = $queue->depend_insert($depend_jobnum);
552 $dbh->rollback if $oldAutoCommit;
553 return "error queuing job dependancy: $error";
560 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
566 Sets any fixed fields for this service (see L<FS::part_svc>). If there is an
567 error, returns the error, otherwise returns the FS::part_svc object (use ref()
568 to test the return). Usually called by the check method.
574 $self->setx('F', @_);
579 Sets all fields to their defaults (see L<FS::part_svc>), overriding their
580 current values. If there is an error, returns the error, otherwise returns
581 the FS::part_svc object (use ref() to test the return).
587 $self->setx('D', @_ );
590 =item set_default_and_fixed
594 sub set_default_and_fixed {
596 $self->setx( [ 'D', 'F' ], @_ );
599 =item setx FLAG | FLAG_ARRAYREF , [ CALLBACK_HASHREF ]
601 Sets fields according to the passed in flag or arrayref of flags.
603 Optionally, a hashref of field names and callback coderefs can be passed.
604 If a coderef exists for a given field name, instead of setting the field,
605 the coderef is called with the column value (part_svc_column.columnvalue)
606 as the single parameter.
613 my @x = ref($x) ? @$x : ($x);
614 my $coderef = scalar(@_) ? shift : $self->_fieldhandlers;
617 $self->ut_numbern('svcnum')
619 return $error if $error;
621 my $part_svc = $self->part_svc;
622 return "Unknown svcpart" unless $part_svc;
624 #set default/fixed/whatever fields from part_svc
626 foreach my $part_svc_column (
627 grep { my $f = $_->columnflag; grep { $f eq $_ } @x } #columnflag in @x
628 $part_svc->all_part_svc_column
631 my $columnname = $part_svc_column->columnname;
632 my $columnvalue = $part_svc_column->columnvalue;
634 $columnvalue = &{ $coderef->{$columnname} }( $self, $columnvalue )
635 if exists( $coderef->{$columnname} );
636 $self->setfield( $columnname, $columnvalue );
649 if ( $self->get('svcpart') ) {
650 $svcpart = $self->get('svcpart');
651 } elsif ( $self->svcnum && qsearchs('cust_svc', {'svcnum'=>$self->svcnum}) ) {
652 my $cust_svc = $self->cust_svc;
653 return "Unknown svcnum" unless $cust_svc;
654 $svcpart = $cust_svc->svcpart;
657 qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
663 Returns the FS::svc_pbx record for this service, if any (see L<FS::svc_pbx>).
665 Only makes sense if the service has a pbxsvc field (currently, svc_phone and
670 # XXX FS::h_svc_{acct,phone} could have a history-aware svc_pbx override
674 return '' unless $self->pbxsvc;
675 qsearchs( 'svc_pbx', { 'svcnum' => $self->pbxsvc } );
680 Returns the title of the FS::svc_pbx record associated with this service, if
683 Only makes sense if the service has a pbxsvc field (currently, svc_phone and
690 my $svc_pbx = $self->svc_pbx or return '';
694 =item pbx_select_hash %OPTIONS
696 Can be called as an object method or a class method.
698 Returns a hash SVCNUM => TITLE ... representing the PBXes this customer
699 that may be associated with this service.
701 Currently available options are: I<pkgnum> I<svcpart>
703 Only makes sense if the service has a pbxsvc field (currently, svc_phone and
708 #false laziness w/svc_acct::domain_select_hash
709 sub pbx_select_hash {
710 my ($self, %options) = @_;
716 $part_svc = $self->part_svc;
717 $cust_pkg = $self->cust_svc->cust_pkg
721 $part_svc = qsearchs('part_svc', { 'svcpart' => $options{svcpart} })
722 if $options{'svcpart'};
724 $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $options{pkgnum} })
725 if $options{'pkgnum'};
727 if ($part_svc && ( $part_svc->part_svc_column('pbxsvc')->columnflag eq 'S'
728 || $part_svc->part_svc_column('pbxsvc')->columnflag eq 'F')) {
729 %pbxes = map { $_->svcnum => $_->title }
730 map { qsearchs('svc_pbx', { 'svcnum' => $_ }) }
731 split(',', $part_svc->part_svc_column('pbxsvc')->columnvalue);
732 } elsif ($cust_pkg) { # && !$conf->exists('svc_acct-alldomains') ) {
733 %pbxes = map { $_->svcnum => $_->title }
734 map { qsearchs('svc_pbx', { 'svcnum' => $_->svcnum }) }
735 map { qsearch('cust_svc', { 'pkgnum' => $_->pkgnum } ) }
736 qsearch('cust_pkg', { 'custnum' => $cust_pkg->custnum });
739 %pbxes = map { $_->svcnum => $_->title } qsearch('svc_pbx', {} );
742 if ($part_svc && $part_svc->part_svc_column('pbxsvc')->columnflag eq 'D') {
743 my $svc_pbx = qsearchs('svc_pbx',
744 { 'svcnum' => $part_svc->part_svc_column('pbxsvc')->columnvalue } );
746 $pbxes{$svc_pbx->svcnum} = $svc_pbx->title;
748 warn "unknown svc_pbx.svcnum for part_svc_column pbxsvc: ".
749 $part_svc->part_svc_column('pbxsvc')->columnvalue;
758 =item set_auto_inventory
760 Sets any fields which auto-populate from inventory (see L<FS::part_svc>), and
761 also check any manually populated inventory fields.
763 If there is an error, returns the error, otherwise returns false.
767 sub set_auto_inventory {
769 my $old = @_ ? shift : '';
772 $self->ut_numbern('svcnum')
774 return $error if $error;
776 my $part_svc = $self->part_svc;
777 return "Unkonwn svcpart" unless $part_svc;
779 local $SIG{HUP} = 'IGNORE';
780 local $SIG{INT} = 'IGNORE';
781 local $SIG{QUIT} = 'IGNORE';
782 local $SIG{TERM} = 'IGNORE';
783 local $SIG{TSTP} = 'IGNORE';
784 local $SIG{PIPE} = 'IGNORE';
786 my $oldAutoCommit = $FS::UID::AutoCommit;
787 local $FS::UID::AutoCommit = 0;
790 #set default/fixed/whatever fields from part_svc
791 my $table = $self->table;
792 foreach my $field ( grep { $_ ne 'svcnum' } $self->fields ) {
794 my $part_svc_column = $part_svc->part_svc_column($field);
795 my $columnflag = $part_svc_column->columnflag;
796 next unless $columnflag =~ /^[AM]$/;
798 next if $columnflag eq 'A' && $self->$field() ne '';
800 my $classnum = $part_svc_column->columnvalue;
801 my %hash = ( 'classnum' => $classnum );
803 if ( $columnflag eq 'A' && $self->$field() eq '' ) {
804 $hash{'svcnum'} = '';
805 } elsif ( $columnflag eq 'M' ) {
806 return "Select inventory item for $field" unless $self->getfield($field);
807 $hash{'item'} = $self->getfield($field);
810 my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql(
812 'table' => 'inventory_item',
815 my $inventory_item = qsearchs({
816 'table' => 'inventory_item',
818 'extra_sql' => "AND $agentnums_sql",
819 'order_by' => 'ORDER BY ( agentnum IS NULL ) '. #agent inventory first
820 ' LIMIT 1 FOR UPDATE',
823 unless ( $inventory_item ) {
824 $dbh->rollback if $oldAutoCommit;
825 my $inventory_class =
826 qsearchs('inventory_class', { 'classnum' => $classnum } );
827 return "Can't find inventory_class.classnum $classnum"
828 unless $inventory_class;
829 return "Out of ". $inventory_class->classname. "s\n"; #Lingua:: BS
833 next if $columnflag eq 'M' && $inventory_item->svcnum == $self->svcnum;
835 $self->setfield( $field, $inventory_item->item );
836 #if $columnflag eq 'A' && $self->$field() eq '';
838 $inventory_item->svcnum( $self->svcnum );
839 my $ierror = $inventory_item->replace();
841 $dbh->rollback if $oldAutoCommit;
842 return "Error provisioning inventory: $ierror";
845 if ( $old && $old->$field() && $old->$field() ne $self->$field() ) {
846 my $old_inv = qsearchs({
847 'table' => 'inventory_item',
848 'hashref' => { 'classnum' => $classnum,
849 'svcnum' => $old->svcnum,
850 'item' => $old->$field(),
854 $old_inv->svcnum('');
855 my $oerror = $old_inv->replace;
857 $dbh->rollback if $oldAutoCommit;
858 return "Error unprovisioning inventory: $oerror";
865 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
871 =item return_inventory
875 sub return_inventory {
878 local $SIG{HUP} = 'IGNORE';
879 local $SIG{INT} = 'IGNORE';
880 local $SIG{QUIT} = 'IGNORE';
881 local $SIG{TERM} = 'IGNORE';
882 local $SIG{TSTP} = 'IGNORE';
883 local $SIG{PIPE} = 'IGNORE';
885 my $oldAutoCommit = $FS::UID::AutoCommit;
886 local $FS::UID::AutoCommit = 0;
889 foreach my $inventory_item ( $self->inventory_item ) {
890 $inventory_item->svcnum('');
891 my $error = $inventory_item->replace();
893 $dbh->rollback if $oldAutoCommit;
894 return "Error returning inventory: $error";
898 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
905 Returns the inventory items associated with this svc_ record, as
906 FS::inventory_item objects (see L<FS::inventory_item>.
913 'table' => 'inventory_item',
914 'hashref' => { 'svcnum' => $self->svcnum, },
920 Returns the cust_svc record associated with this svc_ record, as a FS::cust_svc
921 object (see L<FS::cust_svc>).
927 qsearchs('cust_svc', { 'svcnum' => $self->svcnum } );
932 Runs export_suspend callbacks.
939 my $export_args = $options{'export_args'} || [];
940 $self->export('suspend', @$export_args);
945 Runs export_unsuspend callbacks.
952 my $export_args = $options{'export_args'} || [];
953 $self->export('unsuspend', @$export_args);
958 Runs export_links callbacks and returns the links.
965 $self->export('links', $return);
969 =item export_getsettings
971 Runs export_getsettings callbacks and returns the two hashrefs.
975 sub export_getsettings {
979 my $error = $self->export('getsettings', \%settings, \%defaults);
981 #XXX bubble this up better
982 warn "error running export_getsetings: $error";
985 ( \%settings, \%defaults );
988 =item export HOOK [ EXPORT_ARGS ]
990 Runs the provided export hook (i.e. "suspend", "unsuspend") for this service.
995 my( $self, $method ) = ( shift, shift );
997 $method = "export_$method" unless $method =~ /^export_/;
999 local $SIG{HUP} = 'IGNORE';
1000 local $SIG{INT} = 'IGNORE';
1001 local $SIG{QUIT} = 'IGNORE';
1002 local $SIG{TERM} = 'IGNORE';
1003 local $SIG{TSTP} = 'IGNORE';
1004 local $SIG{PIPE} = 'IGNORE';
1006 my $oldAutoCommit = $FS::UID::AutoCommit;
1007 local $FS::UID::AutoCommit = 0;
1011 unless ( $noexport_hack ) {
1012 foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
1013 next unless $part_export->can($method);
1014 my $error = $part_export->$method($self, @_);
1016 $dbh->rollback if $oldAutoCommit;
1017 return "error exporting $method event to ". $part_export->exporttype.
1018 " (transaction rolled back): $error";
1023 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
1030 Sets or retrieves overlimit date.
1036 #$self->cust_svc->overlimit(@_);
1037 my $cust_svc = $self->cust_svc;
1038 unless ( $cust_svc ) { #wtf?
1039 my $error = "$me overlimit: missing cust_svc record for svc_acct svcnum ".
1041 if ( $overlimit_missing_cust_svc_nonfatal_kludge ) {
1042 cluck "$error; continuing anyway as requested";
1048 $cust_svc->overlimit(@_);
1053 Stub - returns false (no error) so derived classes don't need to define this
1054 methods. Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
1056 This method is called *before* the deletion step which actually deletes the
1057 services. This method should therefore only be used for "pre-deletion"
1058 cancellation steps, if necessary.
1064 =item clone_suspended
1066 Constructor used by FS::part_export::_export_suspend fallback. Stub returning
1067 same object for svc_ classes which don't implement a suspension fallback
1068 (everything except svc_acct at the moment). Document better.
1072 sub clone_suspended {
1076 =item clone_kludge_unsuspend
1078 Constructor used by FS::part_export::_export_unsuspend fallback. Stub returning
1079 same object for svc_ classes which don't implement a suspension fallback
1080 (everything except svc_acct at the moment). Document better.
1084 sub clone_kludge_unsuspend {
1088 =item find_duplicates MODE FIELDS...
1090 Method used by _check_duplicate routines to find services with duplicate
1091 values in specified fields. Set MODE to 'global' to search across all
1092 services, or 'export' to limit to those that share one or more exports
1093 with this service. FIELDS is a list of field names; only services
1094 matching in all fields will be returned. Empty fields will be skipped.
1098 sub find_duplicates {
1103 my %search = map { $_ => $self->getfield($_) }
1104 grep { length($self->getfield($_)) } @fields;
1105 return () if !%search;
1106 my @dup = grep { ! $self->svcnum or $_->svcnum != $self->svcnum }
1107 qsearch( $self->table, \%search );
1109 return @dup if $mode eq 'global';
1110 die "incorrect find_duplicates mode '$mode'" if $mode ne 'export';
1112 my $exports = FS::part_export::export_info($self->table);
1113 my %conflict_svcparts;
1114 my $part_svc = $self->part_svc;
1115 foreach my $part_export ( $part_svc->part_export ) {
1116 %conflict_svcparts = map { $_->svcpart => 1 } $part_export->export_svc;
1118 return grep { $conflict_svcparts{$_->cust_svc->svcpart} } @dup;
1128 The setfixed method return value.
1130 B<export> method isn't used by insert and replace methods yet.
1134 L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>, schema.html
1135 from the base documentation.