package FS::svc_Common;
use strict;
-use vars qw( @ISA $noexport_hack $DEBUG $me );
-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;
$me = '[FS::svc_Common]';
$DEBUG = 0;
+$overlimit_missing_cust_svc_nonfatal_kludge = 0;
+
=head1 NAME
FS::svc_Common - Object method for all svc_ records
=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 {
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.
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 insert {
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';
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 ) {
'pkgnum' => $self->pkgnum,
'svcpart' => $self->svcpart,
} );
- $error = $cust_svc->insert;
+ my $error = $cust_svc->insert;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
$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->set_auto_inventory
+ || $self->check
+ || $self->_check_duplicate
+ || $self->SUPER::insert;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
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.
'';
}
-=item delete
+#fallbacks
+sub _check_duplcate { ''; }
+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.
sub delete {
my $self = shift;
- my $error;
+ my %options = @_;
+ my $export_args = $options{'export_args'} || [];
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
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;
'';
}
-=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.
=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';
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
- # We absolutely have to have an old vs. new record to make this work.
- if ( !defined($old) ) {
- warn "[$me] replace called with no arguments; autoloading old record\n"
- if $DEBUG;
- my $primary_key = $new->dbdef_table->primary_key;
- if ( $primary_key ) {
- $old = qsearchs($new->table, { $primary_key => $new->$primary_key() } )
- or croak "can't find ". $new->table. ".$primary_key ".
- $new->$primary_key();
- } else {
- croak $new->table. " has no primary key; pass old record as argument";
- }
+ my $error = $new->set_auto_inventory;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
}
- my $error = $new->set_auto_inventory;
+ #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;
#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
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.
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.
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.
'';
}
-
=item setfixed
Sets any fixed fields for this service (see L<FS::part_svc>). If there is an
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
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';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
+=item unsuspend
- #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";
- }
- }
- }
+Runs export_unsuspend callbacks.
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
- '';
+=cut
+sub unsuspend {
+ my $self = shift;
+ my %options = @_;
+ my $export_args = $options{'export_args'} || [];
+ $self->export('unsuspend', @$export_args);
}
-=item unsuspend
+=item export_links
-Runs export_unsuspend callbacks.
+Runs export_links callbacks and returns the links.
=cut
-sub unsuspend {
+sub export_links {
my $self = shift;
+ my $return = [];
+ $self->export('links', $return);
+ $return;
+}
+
+=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';
#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";
}
}
}
+=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 this
The setfixed method return value.
+B<export> method isn't used by insert and replace methods yet.
+
=head1 SEE ALSO
L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>, schema.html