sqlradacct_hour price plan to charge per-hour against an external radacct table
[freeside.git] / FS / FS / cust_svc.pm
index 23a3980..ee7c34e 100644 (file)
@@ -85,9 +85,67 @@ otherwise returns false.
 =item delete
 
 Deletes this service from the database.  If there is an error, returns the
-error, otherwise returns false.
+error, otherwise returns false.  Note that this only removes the cust_svc
+record - you should probably use the B<cancel> method instead.
 
-Called by the cancel method of the package (see L<FS::cust_pkg>).
+=item cancel
+
+Cancels the relevant service by calling the B<cancel> method of the associated
+FS::svc_XXX object (i.e. an FS::svc_acct object or FS::svc_domain object),
+deleting the FS::svc_XXX record and then deleting this record.
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub cancel {
+  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 $part_svc = $self->part_svc;
+
+  $part_svc->svcdb =~ /^([\w\-]+)$/ or do {
+    $dbh->rollback if $oldAutoCommit;
+    return "Illegal svcdb value in part_svc!";
+  };
+  my $svcdb = $1;
+  require "FS/$svcdb.pm";
+
+  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;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return "Error deleting service: $error";
+    }
+  }
+
+  my $error = $self->delete;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return "Error deleting cust_svc: $error";
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  ''; #no errors
+
+}
 
 =item replace OLD_RECORD
 
@@ -159,6 +217,10 @@ sub check {
       'pkgpart' => $cust_pkg->pkgpart,
       'svcpart' => $self->svcpart,
     });
+    # or new FS::pkg_svc ( { 'pkgpart'  => $cust_pkg->pkgpart,
+    #                        'svcpart'  => $self->svcpart,
+    #                        'quantity' => 0                   } );
+
     my @cust_svc = qsearch('cust_svc', {
       'pkgnum'  => $self->pkgnum,
       'svcpart' => $self->svcpart,
@@ -278,11 +340,93 @@ sub seconds_since {
   $sth->fetchrow_arrayref->[0];
 }
 
+=item seconds_since_sqlradacct TIMESTAMP_START TIMESTAMP_END ( DBI_DATABASE_HANDLE | DATASRC DB_USERNAME DB_PASSWORD )
+
+See L<FS::svc_acct/seconds_since_sqlradacct>.  Equivalent to
+$cust_svc->svc_x->seconds_since, but more efficient.  Meaningless for records
+where B<svcdb> is not "svc_acct".
+
+NOTE: specifying a DATASRC/USERNAME/PASSWORD instead of a DBI database handle
+is not yet implemented.
+
+=cut
+
+#note: implementation here, POD in FS::svc_acct
+sub seconds_since_sqlradacct {
+  my($self, $start, $end, $dbh) = @_;
+
+  my $username = $self->svc_x->username;
+
+  #select a unix time conversion function based on database type
+  my $str2time;
+  if ( $dbh->{Driver}->{Name} eq 'mysql' ) {
+    $str2time = 'UNIX_TIMESTAMP(';
+  } elsif ( $dbh->{Driver}->{Name} eq 'Pg' ) {
+    $str2time = 'EXTRACT( EPOCH FROM ';
+  } else {
+    warn "warning: unknown database type ". $dbh->{Driver}->{Name}.
+         "; guessing how to convert to UNIX timestamps";
+    $str2time = 'extract(epoch from ';
+  }
+
+  #find sessions completely within the given range
+  my $sth = $dbh->prepare("SELECT SUM(acctsessiontime)
+                             FROM radacct
+                             WHERE UserName = ?
+                               AND $str2time AcctStartTime) >= ?
+                               AND $str2time AcctStopTime ) <  ?
+                               AND AcctStopTime =! 0
+                               AND AcctStopTime IS NOT NULL"
+  ) or die $dbh->errstr;
+  $sth->execute($username, $start, $end) or die $sth->errstr;
+  my $regular = $sth->fetchrow_arrayref->[0];
+
+  #find open sessions which start in the range, count session start->range end
+  $sth = $dbh->prepare("SELECT SUM( ? - $str2time AcctStartTime ) )
+                          FROM radacct
+                          WHERE UserName = ?
+                            AND AcctStartTime >= ?
+                            AND (    AcctStopTime = 0
+                                  OR AcctStopTime IS NULL )"
+  ) or die $dbh->errstr;
+  $sth->execute($end, $username, $start) or die $sth->errstr;
+  my $start_during = $sth->fetchrow_arrayref->[0];
+
+  #find closed sessions which start before the range but stop during,
+  #count range start->session end
+  $sth = $dbh->prepare("SELECT SUM( $str2time AcctStopTime ) - ? ) 
+                          FROM radacct
+                          WHERE UserName = ?
+                            AND AcctStartTime < ?
+                            AND AcctStopTime >= ?
+                            AND AcctStopTime <  ?
+                            AND AcctStopTime != 0
+                            AND AcctStopTime IS NOT NULL"
+  ) or die $dbh->errstr;
+  $sth->execute($start, $username, $start, $start, $end ) or die $sth->errstr;
+  my $end_during = $sth->fetchrow_arrayref->[0];
+
+  #find closed or open sessions which start before the range but stop
+  # after, or are still open, count range start->range end
+  $sth = $dbh->prepare("SELECT COUNT(*)
+                          FROM radacct
+                          WHERE UserName = ?
+                            AND AcctStartTime < ?
+                            AND (    AcctStopTime >= ?
+                                  OR AcctStopTime =  0
+                                  OR AcctStopTime IS NULL )"
+  ) or die $dbh->errstr;
+  $sth->execute($username, $start, $end ) or die $sth->errstr;
+  my $entire_range = ($end-$start) * $sth->fetchrow_arrayref->[0];
+
+  $regular + $end_during + $start_during + $entire_range;
+}
+
 =back
 
 =head1 VERSION
 
-$Id: cust_svc.pm,v 1.13 2002-04-12 15:14:58 ivan Exp $
+$Id: cust_svc.pm,v 1.15.4.1 2002-10-12 13:26:44 ivan Exp $
 
 =head1 BUGS
 
@@ -294,6 +438,9 @@ pkg_svc records are not checked in general (here).
 Deleting this record doesn't check or delete the svc_* record associated
 with this record.
 
+In seconds_since_sqlradacct, specifying a DATASRC/USERNAME/PASSWORD instead of
+a DBI database handle is not yet implemented.
+
 =head1 SEE ALSO
 
 L<FS::Record>, L<FS::cust_pkg>, L<FS::part_svc>, L<FS::pkg_svc>,