X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_svc.pm;h=b608b23498ad79ef7715adbb30cb4d2aece366b9;hb=e513d7cb331d7c7d99d1ea7b5f0a5f8e08e0d725;hp=3ce13144c36df1bc5192f3d4f6dea1efd10c5d30;hpb=cc647ae934abe5c9d9a5fc12f020adfc566349d9;p=freeside.git diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm index 3ce13144c..b608b2349 100644 --- a/FS/FS/cust_svc.pm +++ b/FS/FS/cust_svc.pm @@ -69,6 +69,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 @@ -98,6 +100,28 @@ Deletes this service from the database. If there is an error, returns the error, otherwise returns false. Note that this only removes the cust_svc record - you should probably use the B method instead. +=cut + +sub delete { + my $self = shift; + my $error = $self->SUPER::delete; + return $error if $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}); + my $svcnum = $self->svcnum; + $links->Limit(FIELD => 'Target', + VALUE => 'freeside://freeside/cust_svc/'.$svcnum); + while ( my $l = $links->Next ) { + my ($val, $msg) = $l->Delete; + # can't do anything useful on error + warn "error unlinking ticket $svcnum: $msg\n" if !$val; + } + } +} + =item cancel Cancels the relevant service by calling the B method of the associated @@ -109,7 +133,7 @@ If there is an error, returns the error, otherwise returns false. =cut sub cancel { - my $self = shift; + my($self,%opt) = @_; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -133,19 +157,26 @@ sub cancel { my $svc = $self->svc_x; if ($svc) { - - my $error = $svc->cancel; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "Error canceling service: $error"; - } - $error = $svc->delete; #this deletes this cust_svc record as well - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "Error deleting service: $error"; + if ( %opt && $opt{'date'} ) { + my $error = $svc->expire($opt{'date'}); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "Error expiring service: $error"; + } + } else { + my $error = $svc->cancel; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "Error canceling service: $error"; + } + $error = $svc->delete; #this deletes this cust_svc record as well + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "Error deleting service: $error"; + } } - } else { + } elsif ( !%opt ) { #huh? warn "WARNING: no svc_ record found for svcnum ". $self->svcnum. @@ -290,6 +321,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; @@ -300,27 +332,30 @@ sub check { if ( $self->pkgnum ) { my $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } ); return "Unknown pkgnum" unless $cust_pkg; - my $pkg_svc = qsearchs( 'pkg_svc', { - 'pkgpart' => $cust_pkg->pkgpart, - 'svcpart' => $self->svcpart, - }); - # or new FS::pkg_svc ( { 'pkgpart' => $cust_pkg->pkgpart, - # 'svcpart' => $self->svcpart, - # 'quantity' => 0 } ); - my $quantity = $pkg_svc ? $pkg_svc->quantity : 0; - - my @cust_svc = qsearch('cust_svc', { - 'pkgnum' => $self->pkgnum, - 'svcpart' => $self->svcpart, - }); - return "Already ". scalar(@cust_svc). " ". $part_svc->svc. + ($part_svc) = grep { $_->svcpart == $self->svcpart } $cust_pkg->part_svc; + return "No svcpart ". $self->svcpart. + " services in pkgpart ". $cust_pkg->pkgpart + unless $part_svc || $ignore_quantity; + return "Already ". $part_svc->get('num_cust_svc'). " ". $part_svc->svc. " services for pkgnum ". $self->pkgnum - if scalar(@cust_svc) >= $quantity && !$ignore_quantity; + if !$ignore_quantity && $part_svc->get('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 @@ -375,6 +410,20 @@ sub date_inserted { $self->h_date('insert'); } +=item pkg_cancel_date + +Returns the date this service's package was canceled. This normally only +exists for a service that's been preserved through cancellation with the +part_pkg.preserve flag. + +=cut + +sub pkg_cancel_date { + my $self = shift; + my $cust_pkg = $self->cust_pkg or return; + return $cust_pkg->getfield('cancel') || ''; +} + =item label Returns a list consisting of: @@ -430,7 +479,7 @@ Returns a listref of html elements associated with this service's exports. sub export_links { my $self = shift; my $svc_x = $self->svc_x - or return "can't find ". $self->part_svc->svcdb. '.svcnum '. $self->svcnum; + or return [ "can't find ". $self->part_svc->svcdb. '.svcnum '. $self->svcnum ]; $svc_x->export_links; } @@ -479,18 +528,20 @@ where B is not "svc_acct". =cut -#note: implementation here, POD in FS::svc_acct -sub seconds_since { - my($self, $since) = @_; - my $dbh = dbh; - my $sth = $dbh->prepare(' SELECT SUM(logout-login) FROM session - WHERE svcnum = ? - AND login >= ? - AND logout IS NOT NULL' - ) or die $dbh->errstr; - $sth->execute($self->svcnum, $since) or die $sth->errstr; - $sth->fetchrow_arrayref->[0]; -} +#internal session db deprecated (or at least on hold) +sub seconds_since { 'internal session db deprecated'; }; +##note: implementation here, POD in FS::svc_acct +#sub seconds_since { +# my($self, $since) = @_; +# my $dbh = dbh; +# my $sth = $dbh->prepare(' SELECT SUM(logout-login) FROM session +# WHERE svcnum = ? +# AND login >= ? +# AND logout IS NOT NULL' +# ) or die $dbh->errstr; +# $sth->execute($self->svcnum, $since) or die $sth->errstr; +# $sth->fetchrow_arrayref->[0]; +#} =item seconds_since_sqlradacct TIMESTAMP_START TIMESTAMP_END @@ -539,15 +590,24 @@ sub seconds_since_sqlradacct { warn "$mes finding closed sessions completely within the given range\n" if $DEBUG; + my $realm = ''; + my $realmparam = ''; + if ($part_export->option('process_single_realm')) { + $realm = 'AND Realm = ?'; + $realmparam = $part_export->option('realm'); + } + my $sth = $dbh->prepare("SELECT SUM(acctsessiontime) FROM radacct WHERE UserName = ? + $realm AND $str2time AcctStartTime) >= ? AND $str2time AcctStopTime ) < ? AND $str2time AcctStopTime ) > 0 AND AcctStopTime IS NOT NULL" ) or die $dbh->errstr; - $sth->execute($username, $start, $end) or die $sth->errstr; + $sth->execute($username, ($realm ? $realmparam : ()), $start, $end) + or die $sth->errstr; my $regular = $sth->fetchrow_arrayref->[0]; warn "$mes finding open sessions which start in the range\n" @@ -557,13 +617,19 @@ sub seconds_since_sqlradacct { $query = "SELECT SUM( ? - $str2time AcctStartTime ) ) FROM radacct WHERE UserName = ? + $realm AND $str2time AcctStartTime ) >= ? AND $str2time AcctStartTime ) < ? AND ( ? - $str2time AcctStartTime ) ) < 86400 AND ( $str2time AcctStopTime ) = 0 OR AcctStopTime IS NULL )"; $sth = $dbh->prepare($query) or die $dbh->errstr; - $sth->execute($end, $username, $start, $end, $end) + $sth->execute( $end, + $username, + ($realm ? $realmparam : ()), + $start, + $end, + $end ) or die $sth->errstr. " executing query $query"; my $start_during = $sth->fetchrow_arrayref->[0]; @@ -574,13 +640,20 @@ sub seconds_since_sqlradacct { $sth = $dbh->prepare("SELECT SUM( $str2time AcctStopTime ) - ? ) FROM radacct WHERE UserName = ? + $realm AND $str2time AcctStartTime ) < ? AND $str2time AcctStopTime ) >= ? AND $str2time AcctStopTime ) < ? AND $str2time AcctStopTime ) > 0 AND AcctStopTime IS NOT NULL" ) or die $dbh->errstr; - $sth->execute($start, $username, $start, $start, $end ) or die $sth->errstr; + $sth->execute( $start, + $username, + ($realm ? $realmparam : ()), + $start, + $start, + $end ) + or die $sth->errstr; my $end_during = $sth->fetchrow_arrayref->[0]; warn "$mes finding closed sessions which start before the range but stop after\n" @@ -591,13 +664,15 @@ sub seconds_since_sqlradacct { $sth = $dbh->prepare("SELECT COUNT(*) FROM radacct WHERE UserName = ? + $realm AND $str2time AcctStartTime ) < ? AND ( $str2time AcctStopTime ) >= ? )" # OR AcctStopTime = 0 # OR AcctStopTime IS NULL )" ) or die $dbh->errstr; - $sth->execute($username, $start, $end ) or die $sth->errstr; + $sth->execute($username, ($realm ? $realmparam : ()), $start, $end ) + or die $sth->errstr; my $entire_range = ($end-$start) * $sth->fetchrow_arrayref->[0]; $seconds += $regular + $end_during + $start_during + $entire_range; @@ -658,14 +733,23 @@ sub attribute_since_sqlradacct { warn "$mes SUMing $attrib sessions\n" if $DEBUG; + my $realm = ''; + my $realmparam = ''; + if ($part_export->option('process_single_realm')) { + $realm = 'AND Realm = ?'; + $realmparam = $part_export->option('realm'); + } + my $sth = $dbh->prepare("SELECT SUM($attrib) FROM radacct WHERE UserName = ? + $realm AND $str2time AcctStopTime ) >= ? AND $str2time AcctStopTime ) < ? AND AcctStopTime IS NOT NULL" ) or die $dbh->errstr; - $sth->execute($username, $start, $end) or die $sth->errstr; + $sth->execute($username, ($realm ? $realmparam : ()), $start, $end) + or die $sth->errstr; my $row = $sth->fetchrow_arrayref; $sum += $row->[0] if defined($row->[0]); @@ -709,70 +793,105 @@ sub get_session_history { } -=item get_cdrs_for_update +=item tickets -Returns (and SELECTs "FOR UPDATE") all unprocessed (freesidestatus NULL) CDR -objects (see L) associated with this service. - -CDRs are associated with svc_phone services via svc_phone.phonenum +Returns an array of hashes representing the tickets linked to this service. =cut -sub get_cdrs_for_update { +sub tickets { my $self = shift; - $self->get_cdrs( 'freesidestatus' => '', - 'for_update' => 1, - @_, - ); + + my $conf = FS::Conf->new; + my $num = $conf->config('cust_main-max_tickets') || 10; + my @tickets = (); + + if ( $conf->config('ticket_system') ) { + unless ( $conf->config('ticket_system-custom_priority_field') ) { + + @tickets = @{ FS::TicketSystem->service_tickets($self->svcnum, $num) }; + + } else { + + foreach my $priority ( + $conf->config('ticket_system-custom_priority_field-values'), '' + ) { + last if scalar(@tickets) >= $num; + push @tickets, + @{ FS::TicketSystem->service_tickets( $self->svcnum, + $num - scalar(@tickets), + $priority, + ) + }; + } + } + } + (@tickets); } -sub get_cdrs { - my($self, %options) = @_; - my @fields = ( 'charged_party' ); - push @fields, 'src' unless $options{'disable_src'}; +=back - my $for_update = $options{'for_update'} ? 'FOR UPDATE' : ''; +=head1 SUBROUTINES - my %hash = (); - $hash{'freesidestatus'} = $options{'freesidestatus'} - if exists($options{'freesidestatus'}); +=over 4 - #CDRs are associated with svc_phone services via svc_phone.phonenum +=item smart_search OPTION => VALUE ... - #return () unless $self->svc_x->isa('FS::svc_phone'); - return () unless $self->part_svc->svcdb eq 'svc_phone'; - my $number = $self->svc_x->phonenum; +Accepts the option I, the string to search for. The string will +be searched for as a username, email address, IP address, MAC address, +phone number, and hardware serial number. Unlike the I on +customers, this always requires an exact match. - my $prefix = $options{'default_prefix'}; +=cut - my @orwhere = map " $_ = '$number' ", @fields; - push @orwhere, map " $_ = '$prefix$number' ", @fields - if length($prefix); - if ( $prefix =~ /^\+(\d+)$/ ) { - push @orwhere, map " $_ = '$1$number' ", @fields - } +# though perhaps it should be fuzzy in some cases? - my @where = ( ' ( '. join(' OR ', @orwhere ). ' ) ' ); +sub smart_search { + my %param = __PACKAGE__->smart_search_param(@_); + qsearch(\%param); +} - if ( $options{'begin'} ) { - push @where, 'startdate >= '. $options{'begin'}; - } - if ( $options{'end'} ) { - push @where, 'startdate < '. $options{'end'}; +sub smart_search_param { + my $class = shift; + my %opt = @_; + + my $string = $opt{'search'}; + $string =~ s/(^\s+|\s+$)//; #trim leading & trailing whitespace + + my @or = + map { my $table = $_; + my $search_sql = "FS::$table"->search_sql($string); + " ( svcdb = '$table' + AND 0 < ( SELECT COUNT(*) FROM $table + WHERE $table.svcnum = cust_svc.svcnum + AND $search_sql + ) + ) "; + } + FS::part_svc->svc_tables; + + if ( $string =~ /^(\d+)$/ ) { + unshift @or, " ( agent_svcid IS NOT NULL AND agent_svcid = $1 ) "; } - my $extra_sql = ( keys(%hash) ? ' AND ' : ' WHERE ' ). join(' AND ', @where ); + my @extra_sql = ' ( '. join(' OR ', @or). ' ) '; - my @cdrs = - qsearch( { - 'table' => 'cdr', - 'hashref' => \%hash, - 'extra_sql' => $extra_sql, - 'order_by' => "ORDER BY startdate $for_update", - } ); + push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null_right' => 'View/link unlinked services' + ); + my $extra_sql = ' WHERE '.join(' AND ', @extra_sql); + #for agentnum + my $addl_from = ' LEFT JOIN cust_pkg USING ( pkgnum )'. + ' LEFT JOIN cust_main USING ( custnum )'. + ' LEFT JOIN part_svc USING ( svcpart )'; - @cdrs; + ( + 'table' => 'cust_svc', + 'addl_from' => $addl_from, + 'hashref' => {}, + 'extra_sql' => $extra_sql, + ); } =back