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 );
9 use FS::Record qw( qsearch qsearchs fields dbh );
10 use FS::cust_main_Mixin;
15 use FS::inventory_item;
16 use FS::inventory_class;
17 use FS::NetworkMonitoringSystem;
19 @ISA = qw( FS::cust_main_Mixin FS::Record );
21 $me = '[FS::svc_Common]';
24 $overlimit_missing_cust_svc_nonfatal_kludge = 0;
28 FS::svc_Common - Object method for all svc_ records
34 @ISA = qw( FS::svc_Common );
38 FS::svc_Common is intended as a base class for table-specific classes to
39 inherit from, i.e. FS::svc_acct. FS::svc_Common inherits from FS::Record.
45 =item search_sql_field FIELD STRING
47 Class method which returns an SQL fragment to search for STRING in FIELD.
49 It is now case-insensitive by default.
53 sub search_sql_field {
54 my( $class, $field, $string ) = @_;
55 my $table = $class->table;
56 my $q_string = dbh->quote($string);
57 "LOWER($table.$field) = LOWER($q_string)";
60 #fallback for services that don't provide a search...
62 #my( $class, $string ) = @_;
72 my $class = ref($proto) || $proto;
74 bless ($self, $class);
76 unless ( defined ( $self->table ) ) {
77 $self->{'Table'} = shift;
78 carp "warning: FS::Record::new called with table name ". $self->{'Table'};
81 #$self->{'Hash'} = shift;
83 $self->{'Hash'} = { map { $_ => $newhash->{$_} } qw(svcnum svcpart) };
85 $self->setdefault( $self->_fieldhandlers )
88 $self->{'Hash'}{$_} = $newhash->{$_}
89 foreach grep { defined($newhash->{$_}) && length($newhash->{$_}) }
92 foreach my $field ( grep !defined($self->{'Hash'}{$_}), $self->fields ) {
93 $self->{'Hash'}{$field}='';
96 $self->_rebless if $self->can('_rebless');
98 $self->{'modified'} = 0;
100 $self->_cache($self->{'Hash'}, shift) if $self->can('_cache') && @_;
106 sub _fieldhandlers { {}; }
110 # This restricts the fields based on part_svc_column and the svcpart of
111 # the service. There are four possible cases:
112 # 1. svcpart passed as part of the svc_x hash.
113 # 2. svcpart fetched via cust_svc based on svcnum.
114 # 3. No svcnum or svcpart. In this case, return ALL the fields with
115 # dbtable eq $self->table.
116 # 4. Called via "fields('svc_acct')" or something similar. In this case
117 # there is no $self object.
121 my @vfields = $self->SUPER::virtual_fields;
123 return @vfields unless (ref $self); # Case 4
125 if ($self->svcpart) { # Case 1
126 $svcpart = $self->svcpart;
127 } elsif ( $self->svcnum
128 && qsearchs('cust_svc',{'svcnum'=>$self->svcnum} )
130 $svcpart = $self->cust_svc->svcpart;
135 if ($svcpart) { #Cases 1 and 2
136 my %flags = map { $_->columnname, $_->columnflag } (
137 qsearch ('part_svc_column', { svcpart => $svcpart } )
139 return grep { not ( defined($flags{$_}) && $flags{$_} eq 'X') } @vfields;
148 svc_Common provides a fallback label subroutine that just returns the svcnum.
154 cluck "warning: ". ref($self). " not loaded or missing label method; ".
166 (($self->cust_svc || return)->cust_pkg || return)->cust_main || return
171 defined($self->cust_main);
176 Checks the validity of fields in this record.
178 At present, this does nothing but call FS::Record::check (which, in turn,
179 does nothing but run virtual field checks).
188 =item insert [ , OPTION => VALUE ... ]
190 Adds this record to the database. If there is an error, returns the error,
191 otherwise returns false.
193 The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
194 defined. An FS::cust_svc record will be created and inserted.
196 Currently available options are: I<jobnums>, I<child_objects> and
199 If I<jobnum> is set to an array reference, the jobnums of any export jobs will
200 be added to the referenced array.
202 If I<child_objects> is set to an array reference of FS::tablename objects (for
203 example, FS::acct_snarf objects), they will have their svcnum field set and
204 will be inserted after this record, but before any exports are run. Each
205 element of the array can also optionally be a two-element array reference
206 containing the child object and the name of an alternate field to be filled in
207 with the newly-inserted svcnum, for example C<[ $svc_forward, 'srcsvc' ]>
209 If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
210 jobnums), all provisioning jobs will have a dependancy on the supplied
211 jobnum(s) (they will not run until the specific job(s) complete(s)).
213 If I<export_args> is set to an array reference, the referenced list will be
214 passed to export commands.
221 warn "[$me] insert called with options ".
222 join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
226 local $FS::queue::jobnums = \@jobnums;
227 warn "[$me] insert: set \$FS::queue::jobnums to $FS::queue::jobnums\n"
229 my $objects = $options{'child_objects'} || [];
230 my $depend_jobnums = $options{'depend_jobnum'} || [];
231 $depend_jobnums = [ $depend_jobnums ] unless ref($depend_jobnums);
233 local $SIG{HUP} = 'IGNORE';
234 local $SIG{INT} = 'IGNORE';
235 local $SIG{QUIT} = 'IGNORE';
236 local $SIG{TERM} = 'IGNORE';
237 local $SIG{TSTP} = 'IGNORE';
238 local $SIG{PIPE} = 'IGNORE';
240 my $oldAutoCommit = $FS::UID::AutoCommit;
241 local $FS::UID::AutoCommit = 0;
244 my $svcnum = $self->svcnum;
245 my $cust_svc = $svcnum ? qsearchs('cust_svc',{'svcnum'=>$self->svcnum}) : '';
246 #unless ( $svcnum ) {
247 if ( !$svcnum or !$cust_svc ) {
248 $cust_svc = new FS::cust_svc ( {
249 #hua?# 'svcnum' => $svcnum,
250 'svcnum' => $self->svcnum,
251 'pkgnum' => $self->pkgnum,
252 'svcpart' => $self->svcpart,
254 my $error = $cust_svc->insert;
256 $dbh->rollback if $oldAutoCommit;
259 $svcnum = $self->svcnum($cust_svc->svcnum);
261 #$cust_svc = qsearchs('cust_svc',{'svcnum'=>$self->svcnum});
262 unless ( $cust_svc ) {
263 $dbh->rollback if $oldAutoCommit;
264 return "no cust_svc record found for svcnum ". $self->svcnum;
266 $self->pkgnum($cust_svc->pkgnum);
267 $self->svcpart($cust_svc->svcpart);
270 my $error = $self->preinsert_hook_first
271 || $self->set_auto_inventory
273 || $self->_check_duplicate
274 || $self->preinsert_hook
275 || $self->SUPER::insert;
277 $dbh->rollback if $oldAutoCommit;
281 foreach my $object ( @$objects ) {
283 if ( ref($object) eq 'ARRAY' ) {
284 ($obj, $field) = @$object;
289 $obj->$field($self->svcnum);
290 $error = $obj->insert;
292 $dbh->rollback if $oldAutoCommit;
298 unless ( $noexport_hack ) {
300 warn "[$me] insert: \$FS::queue::jobnums is $FS::queue::jobnums\n"
303 my $export_args = $options{'export_args'} || [];
305 foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
306 my $error = $part_export->export_insert($self, @$export_args);
308 $dbh->rollback if $oldAutoCommit;
309 return "exporting to ". $part_export->exporttype.
310 " (transaction rolled back): $error";
314 foreach my $depend_jobnum ( @$depend_jobnums ) {
315 warn "[$me] inserting dependancies on supplied job $depend_jobnum\n"
317 foreach my $jobnum ( @jobnums ) {
318 my $queue = qsearchs('queue', { 'jobnum' => $jobnum } );
319 warn "[$me] inserting dependancy for job $jobnum on $depend_jobnum\n"
321 my $error = $queue->depend_insert($depend_jobnum);
323 $dbh->rollback if $oldAutoCommit;
324 return "error queuing job dependancy: $error";
331 my $nms_ip_error = $self->nms_ip_insert;
332 if ( $nms_ip_error ) {
333 $dbh->rollback if $oldAutoCommit;
334 return "error queuing IP insert: $nms_ip_error";
337 if ( exists $options{'jobnums'} ) {
338 push @{ $options{'jobnums'} }, @jobnums;
341 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
347 sub preinsert_hook_first { ''; }
348 sub _check_duplcate { ''; }
349 sub preinsert_hook { ''; }
350 sub table_dupcheck_fields { (); }
351 sub predelete_hook { ''; }
352 sub predelete_hook_first { ''; }
354 =item delete [ , OPTION => VALUE ... ]
356 Deletes this account from the database. If there is an error, returns the
357 error, otherwise returns false.
359 The corresponding FS::cust_svc record will be deleted as well.
366 my $export_args = $options{'export_args'} || [];
368 local $SIG{HUP} = 'IGNORE';
369 local $SIG{INT} = 'IGNORE';
370 local $SIG{QUIT} = 'IGNORE';
371 local $SIG{TERM} = 'IGNORE';
372 local $SIG{TSTP} = 'IGNORE';
373 local $SIG{PIPE} = 'IGNORE';
375 my $oldAutoCommit = $FS::UID::AutoCommit;
376 local $FS::UID::AutoCommit = 0;
379 my $error = $self->predelete_hook_first
380 || $self->SUPER::delete
381 || $self->export('delete', @$export_args)
382 || $self->return_inventory
383 || $self->predelete_hook
384 || $self->cust_svc->delete
387 $dbh->rollback if $oldAutoCommit;
391 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
398 Currently this will only run expire exports if any are attached
403 my($self,$date) = (shift,shift);
405 return 'Expire date must be specified' unless $date;
407 local $SIG{HUP} = 'IGNORE';
408 local $SIG{INT} = 'IGNORE';
409 local $SIG{QUIT} = 'IGNORE';
410 local $SIG{TERM} = 'IGNORE';
411 local $SIG{TSTP} = 'IGNORE';
412 local $SIG{PIPE} = 'IGNORE';
414 my $oldAutoCommit = $FS::UID::AutoCommit;
415 local $FS::UID::AutoCommit = 0;
418 my $export_args = [$date];
419 my $error = $self->export('expire', @$export_args);
421 $dbh->rollback if $oldAutoCommit;
425 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
430 =item replace [ OLD_RECORD ] [ HASHREF | OPTION => VALUE ]
432 Replaces OLD_RECORD with this one. If there is an error, returns the error,
433 otherwise returns false.
435 Currently available options are: I<export_args> and I<depend_jobnum>.
437 If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
438 jobnums), all provisioning jobs will have a dependancy on the supplied
439 jobnum(s) (they will not run until the specific job(s) complete(s)).
441 If I<export_args> is set to an array reference, the referenced list will be
442 passed to export commands.
449 my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
454 ( ref($_[0]) eq 'HASH' )
459 local $FS::queue::jobnums = \@jobnums;
460 warn "[$me] replace: set \$FS::queue::jobnums to $FS::queue::jobnums\n"
462 my $depend_jobnums = $options->{'depend_jobnum'} || [];
463 $depend_jobnums = [ $depend_jobnums ] unless ref($depend_jobnums);
465 local $SIG{HUP} = 'IGNORE';
466 local $SIG{INT} = 'IGNORE';
467 local $SIG{QUIT} = 'IGNORE';
468 local $SIG{TERM} = 'IGNORE';
469 local $SIG{TSTP} = 'IGNORE';
470 local $SIG{PIPE} = 'IGNORE';
472 my $oldAutoCommit = $FS::UID::AutoCommit;
473 local $FS::UID::AutoCommit = 0;
476 my $error = $new->set_auto_inventory($old);
478 $dbh->rollback if $oldAutoCommit;
482 #redundant, but so any duplicate fields are maniuplated as appropriate
483 # (svc_phone.phonenum)
484 $error = $new->check;
486 $dbh->rollback if $oldAutoCommit;
490 #if ( $old->username ne $new->username || $old->domsvc != $new->domsvc ) {
491 if ( grep { $old->$_ ne $new->$_ } $new->table_dupcheck_fields ) {
493 $new->svcpart( $new->cust_svc->svcpart ) unless $new->svcpart;
494 $error = $new->_check_duplicate;
496 $dbh->rollback if $oldAutoCommit;
501 $error = $new->SUPER::replace($old);
503 $dbh->rollback if $oldAutoCommit;
508 unless ( $noexport_hack ) {
510 warn "[$me] replace: \$FS::queue::jobnums is $FS::queue::jobnums\n"
513 my $export_args = $options->{'export_args'} || [];
515 #not quite false laziness, but same pattern as FS::svc_acct::replace and
516 #FS::part_export::sqlradius::_export_replace. List::Compare or something
517 #would be useful but too much of a pain in the ass to deploy
519 my @old_part_export = $old->cust_svc->part_svc->part_export;
520 my %old_exportnum = map { $_->exportnum => 1 } @old_part_export;
521 my @new_part_export =
523 ? qsearchs('part_svc', { svcpart=>$new->svcpart } )->part_export
524 : $new->cust_svc->part_svc->part_export;
525 my %new_exportnum = map { $_->exportnum => 1 } @new_part_export;
527 foreach my $delete_part_export (
528 grep { ! $new_exportnum{$_->exportnum} } @old_part_export
530 my $error = $delete_part_export->export_delete($old, @$export_args);
532 $dbh->rollback if $oldAutoCommit;
533 return "error deleting, export to ". $delete_part_export->exporttype.
534 " (transaction rolled back): $error";
538 foreach my $replace_part_export (
539 grep { $old_exportnum{$_->exportnum} } @new_part_export
542 $replace_part_export->export_replace( $new, $old, @$export_args);
544 $dbh->rollback if $oldAutoCommit;
545 return "error exporting to ". $replace_part_export->exporttype.
546 " (transaction rolled back): $error";
550 foreach my $insert_part_export (
551 grep { ! $old_exportnum{$_->exportnum} } @new_part_export
553 my $error = $insert_part_export->export_insert($new, @$export_args );
555 $dbh->rollback if $oldAutoCommit;
556 return "error inserting export to ". $insert_part_export->exporttype.
557 " (transaction rolled back): $error";
561 foreach my $depend_jobnum ( @$depend_jobnums ) {
562 warn "[$me] inserting dependancies on supplied job $depend_jobnum\n"
564 foreach my $jobnum ( @jobnums ) {
565 my $queue = qsearchs('queue', { 'jobnum' => $jobnum } );
566 warn "[$me] inserting dependancy for job $jobnum on $depend_jobnum\n"
568 my $error = $queue->depend_insert($depend_jobnum);
570 $dbh->rollback if $oldAutoCommit;
571 return "error queuing job dependancy: $error";
578 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
584 Sets any fixed fields for this service (see L<FS::part_svc>). If there is an
585 error, returns the error, otherwise returns the FS::part_svc object (use ref()
586 to test the return). Usually called by the check method.
592 $self->setx('F', @_);
597 Sets all fields to their defaults (see L<FS::part_svc>), overriding their
598 current values. If there is an error, returns the error, otherwise returns
599 the FS::part_svc object (use ref() to test the return).
605 $self->setx('D', @_ );
608 =item set_default_and_fixed
612 sub set_default_and_fixed {
614 $self->setx( [ 'D', 'F' ], @_ );
617 =item setx FLAG | FLAG_ARRAYREF , [ CALLBACK_HASHREF ]
619 Sets fields according to the passed in flag or arrayref of flags.
621 Optionally, a hashref of field names and callback coderefs can be passed.
622 If a coderef exists for a given field name, instead of setting the field,
623 the coderef is called with the column value (part_svc_column.columnvalue)
624 as the single parameter.
631 my @x = ref($x) ? @$x : ($x);
632 my $coderef = scalar(@_) ? shift : $self->_fieldhandlers;
635 $self->ut_numbern('svcnum')
637 return $error if $error;
639 my $part_svc = $self->part_svc;
640 return "Unknown svcpart" unless $part_svc;
642 #set default/fixed/whatever fields from part_svc
644 foreach my $part_svc_column (
645 grep { my $f = $_->columnflag; grep { $f eq $_ } @x } #columnflag in @x
646 $part_svc->all_part_svc_column
649 my $columnname = $part_svc_column->columnname;
650 my $columnvalue = $part_svc_column->columnvalue;
652 $columnvalue = &{ $coderef->{$columnname} }( $self, $columnvalue )
653 if exists( $coderef->{$columnname} );
654 $self->setfield( $columnname, $columnvalue );
667 if ( $self->get('svcpart') ) {
668 $svcpart = $self->get('svcpart');
669 } elsif ( $self->svcnum && qsearchs('cust_svc', {'svcnum'=>$self->svcnum}) ) {
670 my $cust_svc = $self->cust_svc;
671 return "Unknown svcnum" unless $cust_svc;
672 $svcpart = $cust_svc->svcpart;
675 qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
681 Returns the FS::svc_pbx record for this service, if any (see L<FS::svc_pbx>).
683 Only makes sense if the service has a pbxsvc field (currently, svc_phone and
688 # XXX FS::h_svc_{acct,phone} could have a history-aware svc_pbx override
692 return '' unless $self->pbxsvc;
693 qsearchs( 'svc_pbx', { 'svcnum' => $self->pbxsvc } );
698 Returns the title of the FS::svc_pbx record associated with this service, if
701 Only makes sense if the service has a pbxsvc field (currently, svc_phone and
708 my $svc_pbx = $self->svc_pbx or return '';
712 =item pbx_select_hash %OPTIONS
714 Can be called as an object method or a class method.
716 Returns a hash SVCNUM => TITLE ... representing the PBXes this customer
717 that may be associated with this service.
719 Currently available options are: I<pkgnum> I<svcpart>
721 Only makes sense if the service has a pbxsvc field (currently, svc_phone and
726 #false laziness w/svc_acct::domain_select_hash
727 sub pbx_select_hash {
728 my ($self, %options) = @_;
734 $part_svc = $self->part_svc;
735 $cust_pkg = $self->cust_svc->cust_pkg
739 $part_svc = qsearchs('part_svc', { 'svcpart' => $options{svcpart} })
740 if $options{'svcpart'};
742 $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $options{pkgnum} })
743 if $options{'pkgnum'};
745 if ($part_svc && ( $part_svc->part_svc_column('pbxsvc')->columnflag eq 'S'
746 || $part_svc->part_svc_column('pbxsvc')->columnflag eq 'F')) {
747 %pbxes = map { $_->svcnum => $_->title }
748 map { qsearchs('svc_pbx', { 'svcnum' => $_ }) }
749 split(',', $part_svc->part_svc_column('pbxsvc')->columnvalue);
750 } elsif ($cust_pkg) { # && !$conf->exists('svc_acct-alldomains') ) {
751 %pbxes = map { $_->svcnum => $_->title }
752 map { qsearchs('svc_pbx', { 'svcnum' => $_->svcnum }) }
753 map { qsearch('cust_svc', { 'pkgnum' => $_->pkgnum } ) }
754 qsearch('cust_pkg', { 'custnum' => $cust_pkg->custnum });
757 %pbxes = map { $_->svcnum => $_->title } qsearch('svc_pbx', {} );
760 if ($part_svc && $part_svc->part_svc_column('pbxsvc')->columnflag eq 'D') {
761 my $svc_pbx = qsearchs('svc_pbx',
762 { 'svcnum' => $part_svc->part_svc_column('pbxsvc')->columnvalue } );
764 $pbxes{$svc_pbx->svcnum} = $svc_pbx->title;
766 warn "unknown svc_pbx.svcnum for part_svc_column pbxsvc: ".
767 $part_svc->part_svc_column('pbxsvc')->columnvalue;
776 =item set_auto_inventory
778 Sets any fields which auto-populate from inventory (see L<FS::part_svc>), and
779 also check any manually populated inventory fields.
781 If there is an error, returns the error, otherwise returns false.
785 sub set_auto_inventory {
787 my $old = @_ ? shift : '';
790 $self->ut_numbern('svcnum')
792 return $error if $error;
794 my $part_svc = $self->part_svc;
795 return "Unkonwn svcpart" unless $part_svc;
797 local $SIG{HUP} = 'IGNORE';
798 local $SIG{INT} = 'IGNORE';
799 local $SIG{QUIT} = 'IGNORE';
800 local $SIG{TERM} = 'IGNORE';
801 local $SIG{TSTP} = 'IGNORE';
802 local $SIG{PIPE} = 'IGNORE';
804 my $oldAutoCommit = $FS::UID::AutoCommit;
805 local $FS::UID::AutoCommit = 0;
808 #set default/fixed/whatever fields from part_svc
809 my $table = $self->table;
810 foreach my $field ( grep { $_ ne 'svcnum' } $self->fields ) {
812 my $part_svc_column = $part_svc->part_svc_column($field);
813 my $columnflag = $part_svc_column->columnflag;
814 next unless $columnflag =~ /^[AM]$/;
816 next if $columnflag eq 'A' && $self->$field() ne '';
818 my $classnum = $part_svc_column->columnvalue;
819 my %hash = ( 'classnum' => $classnum );
821 if ( $columnflag eq 'A' && $self->$field() eq '' ) {
822 $hash{'svcnum'} = '';
823 } elsif ( $columnflag eq 'M' ) {
824 return "Select inventory item for $field" unless $self->getfield($field);
825 $hash{'item'} = $self->getfield($field);
828 my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql(
830 'table' => 'inventory_item',
833 my $inventory_item = qsearchs({
834 'table' => 'inventory_item',
836 'extra_sql' => "AND $agentnums_sql",
837 'order_by' => 'ORDER BY ( agentnum IS NULL ) '. #agent inventory first
838 ' LIMIT 1 FOR UPDATE',
841 unless ( $inventory_item ) {
842 $dbh->rollback if $oldAutoCommit;
843 my $inventory_class =
844 qsearchs('inventory_class', { 'classnum' => $classnum } );
845 return "Can't find inventory_class.classnum $classnum"
846 unless $inventory_class;
847 return "Out of ". $inventory_class->classname. "s\n"; #Lingua:: BS
851 next if $columnflag eq 'M' && $inventory_item->svcnum == $self->svcnum;
853 $self->setfield( $field, $inventory_item->item );
854 #if $columnflag eq 'A' && $self->$field() eq '';
856 $inventory_item->svcnum( $self->svcnum );
857 my $ierror = $inventory_item->replace();
859 $dbh->rollback if $oldAutoCommit;
860 return "Error provisioning inventory: $ierror";
863 if ( $old && $old->$field() && $old->$field() ne $self->$field() ) {
864 my $old_inv = qsearchs({
865 'table' => 'inventory_item',
866 'hashref' => { 'classnum' => $classnum,
867 'svcnum' => $old->svcnum,
868 'item' => $old->$field(),
872 $old_inv->svcnum('');
873 my $oerror = $old_inv->replace;
875 $dbh->rollback if $oldAutoCommit;
876 return "Error unprovisioning inventory: $oerror";
883 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
889 =item return_inventory
893 sub return_inventory {
896 local $SIG{HUP} = 'IGNORE';
897 local $SIG{INT} = 'IGNORE';
898 local $SIG{QUIT} = 'IGNORE';
899 local $SIG{TERM} = 'IGNORE';
900 local $SIG{TSTP} = 'IGNORE';
901 local $SIG{PIPE} = 'IGNORE';
903 my $oldAutoCommit = $FS::UID::AutoCommit;
904 local $FS::UID::AutoCommit = 0;
907 foreach my $inventory_item ( $self->inventory_item ) {
908 $inventory_item->svcnum('');
909 my $error = $inventory_item->replace();
911 $dbh->rollback if $oldAutoCommit;
912 return "Error returning inventory: $error";
916 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
923 Returns the inventory items associated with this svc_ record, as
924 FS::inventory_item objects (see L<FS::inventory_item>.
931 'table' => 'inventory_item',
932 'hashref' => { 'svcnum' => $self->svcnum, },
938 Returns the cust_svc record associated with this svc_ record, as a FS::cust_svc
939 object (see L<FS::cust_svc>).
945 qsearchs('cust_svc', { 'svcnum' => $self->svcnum } );
950 Runs export_suspend callbacks.
957 my $export_args = $options{'export_args'} || [];
958 $self->export('suspend', @$export_args);
963 Runs export_unsuspend callbacks.
970 my $export_args = $options{'export_args'} || [];
971 $self->export('unsuspend', @$export_args);
976 Runs export_links callbacks and returns the links.
983 $self->export('links', $return);
987 =item export_getsettings
989 Runs export_getsettings callbacks and returns the two hashrefs.
993 sub export_getsettings {
997 my $error = $self->export('getsettings', \%settings, \%defaults);
999 #XXX bubble this up better
1000 warn "error running export_getsetings: $error";
1003 ( \%settings, \%defaults );
1006 =item export_getstatus
1008 Runs export_getstatus callbacks and returns a two item list consisting of an
1009 HTML status and a status hashref.
1013 sub export_getstatus {
1017 my $error = $self->export('getstatus', \$html, \%hash);
1019 #XXX bubble this up better
1020 warn "error running export_getstatus: $error";
1026 =item export HOOK [ EXPORT_ARGS ]
1028 Runs the provided export hook (i.e. "suspend", "unsuspend") for this service.
1033 my( $self, $method ) = ( shift, shift );
1035 $method = "export_$method" unless $method =~ /^export_/;
1037 local $SIG{HUP} = 'IGNORE';
1038 local $SIG{INT} = 'IGNORE';
1039 local $SIG{QUIT} = 'IGNORE';
1040 local $SIG{TERM} = 'IGNORE';
1041 local $SIG{TSTP} = 'IGNORE';
1042 local $SIG{PIPE} = 'IGNORE';
1044 my $oldAutoCommit = $FS::UID::AutoCommit;
1045 local $FS::UID::AutoCommit = 0;
1049 unless ( $noexport_hack ) {
1050 foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
1051 next unless $part_export->can($method);
1052 my $error = $part_export->$method($self, @_);
1054 $dbh->rollback if $oldAutoCommit;
1055 return "error exporting $method event to ". $part_export->exporttype.
1056 " (transaction rolled back): $error";
1061 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
1068 Sets or retrieves overlimit date.
1074 #$self->cust_svc->overlimit(@_);
1075 my $cust_svc = $self->cust_svc;
1076 unless ( $cust_svc ) { #wtf?
1077 my $error = "$me overlimit: missing cust_svc record for svc_acct svcnum ".
1079 if ( $overlimit_missing_cust_svc_nonfatal_kludge ) {
1080 cluck "$error; continuing anyway as requested";
1086 $cust_svc->overlimit(@_);
1091 Stub - returns false (no error) so derived classes don't need to define this
1092 methods. Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
1094 This method is called *before* the deletion step which actually deletes the
1095 services. This method should therefore only be used for "pre-deletion"
1096 cancellation steps, if necessary.
1102 =item clone_suspended
1104 Constructor used by FS::part_export::_export_suspend fallback. Stub returning
1105 same object for svc_ classes which don't implement a suspension fallback
1106 (everything except svc_acct at the moment). Document better.
1110 sub clone_suspended {
1114 =item clone_kludge_unsuspend
1116 Constructor used by FS::part_export::_export_unsuspend fallback. Stub returning
1117 same object for svc_ classes which don't implement a suspension fallback
1118 (everything except svc_acct at the moment). Document better.
1122 sub clone_kludge_unsuspend {
1126 =item find_duplicates MODE FIELDS...
1128 Method used by _check_duplicate routines to find services with duplicate
1129 values in specified fields. Set MODE to 'global' to search across all
1130 services, or 'export' to limit to those that share one or more exports
1131 with this service. FIELDS is a list of field names; only services
1132 matching in all fields will be returned. Empty fields will be skipped.
1136 sub find_duplicates {
1141 my %search = map { $_ => $self->getfield($_) }
1142 grep { length($self->getfield($_)) } @fields;
1143 return () if !%search;
1144 my @dup = grep { ! $self->svcnum or $_->svcnum != $self->svcnum }
1145 qsearch( $self->table, \%search );
1147 return @dup if $mode eq 'global';
1148 die "incorrect find_duplicates mode '$mode'" if $mode ne 'export';
1150 my $exports = FS::part_export::export_info($self->table);
1151 my %conflict_svcparts;
1152 my $part_svc = $self->part_svc;
1153 foreach my $part_export ( $part_svc->part_export ) {
1154 %conflict_svcparts = map { $_->svcpart => 1 } $part_export->export_svc;
1156 return grep { $conflict_svcparts{$_->cust_svc->svcpart} } @dup;
1159 =item getstatus_html
1163 sub getstatus_html {
1166 my $part_svc = $self->cust_svc->part_svc;
1170 foreach my $export ( grep $_->can('export_getstatus'), $part_svc->part_export ) {
1171 my $export_html = '';
1173 $export->export_getstatus( $self, \$export_html, \%hash );
1174 $html .= $export_html;
1187 my $conf = new FS::Conf;
1188 return '' unless grep { $self->table eq $_ }
1189 $conf->config('nms-auto_add-svc_ips');
1190 my $ip_field = $self->table_info->{'ip_field'};
1192 my $queue = FS::queue->new( {
1193 'job' => 'FS::NetworkMonitoringSystem::queued_add_router',
1194 'svcnum' => $self->svcnum,
1196 $queue->insert( 'FS::NetworkMonitoringSystem',
1198 $conf->config('nms-auto_add-community')
1207 #XXX not yet implemented
1214 The setfixed method return value.
1216 B<export> method isn't used by insert and replace methods yet.
1220 L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>, schema.html
1221 from the base documentation.