$overlimit_missing_cust_svc_nonfatal_kludge );
use Carp qw( cluck carp croak confess ); #specify cluck have to specify them all
use Scalar::Util qw( blessed );
+use Lingua::EN::Inflect qw( PL_N );
+use FS::Conf;
use FS::Record qw( qsearch qsearchs fields dbh );
use FS::cust_main_Mixin;
use FS::cust_svc;
use FS::cust_main;
use FS::inventory_item;
use FS::inventory_class;
+use FS::NetworkMonitoringSystem;
@ISA = qw( FS::cust_main_Mixin FS::Record );
$self->label(@_);
}
+sub cust_main {
+ my $self = shift;
+ (($self->cust_svc || return)->cust_pkg || return)->cust_main || return
+}
+
+sub cust_linked {
+ my $self = shift;
+ defined($self->cust_main);
+}
+
=item check
Checks the validity of fields in this record.
If I<jobnum> is set to an array reference, the jobnums of any export jobs will
be added to the referenced array.
-If I<child_objects> is set to an array reference of FS::tablename objects (for
-example, FS::acct_snarf objects), they will have their svcnum field set and
-will be inserted after this record, but before any exports are run. Each
-element of the array can also optionally be a two-element array reference
-containing the child object and the name of an alternate field to be filled in
-with the newly-inserted svcnum, for example C<[ $svc_forward, 'srcsvc' ]>
+If I<child_objects> is set to an array reference of FS::tablename objects
+(for example, FS::svc_export_machine or FS::acct_snarf objects), they
+will have their svcnum field set and will be inserted after this record,
+but before any exports are run. Each element of the array can also
+optionally be a two-element array reference containing the child object
+and the name of an alternate field to be filled in with the newly-inserted
+svcnum, for example C<[ $svc_forward, 'srcsvc' ]>
If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
jobnums), all provisioning jobs will have a dependancy on the supplied
my $svcnum = $self->svcnum;
my $cust_svc = $svcnum ? qsearchs('cust_svc',{'svcnum'=>$self->svcnum}) : '';
+ my $inserted_cust_svc = 0;
#unless ( $svcnum ) {
if ( !$svcnum or !$cust_svc ) {
$cust_svc = new FS::cust_svc ( {
$dbh->rollback if $oldAutoCommit;
return $error;
}
+ $inserted_cust_svc = 1;
$svcnum = $self->svcnum($cust_svc->svcnum);
} else {
#$cust_svc = qsearchs('cust_svc',{'svcnum'=>$self->svcnum});
|| $self->preinsert_hook
|| $self->SUPER::insert;
if ( $error ) {
+ if ( $inserted_cust_svc ) {
+ my $derror = $cust_svc->delete;
+ die $derror if $derror;
+ }
$dbh->rollback if $oldAutoCommit;
return $error;
}
}
+ my $nms_ip_error = $self->nms_ip_insert;
+ if ( $nms_ip_error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error queuing IP insert: $nms_ip_error";
+ }
+
if ( exists $options{'jobnums'} ) {
push @{ $options{'jobnums'} }, @jobnums;
}
sub _check_duplcate { ''; }
sub preinsert_hook { ''; }
sub table_dupcheck_fields { (); }
+sub predelete_hook { ''; }
+sub predelete_hook_first { ''; }
=item delete [ , OPTION => VALUE ... ]
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
- my $error = $self->SUPER::delete
+ my $error = $self->predelete_hook_first
+ || $self->SUPER::delete
|| $self->export('delete', @$export_args)
|| $self->return_inventory
+ || $self->predelete_hook
|| $self->cust_svc->delete
;
if ( $error ) {
'';
}
+=item expire DATE
+
+Currently this will only run expire exports if any are attached
+
+=cut
+
+sub expire {
+ my($self,$date) = (shift,shift);
+
+ return 'Expire date must be specified' unless $date;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $export_args = [$date];
+ my $error = $self->export('expire', @$export_args);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
=item replace [ OLD_RECORD ] [ HASHREF | OPTION => VALUE ]
Replaces OLD_RECORD with this one. If there is an error, returns the error,
otherwise returns false.
+Currently available options are: I<child_objects>, I<export_args> and
+I<depend_jobnum>.
+
+If I<child_objects> is set to an array reference of FS::tablename objects
+(for example, FS::svc_export_machine or FS::acct_snarf objects), they
+will have their svcnum field set and will be inserted or replaced after
+this record, but before any exports are run. Each element of the array
+can also optionally be a two-element array reference containing the
+child object and the name of an alternate field to be filled in with
+the newly-inserted svcnum, for example C<[ $svc_forward, 'srcsvc' ]>
+
+If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
+jobnums), all provisioning jobs will have a dependancy on the supplied
+jobnum(s) (they will not run until the specific job(s) complete(s)).
+
+If I<export_args> is set to an array reference, the referenced list will be
+passed to export commands.
+
=cut
sub replace {
? shift
: { @_ };
+ my $objects = $options->{'child_objects'} || [];
+
+ my @jobnums = ();
+ local $FS::queue::jobnums = \@jobnums;
+ warn "[$me] replace: set \$FS::queue::jobnums to $FS::queue::jobnums\n"
+ if $DEBUG;
+ my $depend_jobnums = $options->{'depend_jobnum'} || [];
+ $depend_jobnums = [ $depend_jobnums ] unless ref($depend_jobnums);
+
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
local $SIG{QUIT} = 'IGNORE';
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
- my $error = $new->set_auto_inventory;
+ my $error = $new->set_auto_inventory($old);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
return $error;
}
+ foreach my $object ( @$objects ) {
+ my($field, $obj);
+ if ( ref($object) eq 'ARRAY' ) {
+ ($obj, $field) = @$object;
+ } else {
+ $obj = $object;
+ $field = 'svcnum';
+ }
+ $obj->$field($new->svcnum);
+
+ my $oldobj = qsearchs( $obj->table, {
+ $field => $new->svcnum,
+ map { $_ => $obj->$_ } $obj->_svc_child_partfields,
+ });
+
+ if ( $oldobj ) {
+ my $pkey = $oldobj->primary_key;
+ $obj->$pkey($oldobj->$pkey);
+ $obj->replace($oldobj);
+ } else {
+ $error = $obj->insert;
+ }
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
#new-style exports!
unless ( $noexport_hack ) {
+ warn "[$me] replace: \$FS::queue::jobnums is $FS::queue::jobnums\n"
+ if $DEBUG;
+
my $export_args = $options->{'export_args'} || [];
#not quite false laziness, but same pattern as FS::svc_acct::replace and
}
}
+ foreach my $depend_jobnum ( @$depend_jobnums ) {
+ warn "[$me] inserting dependancies on supplied job $depend_jobnum\n"
+ if $DEBUG;
+ foreach my $jobnum ( @jobnums ) {
+ my $queue = qsearchs('queue', { 'jobnum' => $jobnum } );
+ warn "[$me] inserting dependancy for job $jobnum on $depend_jobnum\n"
+ if $DEBUG;
+ my $error = $queue->depend_insert($depend_jobnum);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error queuing job dependancy: $error";
+ }
+ }
+ }
+
}
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
sub set_auto_inventory {
my $self = shift;
+ my $old = @_ ? shift : '';
my $error =
$self->ut_numbern('svcnum')
$hash{'item'} = $self->getfield($field);
}
+ my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null' => 1,
+ 'table' => 'inventory_item',
+ );
+
my $inventory_item = qsearchs({
'table' => 'inventory_item',
'hashref' => \%hash,
- 'extra_sql' => 'LIMIT 1 FOR UPDATE',
+ 'extra_sql' => "AND $agentnums_sql",
+ 'order_by' => 'ORDER BY ( agentnum IS NULL ) '. #agent inventory first
+ ' LIMIT 1 FOR UPDATE',
});
unless ( $inventory_item ) {
qsearchs('inventory_class', { 'classnum' => $classnum } );
return "Can't find inventory_class.classnum $classnum"
unless $inventory_class;
- return "Out of ". $inventory_class->classname. "s\n"; #Lingua:: BS
- #for pluralizing
+ return "Out of ". PL_N($inventory_class->classname);
}
next if $columnflag eq 'M' && $inventory_item->svcnum == $self->svcnum;
$self->setfield( $field, $inventory_item->item );
#if $columnflag eq 'A' && $self->$field() eq '';
+ if ( $old && $old->$field() && $old->$field() ne $self->$field() ) {
+ my $old_inv = qsearchs({
+ 'table' => 'inventory_item',
+ 'hashref' => { 'classnum' => $classnum,
+ 'svcnum' => $old->svcnum,
+ },
+ 'extra_sql' => ' AND '.
+ '( ( svc_field IS NOT NULL AND svc_field = '.$dbh->quote($field).' )'.
+ ' OR ( svc_field IS NULL AND item = '. dbh->quote($old->$field).' )'.
+ ')',
+ });
+ if ( $old_inv ) {
+ $old_inv->svcnum('');
+ $old_inv->svc_field('');
+ my $oerror = $old_inv->replace;
+ if ( $oerror ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error unprovisioning inventory: $oerror";
+ }
+ } else {
+ warn "old inventory_item not found for $field ". $self->$field;
+ }
+ }
+
$inventory_item->svcnum( $self->svcnum );
+ $inventory_item->svc_field( $field );
my $ierror = $inventory_item->replace();
if ( $ierror ) {
$dbh->rollback if $oldAutoCommit;
foreach my $inventory_item ( $self->inventory_item ) {
$inventory_item->svcnum('');
+ $inventory_item->svc_field('');
my $error = $inventory_item->replace();
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
my %defaults = ();
my $error = $self->export('getsettings', \%settings, \%defaults);
if ( $error ) {
- #XXX bubble this up better
warn "error running export_getsetings: $error";
- return ( {}, {} );
+ return ( { 'error' => $error }, {} );
}
( \%settings, \%defaults );
}
+=item export_getstatus
+
+Runs export_getstatus callbacks and returns a two item list consisting of an
+HTML status and a status hashref.
+
+=cut
+
+sub export_getstatus {
+ my $self = shift;
+ my $html = '';
+ my %hash = ();
+ my $error = $self->export('getstatus', \$html, \%hash);
+ if ( $error ) {
+ warn "error running export_getstatus: $error";
+ return ( '', { 'error' => $error } );
+ }
+ ( $html, \%hash );
+}
+
+=item export_setstatus
+
+Runs export_setstatus callbacks. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub export_setstatus {
+ my( $self, @args ) = @_;
+ my $error = $self->export('setstatus', @args);
+ if ( $error ) {
+ warn "error running export_setstatus: $error";
+ return $error;
+ }
+ '';
+}
+
+
=item export HOOK [ EXPORT_ARGS ]
Runs the provided export hook (i.e. "suspend", "unsuspend") for this service.
shift;
}
+=item find_duplicates MODE FIELDS...
+
+Method used by _check_duplicate routines to find services with duplicate
+values in specified fields. Set MODE to 'global' to search across all
+services, or 'export' to limit to those that share one or more exports
+with this service. FIELDS is a list of field names; only services
+matching in all fields will be returned. Empty fields will be skipped.
+
+=cut
+
+sub find_duplicates {
+ my $self = shift;
+ my $mode = shift;
+ my @fields = @_;
+
+ my %search = map { $_ => $self->getfield($_) }
+ grep { length($self->getfield($_)) } @fields;
+ return () if !%search;
+ my @dup = grep { ! $self->svcnum or $_->svcnum != $self->svcnum }
+ qsearch( $self->table, \%search );
+ return () if !@dup;
+ return @dup if $mode eq 'global';
+ die "incorrect find_duplicates mode '$mode'" if $mode ne 'export';
+
+ my $exports = FS::part_export::export_info($self->table);
+ my %conflict_svcparts;
+ my $part_svc = $self->part_svc;
+ foreach my $part_export ( $part_svc->part_export ) {
+ %conflict_svcparts = map { $_->svcpart => 1 } $part_export->export_svc;
+ }
+ return grep { $conflict_svcparts{$_->cust_svc->svcpart} } @dup;
+}
+
+=item getstatus_html
+
+=cut
+
+sub getstatus_html {
+ my $self = shift;
+
+ my $part_svc = $self->cust_svc->part_svc;
+
+ my $html = '';
+
+ foreach my $export ( grep $_->can('export_getstatus'), $part_svc->part_export ) {
+ my $export_html = '';
+ my %hash = ();
+ $export->export_getstatus( $self, \$export_html, \%hash );
+ $html .= $export_html;
+ }
+
+ $html;
+
+}
+
+=item nms_ip_insert
+
+=cut
+
+sub nms_ip_insert {
+ my $self = shift;
+ my $conf = new FS::Conf;
+ return '' unless grep { $self->table eq $_ }
+ $conf->config('nms-auto_add-svc_ips');
+ my $ip_field = $self->table_info->{'ip_field'};
+
+ my $queue = FS::queue->new( {
+ 'job' => 'FS::NetworkMonitoringSystem::queued_add_router',
+ 'svcnum' => $self->svcnum,
+ } );
+ $queue->insert( 'FS::NetworkMonitoringSystem',
+ $self->$ip_field(),
+ $conf->config('nms-auto_add-community')
+ );
+}
+
+=item nms_delip
+
+=cut
+
+sub nms_ip_delete {
+#XXX not yet implemented
+}
+
=back
=head1 BUGS