X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fsvc_Common.pm;h=ee270caff27617ac1efe6894a79d538b39367b17;hp=f2e1b9adb82f8257181bb3050170b036bc8a43b6;hb=cc647ae934abe5c9d9a5fc12f020adfc566349d9;hpb=947c955be56140c4a10b16345c1b15c44b02070a diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index f2e1b9adb..ee270caff 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -1,8 +1,10 @@ package FS::svc_Common; use strict; -use vars qw( @ISA $noexport_hack $DEBUG ); -use Carp; +use vars qw( @ISA $noexport_hack $DEBUG $me + $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 FS::Record qw( qsearch qsearchs fields dbh ); use FS::cust_main_Mixin; use FS::cust_svc; @@ -14,7 +16,10 @@ use FS::inventory_class; @ISA = qw( FS::cust_main_Mixin FS::Record ); -$DEBUG = 1; +$me = '[FS::svc_Common]'; +$DEBUG = 0; + +$overlimit_missing_cust_svc_nonfatal_kludge = 0; =head1 NAME @@ -35,6 +40,29 @@ inherit from, i.e. FS::svc_acct. FS::svc_Common inherits from FS::Record. =over 4 +=item search_sql_field FIELD STRING + +Class method which returns an SQL fragment to search for STRING in FIELD. + +It is now case-insensitive by default. + +=cut + +sub search_sql_field { + my( $class, $field, $string ) = @_; + my $table = $class->table; + my $q_string = dbh->quote($string); + "LOWER($table.$field) = LOWER($q_string)"; +} + +#fallback for services that don't provide a search... +sub search_sql { + #my( $class, $string ) = @_; + '1 = 0'; #false +} + +=item new + =cut sub new { @@ -51,7 +79,10 @@ sub new { #$self->{'Hash'} = shift; my $newhash = shift; $self->{'Hash'} = { map { $_ => $newhash->{$_} } qw(svcnum svcpart) }; - $self->setdefault; + + $self->setdefault( $self->_fieldhandlers ) + unless $self->svcnum; + $self->{'Hash'}{$_} = $newhash->{$_} foreach grep { defined($newhash->{$_}) && length($newhash->{$_}) } keys %$newhash; @@ -69,6 +100,9 @@ sub new { $self; } +#empty default +sub _fieldhandlers { {}; } + sub virtual_fields { # This restricts the fields based on part_svc_column and the svcpart of @@ -100,13 +134,31 @@ sub virtual_fields { my %flags = map { $_->columnname, $_->columnflag } ( qsearch ('part_svc_column', { svcpart => $svcpart } ) ); - return grep { not ($flags{$_} eq 'X') } @vfields; + return grep { not ( defined($flags{$_}) && $flags{$_} eq 'X') } @vfields; } else { # Case 3 return @vfields; } return (); } +=item label + +svc_Common provides a fallback label subroutine that just returns the svcnum. + +=cut + +sub label { + my $self = shift; + cluck "warning: ". ref($self). " not loaded or missing label method; ". + "using svcnum"; + $self->svcnum; +} + +sub label_long { + my $self = shift; + $self->label(@_); +} + =item check Checks the validity of fields in this record. @@ -146,23 +198,25 @@ If I 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 is set to an array reference, the referenced list will be +passed to export commands. + =cut sub insert { my $self = shift; my %options = @_; - warn "FS::svc_Common::insert called with options ". - join(', ', map { "$_: $options{$_}" } keys %options ). "\n" - if $DEBUG; + warn "[$me] insert called with options ". + join(', ', map { "$_: $options{$_}" } keys %options ). "\n" + if $DEBUG; my @jobnums = (); local $FS::queue::jobnums = \@jobnums; - warn "FS::svc_Common::insert: set \$FS::queue::jobnums to $FS::queue::jobnums" + warn "[$me] insert: set \$FS::queue::jobnums to $FS::queue::jobnums\n" if $DEBUG; my $objects = $options{'child_objects'} || []; my $depend_jobnums = $options{'depend_jobnum'} || []; $depend_jobnums = [ $depend_jobnums ] unless ref($depend_jobnums); - my $error; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -175,9 +229,6 @@ sub insert { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - $error = $self->check; - return $error if $error; - my $svcnum = $self->svcnum; my $cust_svc = $svcnum ? qsearchs('cust_svc',{'svcnum'=>$self->svcnum}) : ''; #unless ( $svcnum ) { @@ -188,7 +239,7 @@ sub insert { 'pkgnum' => $self->pkgnum, 'svcpart' => $self->svcpart, } ); - $error = $cust_svc->insert; + my $error = $cust_svc->insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -204,13 +255,12 @@ sub insert { $self->svcpart($cust_svc->svcpart); } - $error = $self->set_auto_inventory; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - - $error = $self->SUPER::insert; + my $error = $self->preinsert_hook_first + || $self->set_auto_inventory + || $self->check + || $self->_check_duplicate + || $self->preinsert_hook + || $self->SUPER::insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -235,11 +285,13 @@ sub insert { #new-style exports! unless ( $noexport_hack ) { - warn "FS::svc_Common::insert: \$FS::queue::jobnums is $FS::queue::jobnums" + warn "[$me] insert: \$FS::queue::jobnums is $FS::queue::jobnums\n" if $DEBUG; + my $export_args = $options{'export_args'} || []; + foreach my $part_export ( $self->cust_svc->part_svc->part_export ) { - my $error = $part_export->export_insert($self); + my $error = $part_export->export_insert($self, @$export_args); if ( $error ) { $dbh->rollback if $oldAutoCommit; return "exporting to ". $part_export->exporttype. @@ -248,11 +300,11 @@ sub insert { } foreach my $depend_jobnum ( @$depend_jobnums ) { - warn "inserting dependancies on supplied job $depend_jobnum\n" + warn "[$me] inserting dependancies on supplied job $depend_jobnum\n" if $DEBUG; foreach my $jobnum ( @jobnums ) { my $queue = qsearchs('queue', { 'jobnum' => $jobnum } ); - warn "inserting dependancy for job $jobnum on $depend_jobnum\n" + warn "[$me] inserting dependancy for job $jobnum on $depend_jobnum\n" if $DEBUG; my $error = $queue->depend_insert($depend_jobnum); if ( $error ) { @@ -273,7 +325,13 @@ sub insert { ''; } -=item delete +#fallbacks +sub preinsert_hook_first { ''; } +sub _check_duplcate { ''; } +sub preinsert_hook { ''; } +sub table_dupcheck_fields { (); } + +=item delete [ , OPTION => VALUE ... ] Deletes this account from the database. If there is an error, returns the error, otherwise returns false. @@ -284,7 +342,8 @@ The corresponding FS::cust_svc record will be deleted as well. sub delete { my $self = shift; - my $error; + my %options = @_; + my $export_args = $options{'export_args'} || []; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -293,35 +352,15 @@ sub delete { local $SIG{TSTP} = 'IGNORE'; local $SIG{PIPE} = 'IGNORE'; - my $svcnum = $self->svcnum; - my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; my $dbh = dbh; - $error = $self->SUPER::delete; - return $error if $error; - - #new-style exports! - unless ( $noexport_hack ) { - foreach my $part_export ( $self->cust_svc->part_svc->part_export ) { - $error = $part_export->export_delete($self); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "exporting to ". $part_export->exporttype. - " (transaction rolled back): $error"; - } - } - } - - $error = $self->return_inventory; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "error returning inventory: $error"; - } - - my $cust_svc = $self->cust_svc; - $error = $cust_svc->delete; + my $error = $self->SUPER::delete + || $self->export('delete', @$export_args) + || $self->return_inventory + || $self->cust_svc->delete + ; if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -332,7 +371,7 @@ sub delete { ''; } -=item replace OLD_RECORD +=item replace [ OLD_RECORD ] [ HASHREF | OPTION => VALUE ] Replaces OLD_RECORD with this one. If there is an error, returns the error, otherwise returns false. @@ -340,7 +379,16 @@ otherwise returns false. =cut sub replace { - my ($new, $old) = (shift, shift); + my $new = shift; + + my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') ) + ? shift + : $new->replace_old; + + my $options = + ( ref($_[0]) eq 'HASH' ) + ? shift + : { @_ }; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -359,6 +407,25 @@ sub replace { return $error; } + #redundant, but so any duplicate fields are maniuplated as appropriate + # (svc_phone.phonenum) + $error = $new->check; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + #if ( $old->username ne $new->username || $old->domsvc != $new->domsvc ) { + if ( grep { $old->$_ ne $new->$_ } $new->table_dupcheck_fields ) { + + $new->svcpart( $new->cust_svc->svcpart ) unless $new->svcpart; + $error = $new->_check_duplicate; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + $error = $new->SUPER::replace($old); if ($error) { $dbh->rollback if $oldAutoCommit; @@ -368,6 +435,8 @@ sub replace { #new-style exports! unless ( $noexport_hack ) { + my $export_args = $options->{'export_args'} || []; + #not quite false laziness, but same pattern as FS::svc_acct::replace and #FS::part_export::sqlradius::_export_replace. List::Compare or something #would be useful but too much of a pain in the ass to deploy @@ -383,7 +452,7 @@ sub replace { foreach my $delete_part_export ( grep { ! $new_exportnum{$_->exportnum} } @old_part_export ) { - my $error = $delete_part_export->export_delete($old); + my $error = $delete_part_export->export_delete($old, @$export_args); if ( $error ) { $dbh->rollback if $oldAutoCommit; return "error deleting, export to ". $delete_part_export->exporttype. @@ -394,7 +463,8 @@ sub replace { foreach my $replace_part_export ( grep { $old_exportnum{$_->exportnum} } @new_part_export ) { - my $error = $replace_part_export->export_replace($new,$old); + my $error = + $replace_part_export->export_replace( $new, $old, @$export_args); if ( $error ) { $dbh->rollback if $oldAutoCommit; return "error exporting to ". $replace_part_export->exporttype. @@ -405,7 +475,7 @@ sub replace { foreach my $insert_part_export ( grep { ! $old_exportnum{$_->exportnum} } @new_part_export ) { - my $error = $insert_part_export->export_insert($new); + my $error = $insert_part_export->export_insert($new, @$export_args ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return "error inserting export to ". $insert_part_export->exporttype. @@ -419,7 +489,6 @@ sub replace { ''; } - =item setfixed Sets any fixed fields for this service (see L). If there is an @@ -470,7 +539,7 @@ sub setx { my $self = shift; my $x = shift; my @x = ref($x) ? @$x : ($x); - my %coderef = @_ ? shift : {}; + my $coderef = scalar(@_) ? shift : $self->_fieldhandlers; my $error = $self->ut_numbern('svcnum') @@ -478,7 +547,7 @@ sub setx { return $error if $error; my $part_svc = $self->part_svc; - return "Unkonwn svcpart" unless $part_svc; + return "Unknown svcpart" unless $part_svc; #set default/fixed/whatever fields from part_svc @@ -490,11 +559,9 @@ sub setx { my $columnname = $part_svc_column->columnname; my $columnvalue = $part_svc_column->columnvalue; - if ( exists( $coderef{columnname} ) ) { - &{ $coderef{$columnname} }( $self, $columnvalue); - } else { - $self->setfield( $columnname, $columnvalue ); - } + $columnvalue = &{ $coderef->{$columnname} }( $self, $columnvalue ) + if exists( $coderef->{$columnname} ); + $self->setfield( $columnname, $columnvalue ); } @@ -519,6 +586,103 @@ 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). @@ -659,43 +823,66 @@ Runs export_suspend callbacks. sub suspend { my $self = shift; + my %options = @_; + my $export_args = $options{'export_args'} || []; + $self->export('suspend', @$export_args); +} - local $SIG{HUP} = 'IGNORE'; - local $SIG{INT} = 'IGNORE'; - local $SIG{QUIT} = 'IGNORE'; - local $SIG{TERM} = 'IGNORE'; - local $SIG{TSTP} = 'IGNORE'; - local $SIG{PIPE} = 'IGNORE'; +=item unsuspend - my $oldAutoCommit = $FS::UID::AutoCommit; - local $FS::UID::AutoCommit = 0; - my $dbh = dbh; +Runs export_unsuspend callbacks. - #new-style exports! - unless ( $noexport_hack ) { - foreach my $part_export ( $self->cust_svc->part_svc->part_export ) { - my $error = $part_export->export_suspend($self); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "error exporting to ". $part_export->exporttype. - " (transaction rolled back): $error"; - } - } - } +=cut - $dbh->commit or die $dbh->errstr if $oldAutoCommit; - ''; +sub unsuspend { + my $self = shift; + my %options = @_; + my $export_args = $options{'export_args'} || []; + $self->export('unsuspend', @$export_args); +} + +=item export_links + +Runs export_links callbacks and returns the links. + +=cut +sub export_links { + my $self = shift; + my $return = []; + $self->export('links', $return); + $return; } -=item unsuspend +=item export_getsettings -Runs export_unsuspend callbacks. +Runs export_getsettings callbacks and returns the two hashrefs. =cut -sub unsuspend { +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. + +=cut + +sub export { + my( $self, $method ) = ( shift, shift ); + + $method = "export_$method" unless $method =~ /^export_/; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -711,10 +898,11 @@ sub unsuspend { #new-style exports! unless ( $noexport_hack ) { foreach my $part_export ( $self->cust_svc->part_svc->part_export ) { - my $error = $part_export->export_unsuspend($self); + next unless $part_export->can($method); + my $error = $part_export->$method($self, @_); if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "error exporting to ". $part_export->exporttype. + return "error exporting $method event to ". $part_export->exporttype. " (transaction rolled back): $error"; } } @@ -725,11 +913,38 @@ sub unsuspend { } +=item overlimit + +Sets or retrieves overlimit date. + +=cut + +sub overlimit { + my $self = shift; + #$self->cust_svc->overlimit(@_); + my $cust_svc = $self->cust_svc; + unless ( $cust_svc ) { #wtf? + my $error = "$me overlimit: missing cust_svc record for svc_acct svcnum ". + $self->svcnum; + if ( $overlimit_missing_cust_svc_nonfatal_kludge ) { + cluck "$error; continuing anyway as requested"; + return ''; + } else { + confess $error; + } + } + $cust_svc->overlimit(@_); +} + =item cancel -Stub - returns false (no error) so derived classes don't need to define these +Stub - returns false (no error) so derived classes don't need to define this methods. Called by the cancel method of FS::cust_pkg (see L). +This method is called *before* the deletion step which actually deletes the +services. This method should therefore only be used for "pre-deletion" +cancellation steps, if necessary. + =cut sub cancel { ''; } @@ -764,6 +979,8 @@ sub clone_kludge_unsuspend { The setfixed method return value. +B method isn't used by insert and replace methods yet. + =head1 SEE ALSO L, L, L, L, schema.html