X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fsvc_Common.pm;h=3d8fe16a5cbf4ff98575951e3141d7f9e529bda2;hp=a67504a5b49d8dc0c9ba091eed51543b2083f92a;hb=74e058c8a010ef6feb539248a550d0bb169c1e94;hpb=f274814c7cde3681578ca594a2b00475370e4c92 diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index a67504a5b..3d8fe16a5 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -330,6 +330,8 @@ sub preinsert_hook_first { ''; } sub _check_duplcate { ''; } sub preinsert_hook { ''; } sub table_dupcheck_fields { (); } +sub predelete_hook { ''; } +sub predelete_hook_first { ''; } =item delete [ , OPTION => VALUE ... ] @@ -356,9 +358,11 @@ sub delete { 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 ) { @@ -371,6 +375,40 @@ sub delete { ''; } +=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, @@ -401,7 +439,7 @@ sub replace { 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; @@ -586,15 +624,115 @@ sub part_svc { } +=item svc_pbx + +Returns the FS::svc_pbx record for this service, if any (see L). + +Only makes sense if the service has a pbxsvc field (currently, svc_phone and +svc_acct). + +=cut + +# XXX FS::h_svc_{acct,phone} could have a history-aware svc_pbx override + +sub svc_pbx { + my $self = shift; + return '' unless $self->pbxsvc; + qsearchs( 'svc_pbx', { 'svcnum' => $self->pbxsvc } ); +} + +=item pbx_title + +Returns the title of the FS::svc_pbx record associated with this service, if +any. + +Only makes sense if the service has a pbxsvc field (currently, svc_phone and +svc_acct). + +=cut + +sub pbx_title { + my $self = shift; + my $svc_pbx = $self->svc_pbx or return ''; + $svc_pbx->title; +} + +=item pbx_select_hash %OPTIONS + +Can be called as an object method or a class method. + +Returns a hash SVCNUM => TITLE ... representing the PBXes this customer +that may be associated with this service. + +Currently available options are: I I + +Only makes sense if the service has a pbxsvc field (currently, svc_phone and +svc_acct). + +=cut + +#false laziness w/svc_acct::domain_select_hash +sub pbx_select_hash { + my ($self, %options) = @_; + my %pbxes = (); + my $part_svc; + my $cust_pkg; + + if (ref($self)) { + $part_svc = $self->part_svc; + $cust_pkg = $self->cust_svc->cust_pkg + if $self->cust_svc; + } + + $part_svc = qsearchs('part_svc', { 'svcpart' => $options{svcpart} }) + if $options{'svcpart'}; + + $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $options{pkgnum} }) + if $options{'pkgnum'}; + + if ($part_svc && ( $part_svc->part_svc_column('pbxsvc')->columnflag eq 'S' + || $part_svc->part_svc_column('pbxsvc')->columnflag eq 'F')) { + %pbxes = map { $_->svcnum => $_->title } + map { qsearchs('svc_pbx', { 'svcnum' => $_ }) } + split(',', $part_svc->part_svc_column('pbxsvc')->columnvalue); + } elsif ($cust_pkg) { # && !$conf->exists('svc_acct-alldomains') ) { + %pbxes = map { $_->svcnum => $_->title } + map { qsearchs('svc_pbx', { 'svcnum' => $_->svcnum }) } + map { qsearch('cust_svc', { 'pkgnum' => $_->pkgnum } ) } + qsearch('cust_pkg', { 'custnum' => $cust_pkg->custnum }); + } else { + #XXX agent-virt + %pbxes = map { $_->svcnum => $_->title } qsearch('svc_pbx', {} ); + } + + if ($part_svc && $part_svc->part_svc_column('pbxsvc')->columnflag eq 'D') { + my $svc_pbx = qsearchs('svc_pbx', + { 'svcnum' => $part_svc->part_svc_column('pbxsvc')->columnvalue } ); + if ( $svc_pbx ) { + $pbxes{$svc_pbx->svcnum} = $svc_pbx->title; + } else { + warn "unknown svc_pbx.svcnum for part_svc_column pbxsvc: ". + $part_svc->part_svc_column('pbxsvc')->columnvalue; + + } + } + + (%pbxes); + +} + =item set_auto_inventory -Sets any fields which auto-populate from inventory (see L). +Sets any fields which auto-populate from inventory (see L), and +also check any manually populated inventory fields. + If there is an error, returns the error, otherwise returns false. =cut sub set_auto_inventory { my $self = shift; + my $old = @_ ? shift : ''; my $error = $self->ut_numbern('svcnum') @@ -618,39 +756,76 @@ sub set_auto_inventory { #set default/fixed/whatever fields from part_svc my $table = $self->table; foreach my $field ( grep { $_ ne 'svcnum' } $self->fields ) { + my $part_svc_column = $part_svc->part_svc_column($field); - if ( $part_svc_column->columnflag eq 'A' && $self->$field() eq '' ) { - - my $classnum = $part_svc_column->columnvalue; - my $inventory_item = qsearchs({ - 'table' => 'inventory_item', - 'hashref' => { 'classnum' => $classnum, - 'svcnum' => '', - }, - 'extra_sql' => 'LIMIT 1 FOR UPDATE', - }); + my $columnflag = $part_svc_column->columnflag; + next unless $columnflag =~ /^[AM]$/; - unless ( $inventory_item ) { - $dbh->rollback if $oldAutoCommit; - my $inventory_class = - 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 - } + next if $columnflag eq 'A' && $self->$field() ne ''; - $inventory_item->svcnum( $self->svcnum ); - my $ierror = $inventory_item->replace(); - if ( $ierror ) { - $dbh->rollback if $oldAutoCommit; - return "Error provisioning inventory: $ierror"; - - } + my $classnum = $part_svc_column->columnvalue; + my %hash = ( 'classnum' => $classnum ); + + if ( $columnflag eq 'A' && $self->$field() eq '' ) { + $hash{'svcnum'} = ''; + } elsif ( $columnflag eq 'M' ) { + return "Select inventory item for $field" unless $self->getfield($field); + $hash{'item'} = $self->getfield($field); + } - $self->setfield( $field, $inventory_item->item ); + my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null' => 1, + 'table' => 'inventory_item', + ); + my $inventory_item = qsearchs({ + 'table' => 'inventory_item', + 'hashref' => \%hash, + 'extra_sql' => "AND $agentnums_sql", + 'order_by' => 'ORDER BY ( agentnum IS NULL ) '. #agent inventory first + ' LIMIT 1 FOR UPDATE', + }); + + unless ( $inventory_item ) { + $dbh->rollback if $oldAutoCommit; + my $inventory_class = + 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 } + + next if $columnflag eq 'M' && $inventory_item->svcnum == $self->svcnum; + + $self->setfield( $field, $inventory_item->item ); + #if $columnflag eq 'A' && $self->$field() eq ''; + + $inventory_item->svcnum( $self->svcnum ); + my $ierror = $inventory_item->replace(); + if ( $ierror ) { + $dbh->rollback if $oldAutoCommit; + return "Error provisioning inventory: $ierror"; + } + + if ( $old && $old->$field() && $old->$field() ne $self->$field() ) { + my $old_inv = qsearchs({ + 'table' => 'inventory_item', + 'hashref' => { 'classnum' => $classnum, + 'svcnum' => $old->svcnum, + 'item' => $old->$field(), + }, + }); + if ( $old_inv ) { + $old_inv->svcnum(''); + my $oerror = $old_inv->replace; + if ( $oerror ) { + $dbh->rollback if $oldAutoCommit; + return "Error unprovisioning inventory: $oerror"; + } + } + } + } $dbh->commit or die $dbh->errstr if $oldAutoCommit; @@ -757,6 +932,25 @@ sub export_links { $return; } +=item export_getsettings + +Runs export_getsettings callbacks and returns the two hashrefs. + +=cut + +sub export_getsettings { + my $self = shift; + my %settings = (); + my %defaults = (); + my $error = $self->export('getsettings', \%settings, \%defaults); + if ( $error ) { + #XXX bubble this up better + warn "error running export_getsetings: $error"; + return ( {}, {} ); + } + ( \%settings, \%defaults ); +} + =item export HOOK [ EXPORT_ARGS ] Runs the provided export hook (i.e. "suspend", "unsuspend") for this service. @@ -857,6 +1051,42 @@ sub clone_kludge_unsuspend { 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; +} + + + + =back =head1 BUGS