X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_svc.pm;h=e64862f9d38cf708f157d30cb407ad3fdfbddc2e;hb=dec15062e2df6f615e42d5f87193133a3e072826;hp=6bd8cb83783bca3b11dd3f2a83df48a9cc26309b;hpb=e39c7a239fe3604445ffdef6472d0e41abca0049;p=freeside.git diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm index 6bd8cb837..e64862f9d 100644 --- a/FS/FS/cust_svc.pm +++ b/FS/FS/cust_svc.pm @@ -1,11 +1,12 @@ package FS::cust_svc; use strict; -use vars qw( @ISA $DEBUG $me $ignore_quantity ); -use Carp; +use vars qw( @ISA $DEBUG $me $ignore_quantity $conf $ticket_system ); +use Carp qw(cluck); #use Scalar::Util qw( blessed ); +use List::Util qw( max ); use FS::Conf; -use FS::Record qw( qsearch qsearchs dbh str2time_sql ); +use FS::Record qw( qsearch qsearchs dbh str2time_sql str2time_sql_closing ); use FS::cust_pkg; use FS::part_pkg; use FS::part_svc; @@ -13,6 +14,8 @@ use FS::pkg_svc; use FS::domain_record; use FS::part_export; use FS::cdr; +use FS::UI::Web; +use FS::export_cust_svc; #most FS::svc_ classes are autoloaded in svc_x emthod use FS::svc_acct; #this one is used in the cache stuff @@ -24,6 +27,21 @@ $me = '[cust_svc]'; $ignore_quantity = 0; +#ask FS::UID to run this stuff for us later +FS::UID->install_callback( sub { + $conf = new FS::Conf; + $ticket_system = $conf->config('ticket_system') +}); + +our $cache_enabled = 0; + +sub _simplecache { + my( $self, $hashref ) = @_; + if ( $cache_enabled && $hashref->{'svc'} ) { + $self->{'_svcpart'} = FS::part_svc->new($hashref); + } +} + sub _cache { my $self = shift; my ( $hashref, $cache ) = @_; @@ -69,6 +87,8 @@ The following fields are currently supported: =item svcpart - Service definition (see L) +=item agent_svcid - Optional legacy service ID + =item overlimit - date the service exceeded its usage limit =back @@ -92,6 +112,37 @@ sub table { 'cust_svc'; } Adds this service to the database. If there is an error, returns the error, otherwise returns false. +=cut + +sub insert { + my $self = shift; + + 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 $error = $self->SUPER::insert; + + #check if this releases a hold (see FS::pkg_svc provision_hold) + $error ||= $self->_check_provision_hold; + + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error if $error + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; #no error + +} + =item delete Deletes this service from the database. If there is an error, returns the @@ -100,20 +151,63 @@ record - you should probably use the B method instead. =cut +my $rt_session; + sub delete { my $self = shift; + + my $cust_pkg = $self->cust_pkg; + my $custnum = $cust_pkg->custnum if $cust_pkg; + + 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; + + # delete associated export_cust_svc + foreach my $export_cust_svc ( + qsearch('export_cust_svc',{ 'svcnum' => $self->svcnum }) + ) { + my $error = $export_cust_svc->delete; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + my $error = $self->SUPER::delete; - return $error if $error; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } - if ( FS::Conf->new->config('ticket_system') eq 'RT_Internal' ) { - FS::TicketSystem->init; - my $session = FS::TicketSystem->session; - my $links = RT::Links->new($session->{CurrentUser}); + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + if ( $ticket_system eq 'RT_Internal' ) { + unless ( $rt_session ) { + FS::TicketSystem->init; + $rt_session = FS::TicketSystem->session; + } + my $links = RT::Links->new($rt_session->{CurrentUser}); my $svcnum = $self->svcnum; $links->Limit(FIELD => 'Target', VALUE => 'freeside://freeside/cust_svc/'.$svcnum); while ( my $l = $links->Next ) { - my ($val, $msg) = $l->Delete; + my ($val, $msg); + if ( $custnum ) { + # re-link to point to the customer instead + ($val, $msg) = + $l->SetTarget('freeside://freeside/cust_main/'.$custnum); + } else { + # unlinked service + ($val, $msg) = $l->Delete; + } # can't do anything useful on error warn "error unlinking ticket $svcnum: $msg\n" if !$val; } @@ -292,11 +386,54 @@ sub replace { # } # } + #trigger a pkg_change export on pkgnum changes + if ( $new->pkgnum != $old->pkgnum ) { + my $error = $new->svc_x->export('pkg_change', $new->cust_pkg, + $old->cust_pkg, + ); + + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error if $error; + } + } # if pkgnum is changing + #my $error = $new->SUPER::replace($old, @_); my $error = $new->SUPER::replace($old); + + #trigger a relocate export on location changes + if ( $new->cust_pkg->locationnum != $old->cust_pkg->locationnum ) { + my $svc_x = $new->svc_x; + if ( $svc_x->locationnum ) { + if ( $svc_x->locationnum == $old->cust_pkg->locationnum ) { + # in this case, set the service location to be the same as the new + # package location + $svc_x->set('locationnum', $new->cust_pkg->locationnum); + # and replace it, which triggers a relocate export so we don't + # need to + $error ||= $svc_x->replace; + } else { + # the service already has a different location from its package + # so don't change it + } + } else { + # the service doesn't have a locationnum (either isn't of a type + # that has the locationnum field, or the locationnum is null and + # defaults to cust_pkg->locationnum) + # so just trigger the export here + $error ||= $new->svc_x->export('relocate', + $new->cust_pkg->cust_location, + $old->cust_pkg->cust_location, + ); + } # if ($svc_x->locationnum) + } # if this is a location change + + #check if this releases a hold (see FS::pkg_svc provision_hold) + $error ||= $new->_check_provision_hold; + if ( $error ) { $dbh->rollback if $oldAutoCommit; - return $error if $error; + return $error if $error } $dbh->commit or die $dbh->errstr if $oldAutoCommit; @@ -319,6 +456,7 @@ sub check { $self->ut_numbern('svcnum') || $self->ut_numbern('pkgnum') || $self->ut_number('svcpart') + || $self->ut_numbern('agent_svcid') || $self->ut_numbern('overlimit') ; return $error if $error; @@ -326,21 +464,62 @@ sub check { my $part_svc = qsearchs( 'part_svc', { 'svcpart' => $self->svcpart } ); return "Unknown svcpart" unless $part_svc; - if ( $self->pkgnum ) { - my $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } ); - return "Unknown pkgnum" unless $cust_pkg; - ($part_svc) = grep { $_->svcpart == $self->svcpart } $cust_pkg->part_svc; - return "No svcpart ". $self->svcpart. - " services in pkgpart ". $cust_pkg->pkgpart - unless $part_svc; - return "Already ". $part_svc->get('num_cust_svc'). " ". $part_svc->svc. + if ( $self->pkgnum && ! $ignore_quantity ) { + + #slightly inefficient since ->pkg_svc will also look it up, but fixing + # a much larger perf problem and have bigger fish to fry + my $cust_pkg = $self->cust_pkg; + + my $pkg_svc = $self->pkg_svc + || new FS::pkg_svc { 'svcpart' => $self->svcpart, + 'pkgpart' => $cust_pkg->pkgpart, + 'quantity' => 0, + }; + + #service add-ons, kinda false laziness/reimplementation of part_pkg->pkg_svc + foreach my $part_pkg_link ( $cust_pkg->part_pkg->svc_part_pkg_link ) { + my $addon_pkg_svc = qsearchs('pkg_svc', { + pkgpart => $part_pkg_link->dst_pkgpart, + svcpart => $self->svcpart, + }); + $pkg_svc->quantity( $pkg_svc->quantity + $addon_pkg_svc->quantity ) + if $addon_pkg_svc; + } + + #better error message? UI shouldn't get here + return "No svcpart ". $self->svcpart. + " services in pkgpart ". $cust_pkg->pkgpart + unless $pkg_svc->quantity > 0; + + my $num_cust_svc = $cust_pkg->num_cust_svc( $self->svcpart ); + + #false laziness w/cust_pkg->part_svc + my $num_avail = max( 0, ($cust_pkg->quantity || 1) * $pkg_svc->quantity + - $num_cust_svc + ); + + #better error message? again, UI shouldn't get here + return "Already $num_cust_svc ". $pkg_svc->part_svc->svc. " services for pkgnum ". $self->pkgnum - if $part_svc->get('num_avail') == 0 and !$ignore_quantity; + if $num_avail <= 0; + } $self->SUPER::check; } +=item display_svcnum + +Returns the displayed service number for this service: agent_svcid if it has a +value, svcnum otherwise + +=cut + +sub display_svcnum { + my $self = shift; + $self->agent_svcid || $self->svcnum; +} + =item part_svc Returns the definition for this service, as a FS::part_svc object (see @@ -350,9 +529,9 @@ L). sub part_svc { my $self = shift; - $self->{'_svcpart'} - ? $self->{'_svcpart'} - : qsearchs( 'part_svc', { 'svcpart' => $self->svcpart } ); + return $self->{_svcpart} if $self->{_svcpart}; + cluck 'cust_svc->part_svc called' if $DEBUG; + qsearchs( 'part_svc', { 'svcpart' => $self->svcpart } ); } =item cust_pkg @@ -409,10 +588,10 @@ sub pkg_cancel_date { return $cust_pkg->getfield('cancel') || ''; } -=item label +=item label [ LOCALE ] Returns a list consisting of: -- The name of this service (from part_svc) +- The name of this service (from part_svc), optionally localized - A meaningful identifier (username, domain, or mail alias) - The table name (i.e. svc_domain) for this service - svcnum @@ -421,7 +600,7 @@ Usage example: my($label, $value, $svcdb) = $cust_svc->label; -=item label_long +=item label_long [ LOCALE ] Like the B