+sub seconds_since_sqlradacct {
+ my($self, $start, $end) = @_;
+
+ my $mes = "$me seconds_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 $seconds = 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 $closing = str2time_sql_closing( $dbh->{Driver}->{Name} );
+
+ my $username = $part_export->export_username($svc_x);
+
+ my $query;
+
+ 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 $closing >= ?
+ AND $str2time AcctStopTime $closing < ?
+ AND $str2time AcctStopTime $closing > 0
+ AND AcctStopTime IS NOT NULL"
+ ) or die $dbh->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"
+ if $DEBUG;
+
+ # count session start->range end
+ $query = "SELECT SUM( ? - $str2time AcctStartTime $closing )
+ FROM radacct
+ WHERE UserName = ?
+ $realm
+ AND $str2time AcctStartTime $closing >= ?
+ AND $str2time AcctStartTime $closing < ?
+ AND ( ? - $str2time AcctStartTime $closing ) < 86400
+ AND ( $str2time AcctStopTime $closing = 0
+ OR AcctStopTime IS NULL )";
+ $sth = $dbh->prepare($query) or die $dbh->errstr;
+ $sth->execute( $end,
+ $username,
+ ($realm ? $realmparam : ()),
+ $start,
+ $end,
+ $end )
+ or die $sth->errstr. " executing query $query";
+ my $start_during = $sth->fetchrow_arrayref->[0];
+
+ warn "$mes finding closed sessions which start before the range but stop during\n"
+ if $DEBUG;
+
+ #count range start->session end
+ $sth = $dbh->prepare("SELECT SUM( $str2time AcctStopTime $closing - ? )
+ FROM radacct
+ WHERE UserName = ?
+ $realm
+ AND $str2time AcctStartTime $closing < ?
+ AND $str2time AcctStopTime $closing >= ?
+ AND $str2time AcctStopTime $closing < ?
+ AND $str2time AcctStopTime $closing > 0
+ AND AcctStopTime IS NOT NULL"
+ ) or die $dbh->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"
+ if $DEBUG;
+
+ # count range start->range end
+ # don't count open sessions anymore (probably missing stop record)
+ $sth = $dbh->prepare("SELECT COUNT(*)
+ FROM radacct
+ WHERE UserName = ?
+ $realm
+ AND $str2time AcctStartTime $closing < ?
+ AND ( $str2time AcctStopTime $closing >= ?
+ )"
+ # OR AcctStopTime = 0
+ # OR AcctStopTime IS NULL )"
+ ) or die $dbh->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;
+
+ warn "$mes done finding sessions\n"
+ if $DEBUG;
+
+ }
+
+ $seconds;
+
+}
+
+=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 $closing = str2time_sql_closing( $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 $closing >= ?
+ AND $str2time AcctStopTime $closing < ?
+ 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;
+
+}
+
+#note: implementation here, POD in FS::svc_acct
+# false laziness w/above
+sub attribute_last_sqlradacct {
+ my($self, $attrib) = @_;
+
+ my $mes = "$me attribute_last_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 $value = '';
+ my $AcctStartTime = 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 $closing = str2time_sql_closing( $dbh->{Driver}->{Name} );
+
+ my $username = $part_export->export_username($svc_x);
+
+ warn "$mes finding most-recent $attrib\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 $attrib, $str2time AcctStartTime $closing
+ FROM radacct
+ WHERE UserName = ?
+ $realm
+ ORDER BY AcctStartTime DESC LIMIT 1
+ ") or die $dbh->errstr;
+ $sth->execute($username, ($realm ? $realmparam : ()) )
+ or die $sth->errstr;
+
+ my $row = $sth->fetchrow_arrayref;
+ if ( defined($row->[0]) && $row->[1] > $AcctStartTime ) {
+ $value = $row->[0];
+ $AcctStartTime = $row->[1];
+ }
+
+ warn "$mes done\n"
+ if $DEBUG;
+
+ }
+
+ $value;
+
+}
+
+=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 [ STATUS ]
+
+Returns an array of hashes representing the tickets linked to this service.
+
+An optional status (or arrayref or hashref of statuses) may be specified.
+
+=cut
+
+sub tickets {
+ my $self = shift;
+ my $status = ( @_ && $_[0] ) ? 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,
+ undef,
+ $status,
+ )
+ };
+
+ } 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,
+ $status,
+ )
+ };
+ }
+ }
+ }
+ (@tickets);