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.
422 my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
427 ( ref($_[0]) eq 'HASH' )
431 local $SIG{HUP} = 'IGNORE';
432 local $SIG{INT} = 'IGNORE';
433 local $SIG{QUIT} = 'IGNORE';
434 local $SIG{TERM} = 'IGNORE';
435 local $SIG{TSTP} = 'IGNORE';
436 local $SIG{PIPE} = 'IGNORE';
438 my $oldAutoCommit = $FS::UID::AutoCommit;
439 local $FS::UID::AutoCommit = 0;
442 my $error = $new->set_auto_inventory($old);
444 $dbh->rollback if $oldAutoCommit;
448 #redundant, but so any duplicate fields are maniuplated as appropriate
449 # (svc_phone.phonenum)
450 $error = $new->check;
452 $dbh->rollback if $oldAutoCommit;
456 #if ( $old->username ne $new->username || $old->domsvc != $new->domsvc ) {
457 if ( grep { $old->$_ ne $new->$_ } $new->table_dupcheck_fields ) {
459 $new->svcpart( $new->cust_svc->svcpart ) unless $new->svcpart;
460 $error = $new->_check_duplicate;
462 $dbh->rollback if $oldAutoCommit;
467 $error = $new->SUPER::replace($old);
469 $dbh->rollback if $oldAutoCommit;
474 unless ( $noexport_hack ) {
476 my $export_args = $options->{'export_args'} || [];
478 #not quite false laziness, but same pattern as FS::svc_acct::replace and
479 #FS::part_export::sqlradius::_export_replace. List::Compare or something
480 #would be useful but too much of a pain in the ass to deploy
482 my @old_part_export = $old->cust_svc->part_svc->part_export;
483 my %old_exportnum = map { $_->exportnum => 1 } @old_part_export;
484 my @new_part_export =
486 ? qsearchs('part_svc', { svcpart=>$new->svcpart } )->part_export
487 : $new->cust_svc->part_svc->part_export;
488 my %new_exportnum = map { $_->exportnum => 1 } @new_part_export;
490 foreach my $delete_part_export (
491 grep { ! $new_exportnum{$_->exportnum} } @old_part_export
493 my $error = $delete_part_export->export_delete($old, @$export_args);
495 $dbh->rollback if $oldAutoCommit;
496 return "error deleting, export to ". $delete_part_export->exporttype.
497 " (transaction rolled back): $error";
501 foreach my $replace_part_export (
502 grep { $old_exportnum{$_->exportnum} } @new_part_export
505 $replace_part_export->export_replace( $new, $old, @$export_args);
507 $dbh->rollback if $oldAutoCommit;
508 return "error exporting to ". $replace_part_export->exporttype.
509 " (transaction rolled back): $error";
513 foreach my $insert_part_export (
514 grep { ! $old_exportnum{$_->exportnum} } @new_part_export
516 my $error = $insert_part_export->export_insert($new, @$export_args );
518 $dbh->rollback if $oldAutoCommit;
519 return "error inserting export to ". $insert_part_export->exporttype.
520 " (transaction rolled back): $error";
526 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
532 Sets any fixed fields for this service (see L<FS::part_svc>). If there is an
533 error, returns the error, otherwise returns the FS::part_svc object (use ref()
534 to test the return). Usually called by the check method.
540 $self->setx('F', @_);
545 Sets all fields to their defaults (see L<FS::part_svc>), overriding their
546 current values. If there is an error, returns the error, otherwise returns
547 the FS::part_svc object (use ref() to test the return).
553 $self->setx('D', @_ );
556 =item set_default_and_fixed
560 sub set_default_and_fixed {
562 $self->setx( [ 'D', 'F' ], @_ );
565 =item setx FLAG | FLAG_ARRAYREF , [ CALLBACK_HASHREF ]
567 Sets fields according to the passed in flag or arrayref of flags.
569 Optionally, a hashref of field names and callback coderefs can be passed.
570 If a coderef exists for a given field name, instead of setting the field,
571 the coderef is called with the column value (part_svc_column.columnvalue)
572 as the single parameter.
579 my @x = ref($x) ? @$x : ($x);
580 my $coderef = scalar(@_) ? shift : $self->_fieldhandlers;
583 $self->ut_numbern('svcnum')
585 return $error if $error;
587 my $part_svc = $self->part_svc;
588 return "Unknown svcpart" unless $part_svc;
590 #set default/fixed/whatever fields from part_svc
592 foreach my $part_svc_column (
593 grep { my $f = $_->columnflag; grep { $f eq $_ } @x } #columnflag in @x
594 $part_svc->all_part_svc_column
597 my $columnname = $part_svc_column->columnname;
598 my $columnvalue = $part_svc_column->columnvalue;
600 $columnvalue = &{ $coderef->{$columnname} }( $self, $columnvalue )
601 if exists( $coderef->{$columnname} );
602 $self->setfield( $columnname, $columnvalue );
615 if ( $self->get('svcpart') ) {
616 $svcpart = $self->get('svcpart');
617 } elsif ( $self->svcnum && qsearchs('cust_svc', {'svcnum'=>$self->svcnum}) ) {
618 my $cust_svc = $self->cust_svc;
619 return "Unknown svcnum" unless $cust_svc;
620 $svcpart = $cust_svc->svcpart;
623 qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
629 Returns the FS::svc_pbx record for this service, if any (see L<FS::svc_pbx>).
631 Only makes sense if the service has a pbxsvc field (currently, svc_phone and
636 # XXX FS::h_svc_{acct,phone} could have a history-aware svc_pbx override
640 return '' unless $self->pbxsvc;
641 qsearchs( 'svc_pbx', { 'svcnum' => $self->pbxsvc } );
646 Returns the title of the FS::svc_pbx record associated with this service, if
649 Only makes sense if the service has a pbxsvc field (currently, svc_phone and
656 my $svc_pbx = $self->svc_pbx or return '';
660 =item pbx_select_hash %OPTIONS
662 Can be called as an object method or a class method.
664 Returns a hash SVCNUM => TITLE ... representing the PBXes this customer
665 that may be associated with this service.
667 Currently available options are: I<pkgnum> I<svcpart>
669 Only makes sense if the service has a pbxsvc field (currently, svc_phone and
674 #false laziness w/svc_acct::domain_select_hash
675 sub pbx_select_hash {
676 my ($self, %options) = @_;
682 $part_svc = $self->part_svc;
683 $cust_pkg = $self->cust_svc->cust_pkg
687 $part_svc = qsearchs('part_svc', { 'svcpart' => $options{svcpart} })
688 if $options{'svcpart'};
690 $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $options{pkgnum} })
691 if $options{'pkgnum'};
693 if ($part_svc && ( $part_svc->part_svc_column('pbxsvc')->columnflag eq 'S'
694 || $part_svc->part_svc_column('pbxsvc')->columnflag eq 'F')) {
695 %pbxes = map { $_->svcnum => $_->title }
696 map { qsearchs('svc_pbx', { 'svcnum' => $_ }) }
697 split(',', $part_svc->part_svc_column('pbxsvc')->columnvalue);
698 } elsif ($cust_pkg) { # && !$conf->exists('svc_acct-alldomains') ) {
699 %pbxes = map { $_->svcnum => $_->title }
700 map { qsearchs('svc_pbx', { 'svcnum' => $_->svcnum }) }
701 map { qsearch('cust_svc', { 'pkgnum' => $_->pkgnum } ) }
702 qsearch('cust_pkg', { 'custnum' => $cust_pkg->custnum });
705 %pbxes = map { $_->svcnum => $_->title } qsearch('svc_pbx', {} );
708 if ($part_svc && $part_svc->part_svc_column('pbxsvc')->columnflag eq 'D') {
709 my $svc_pbx = qsearchs('svc_pbx',
710 { 'svcnum' => $part_svc->part_svc_column('pbxsvc')->columnvalue } );
712 $pbxes{$svc_pbx->svcnum} = $svc_pbx->title;
714 warn "unknown svc_pbx.svcnum for part_svc_column pbxsvc: ".
715 $part_svc->part_svc_column('pbxsvc')->columnvalue;
724 =item set_auto_inventory
726 Sets any fields which auto-populate from inventory (see L<FS::part_svc>), and
727 also check any manually populated inventory fields.
729 If there is an error, returns the error, otherwise returns false.
733 sub set_auto_inventory {
735 my $old = @_ ? shift : '';
738 $self->ut_numbern('svcnum')
740 return $error if $error;
742 my $part_svc = $self->part_svc;
743 return "Unkonwn svcpart" unless $part_svc;
745 local $SIG{HUP} = 'IGNORE';
746 local $SIG{INT} = 'IGNORE';
747 local $SIG{QUIT} = 'IGNORE';
748 local $SIG{TERM} = 'IGNORE';
749 local $SIG{TSTP} = 'IGNORE';
750 local $SIG{PIPE} = 'IGNORE';
752 my $oldAutoCommit = $FS::UID::AutoCommit;
753 local $FS::UID::AutoCommit = 0;
756 #set default/fixed/whatever fields from part_svc
757 my $table = $self->table;
758 foreach my $field ( grep { $_ ne 'svcnum' } $self->fields ) {
760 my $part_svc_column = $part_svc->part_svc_column($field);
761 my $columnflag = $part_svc_column->columnflag;
762 next unless $columnflag =~ /^[AM]$/;
764 next if $columnflag eq 'A' && $self->$field() ne '';
766 my $classnum = $part_svc_column->columnvalue;
767 my %hash = ( 'classnum' => $classnum );
769 if ( $columnflag eq 'A' && $self->$field() eq '' ) {
770 $hash{'svcnum'} = '';
771 } elsif ( $columnflag eq 'M' ) {
772 return "Select inventory item for $field" unless $self->getfield($field);
773 $hash{'item'} = $self->getfield($field);
776 my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql(
778 'table' => 'inventory_item',
781 my $inventory_item = qsearchs({
782 'table' => 'inventory_item',
784 'extra_sql' => "AND $agentnums_sql",
785 'order_by' => 'ORDER BY ( agentnum IS NULL ) '. #agent inventory first
786 ' LIMIT 1 FOR UPDATE',
789 unless ( $inventory_item ) {
790 $dbh->rollback if $oldAutoCommit;
791 my $inventory_class =
792 qsearchs('inventory_class', { 'classnum' => $classnum } );
793 return "Can't find inventory_class.classnum $classnum"
794 unless $inventory_class;
795 return "Out of ". $inventory_class->classname. "s\n"; #Lingua:: BS
799 next if $columnflag eq 'M' && $inventory_item->svcnum == $self->svcnum;
801 $self->setfield( $field, $inventory_item->item );
802 #if $columnflag eq 'A' && $self->$field() eq '';
804 $inventory_item->svcnum( $self->svcnum );
805 my $ierror = $inventory_item->replace();
807 $dbh->rollback if $oldAutoCommit;
808 return "Error provisioning inventory: $ierror";
811 if ( $old && $old->$field() && $old->$field() ne $self->$field() ) {
812 my $old_inv = qsearchs({
813 'table' => 'inventory_item',
814 'hashref' => { 'classnum' => $classnum,
815 'svcnum' => $old->svcnum,
816 'item' => $old->$field(),
820 $old_inv->svcnum('');
821 my $oerror = $old_inv->replace;
823 $dbh->rollback if $oldAutoCommit;
824 return "Error unprovisioning inventory: $oerror";
831 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
837 =item return_inventory
841 sub return_inventory {
844 local $SIG{HUP} = 'IGNORE';
845 local $SIG{INT} = 'IGNORE';
846 local $SIG{QUIT} = 'IGNORE';
847 local $SIG{TERM} = 'IGNORE';
848 local $SIG{TSTP} = 'IGNORE';
849 local $SIG{PIPE} = 'IGNORE';
851 my $oldAutoCommit = $FS::UID::AutoCommit;
852 local $FS::UID::AutoCommit = 0;
855 foreach my $inventory_item ( $self->inventory_item ) {
856 $inventory_item->svcnum('');
857 my $error = $inventory_item->replace();
859 $dbh->rollback if $oldAutoCommit;
860 return "Error returning inventory: $error";
864 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
871 Returns the inventory items associated with this svc_ record, as
872 FS::inventory_item objects (see L<FS::inventory_item>.
879 'table' => 'inventory_item',
880 'hashref' => { 'svcnum' => $self->svcnum, },
886 Returns the cust_svc record associated with this svc_ record, as a FS::cust_svc
887 object (see L<FS::cust_svc>).
893 qsearchs('cust_svc', { 'svcnum' => $self->svcnum } );
898 Runs export_suspend callbacks.
905 my $export_args = $options{'export_args'} || [];
906 $self->export('suspend', @$export_args);
911 Runs export_unsuspend callbacks.
918 my $export_args = $options{'export_args'} || [];
919 $self->export('unsuspend', @$export_args);
924 Runs export_links callbacks and returns the links.
931 $self->export('links', $return);
935 =item export_getsettings
937 Runs export_getsettings callbacks and returns the two hashrefs.
941 sub export_getsettings {
945 my $error = $self->export('getsettings', \%settings, \%defaults);
947 #XXX bubble this up better
948 warn "error running export_getsetings: $error";
951 ( \%settings, \%defaults );
954 =item export HOOK [ EXPORT_ARGS ]
956 Runs the provided export hook (i.e. "suspend", "unsuspend") for this service.
961 my( $self, $method ) = ( shift, shift );
963 $method = "export_$method" unless $method =~ /^export_/;
965 local $SIG{HUP} = 'IGNORE';
966 local $SIG{INT} = 'IGNORE';
967 local $SIG{QUIT} = 'IGNORE';
968 local $SIG{TERM} = 'IGNORE';
969 local $SIG{TSTP} = 'IGNORE';
970 local $SIG{PIPE} = 'IGNORE';
972 my $oldAutoCommit = $FS::UID::AutoCommit;
973 local $FS::UID::AutoCommit = 0;
977 unless ( $noexport_hack ) {
978 foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
979 next unless $part_export->can($method);
980 my $error = $part_export->$method($self, @_);
982 $dbh->rollback if $oldAutoCommit;
983 return "error exporting $method event to ". $part_export->exporttype.
984 " (transaction rolled back): $error";
989 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
996 Sets or retrieves overlimit date.
1002 #$self->cust_svc->overlimit(@_);
1003 my $cust_svc = $self->cust_svc;
1004 unless ( $cust_svc ) { #wtf?
1005 my $error = "$me overlimit: missing cust_svc record for svc_acct svcnum ".
1007 if ( $overlimit_missing_cust_svc_nonfatal_kludge ) {
1008 cluck "$error; continuing anyway as requested";
1014 $cust_svc->overlimit(@_);
1019 Stub - returns false (no error) so derived classes don't need to define this
1020 methods. Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
1022 This method is called *before* the deletion step which actually deletes the
1023 services. This method should therefore only be used for "pre-deletion"
1024 cancellation steps, if necessary.
1030 =item clone_suspended
1032 Constructor used by FS::part_export::_export_suspend fallback. Stub returning
1033 same object for svc_ classes which don't implement a suspension fallback
1034 (everything except svc_acct at the moment). Document better.
1038 sub clone_suspended {
1042 =item clone_kludge_unsuspend
1044 Constructor used by FS::part_export::_export_unsuspend fallback. Stub returning
1045 same object for svc_ classes which don't implement a suspension fallback
1046 (everything except svc_acct at the moment). Document better.
1050 sub clone_kludge_unsuspend {
1054 =item find_duplicates MODE FIELDS...
1056 Method used by _check_duplicate routines to find services with duplicate
1057 values in specified fields. Set MODE to 'global' to search across all
1058 services, or 'export' to limit to those that share one or more exports
1059 with this service. FIELDS is a list of field names; only services
1060 matching in all fields will be returned. Empty fields will be skipped.
1064 sub find_duplicates {
1069 my %search = map { $_ => $self->getfield($_) }
1070 grep { length($self->getfield($_)) } @fields;
1071 return () if !%search;
1072 my @dup = grep { ! $self->svcnum or $_->svcnum != $self->svcnum }
1073 qsearch( $self->table, \%search );
1075 return @dup if $mode eq 'global';
1076 die "incorrect find_duplicates mode '$mode'" if $mode ne 'export';
1078 my $exports = FS::part_export::export_info($self->table);
1079 my %conflict_svcparts;
1080 my $part_svc = $self->part_svc;
1081 foreach my $part_export ( $part_svc->part_export ) {
1082 %conflict_svcparts = map { $_->svcpart => 1 } $part_export->export_svc;
1084 return grep { $conflict_svcparts{$_->cust_svc->svcpart} } @dup;
1094 The setfixed method return value.
1096 B<export> method isn't used by insert and replace methods yet.
1100 L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>, schema.html
1101 from the base documentation.