package FS::svc_Common;
use strict;
-use vars qw( @ISA $noexport_hack $DEBUG $me );
-use Carp qw( cluck carp croak ); #specify cluck have to specify them all..
+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;
$me = '[FS::svc_Common]';
$DEBUG = 0;
+$overlimit_missing_cust_svc_nonfatal_kludge = 0;
+
=head1 NAME
FS::svc_Common - Object method for all svc_ records
$self->svcpart($cust_svc->svcpart);
}
- my $error = $self->set_auto_inventory
+ 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;
}
#fallbacks
+sub preinsert_hook_first { ''; }
sub _check_duplcate { ''; }
+sub preinsert_hook { ''; }
sub table_dupcheck_fields { (); }
=item delete [ , OPTION => VALUE ... ]
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;
}
+=item svc_pbx
+
+Returns the FS::svc_pbx record for this service, if any (see L<FS::svc_pbx>).
+
+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<pkgnum> I<svcpart>
+
+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<FS::part_svc>).
+Sets any fields which auto-populate from inventory (see L<FS::part_svc>), 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')
#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);
+ }
+
+ 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',
+ });
- $self->setfield( $field, $inventory_item->item );
+ 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;
$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.
sub overlimit {
my $self = shift;
- $self->cust_svc->overlimit(@_);
+ #$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