+=item attribute_since_sqlradacct TIMESTAMP_START TIMESTAMP_END ATTRIBUTE
+
+See L<FS::svc_acct/attribute_since_sqlradacct>. Equivalent to
+$cust_svc->svc_x->attribute_since_sqlradacct, but more efficient. Meaningless
+for records where B<svcdb> is not "svc_acct".
+
+=cut
+
+#note: implementation here, POD in FS::svc_acct
+#(false laziness w/seconds_since_sqlradacct above)
+sub attribute_since_sqlradacct {
+ my($self, $start, $end, $attrib) = @_;
+
+ my $mes = "$me attribute_since_sqlradacct:";
+
+ my $svc_x = $self->svc_x;
+
+ my @part_export = $self->part_svc->part_export_usage;
+ die "no accounting-capable exports are enabled for ". $self->part_svc->svc.
+ " service definition"
+ unless @part_export;
+ #or return undef;
+
+ my $sum = 0;
+
+ foreach my $part_export ( @part_export ) {
+
+ next if $part_export->option('ignore_accounting');
+
+ warn "$mes connecting to sqlradius database\n"
+ if $DEBUG;
+
+ my $dbh = DBI->connect( map { $part_export->option($_) }
+ qw(datasrc username password) )
+ or die "can't connect to sqlradius database: ". $DBI::errstr;
+
+ warn "$mes connected to sqlradius database\n"
+ if $DEBUG;
+
+ #select a unix time conversion function based on database type
+ my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
+
+ my $username = $part_export->export_username($svc_x);
+
+ 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, ($realm ? $realmparam : ()), $start, $end)
+ or die $sth->errstr;
+
+ my $row = $sth->fetchrow_arrayref;
+ $sum += $row->[0] if defined($row->[0]);
+
+ warn "$mes done SUMing sessions\n"
+ if $DEBUG;
+
+ }
+
+ $sum;
+
+}
+
+=item get_session_history TIMESTAMP_START TIMESTAMP_END
+
+See L<FS::svc_acct/get_session_history>. Equivalent to
+$cust_svc->svc_x->get_session_history, but more efficient. Meaningless for
+records where B<svcdb> is not "svc_acct".
+
+=cut
+
+sub get_session_history {
+ my($self, $start, $end, $attrib) = @_;
+
+ #$attrib ???
+
+ my @part_export = $self->part_svc->part_export_usage;
+ die "no accounting-capable exports are enabled for ". $self->part_svc->svc.
+ " service definition"
+ unless @part_export;
+ #or return undef;
+
+ my @sessions = ();
+
+ foreach my $part_export ( @part_export ) {
+ push @sessions,
+ @{ $part_export->usage_sessions( $start, $end, $self->svc_x ) };
+ }
+
+ @sessions;
+
+}
+
+=item tickets
+
+Returns an array of hashes representing the tickets linked to this service.
+
+=cut
+
+sub tickets {
+ my $self = shift;
+
+ 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);
+}
+
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item smart_search OPTION => VALUE ...
+
+Accepts the option I<search>, 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<smart_search> on
+customers, this always requires an exact match.
+
+=cut
+
+# though perhaps it should be fuzzy in some cases?
+
+sub smart_search {
+ my %param = __PACKAGE__->smart_search_param(@_);
+ qsearch(\%param);
+}
+
+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 = ' ( '. join(' OR ', @or). ' ) ';
+
+ 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 )';
+
+ (
+ 'table' => 'cust_svc',
+ 'addl_from' => $addl_from,
+ 'hashref' => {},
+ 'extra_sql' => $extra_sql,
+ );
+}
+