prepaid download/upload tracking
authorjeff <jeff>
Thu, 30 Nov 2006 02:27:57 +0000 (02:27 +0000)
committerjeff <jeff>
Thu, 30 Nov 2006 02:27:57 +0000 (02:27 +0000)
17 files changed:
FS/FS/AccessRight.pm
FS/FS/ClientAPI/MyAccount.pm
FS/FS/Conf.pm
FS/FS/Schema.pm
FS/FS/UI/Web.pm
FS/FS/cust_main.pm
FS/FS/part_export/sqlradius.pm
FS/FS/svc_acct.pm
fs_selfservice/FS-SelfService/cgi/myaccount_menu.html
fs_selfservice/FS-SelfService/cgi/selfservice.cgi
fs_selfservice/FS-SelfService/cgi/view_usage.html [new file with mode: 0644]
httemplate/edit/prepay_credit.cgi
httemplate/edit/process/prepay_credit.cgi
httemplate/misc/process/recharge_svc.html [new file with mode: 0755]
httemplate/misc/recharge_svc.html [new file with mode: 0755]
httemplate/search/prepay_credit.html
httemplate/view/cust_main/packages.html

index 5370e22..8ba78d5 100644 (file)
@@ -115,6 +115,7 @@ assigned to users and/or groups.
 # customer service rights
 ###
   'Provision customer service',
+  'Recharge customer service',
   'Unprovision customer service',
 
   'View/link unlinked services', #not agent-virtualizable without more work
index 16b2071..eb49a6d 100644 (file)
@@ -8,6 +8,7 @@ use Date::Format;
 use Business::CreditCard;
 use Time::Duration;
 use FS::CGI qw(small_custview); #doh
+use FS::UI::Web;
 use FS::Conf;
 use FS::Record qw(qsearch qsearchs);
 use FS::Msgcat qw(gettext);
@@ -378,10 +379,12 @@ sub process_prepay {
   my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
     or return { 'error' => "unknown custnum $custnum" };
 
-  my( $amount, $seconds ) = ( 0, 0 );
+  my( $amount, $seconds, $upbytes, $downbytes ) = ( 0, 0, 0, 0 );
   my $error = $cust_main->recharge_prepay( $p->{'prepaid_cardnum'},
                                            \$amount,
-                                           \$seconds
+                                           \$seconds,
+                                           \$upbytes,
+                                           \$downbytes
                                          );
 
   return { 'error' => $error } if $error;
@@ -390,6 +393,10 @@ sub process_prepay {
            'amount'   => $amount,
            'seconds'  => $seconds,
            'duration' => duration_exact($seconds),
+           'upbytes'  => $upbytes,
+           'upload'   => FS::UI::Web::bytecount_unexact($upbytes),
+           'downbytes'=> $downbytes,
+           'download' => FS::UI::Web::bytecount_unexact($downbytes),
          };
 
 }
@@ -539,7 +546,9 @@ sub list_svcs {
 
   my @cust_svc = ();
   #foreach my $cust_pkg ( $cust_main->ncancelled_pkgs ) {
-  foreach my $cust_pkg ( $cust_main->unsuspended_pkgs ) {
+  foreach my $cust_pkg ( $p->{'ncancelled'} 
+                         ? $cust_main->ncancelled_pkgs
+                         : $cust_main->unsuspended_pkgs ) {
     push @cust_svc, @{[ $cust_pkg->cust_svc ]}; #@{[ ]} to force array context
   }
   @cust_svc = grep { $_->part_svc->svcdb eq $p->{'svcdb'} } @cust_svc
@@ -560,6 +569,9 @@ sub list_svcs {
                             'value'    => $value,
                             'username' => $svc_x->username,
                             'email'    => $svc_x->email,
+                            'seconds'  => $svc_x->seconds,
+                            'upbytes'  => $svc_x->upbytes,
+                            'downbytes'=> $svc_x->downbytes,
                             # more...
                           };
                         }
index bf853bc..639f06b 100644 (file)
@@ -1419,6 +1419,42 @@ httemplate/docs/config.html
   },
 
   {
+    'key'         => 'warning_email',
+    'section'     => '',
+    'description' => 'Template file for warning email.  Warning emails are sent to the customer email invoice destination(s) each time a svc_acct record has its usage drop below a threshold or 0.  See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language.  The following variables are available<ul><li><code>$username</code> <li><code>$password</code> <li><code>$first</code> <li><code>$last</code> <li><code>$pkg</code> <li><code>$column</code> <li><code>$amount</code> <li><code>$threshold</code></ul>',
+    'type'        => 'textarea',
+  },
+
+  {
+    'key'         => 'warning_email-from',
+    'section'     => '',
+    'description' => 'From: address header for warning email',
+    'type'        => 'text',
+  },
+
+  {
+    'key'         => 'warning_email-cc',
+    'section'     => '',
+    'description' => 'Additional recipient(s) (comma separated) for warning email when remaining usage reaches zero.',
+    'type'        => 'text',
+  },
+
+  {
+    'key'         => 'warning_email-subject',
+    'section'     => '',
+    'description' => 'Subject: header for warning email',
+    'type'        => 'text',
+  },
+  
+  {
+    'key'         => 'warning_email-mimetype',
+    'section'     => '',
+    'description' => 'MIME type for warning email',
+    'type'        => 'select',
+    'select_enum' => [ 'text/plain', 'text/html' ],
+  },
+
+  {
     'key'         => 'payby',
     'section'     => 'billing',
     'description' => 'Available payment types.',
@@ -1715,18 +1751,25 @@ httemplate/docs/config.html
   {
     'key'         => 'svc_acct-usage_suspend',
     'section'     => 'billing',
-    'description' => 'Suspends the package an account belongs to when svc_acct.seconds is decremented to 0 or below (accounts with an empty seconds value are ignored).  Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.',
+    'description' => 'Suspends the package an account belongs to when svc_acct.seconds or a bytecount is decremented to 0 or below (accounts with an empty seconds and up|down|totalbytes value are ignored).  Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.',
     'type'        => 'checkbox',
   },
 
   {
     'key'         => 'svc_acct-usage_unsuspend',
     'section'     => 'billing',
-    'description' => 'Unuspends the package an account belongs to when svc_acct.seconds is incremented from 0 or below to a positive value (accounts with an empty seconds value are ignored).  Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.',
+    'description' => 'Unuspends the package an account belongs to when svc_acct.seconds or a bytecount is incremented from 0 or below to a positive value (accounts with an empty seconds and up|down|totalbytes value are ignored).  Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.',
     'type'        => 'checkbox',
   },
 
   {
+    'key'         => 'svc_acct-usage_threshold',
+    'section'     => 'billing',
+    'description' => 'The threshold (expressed as percentage) of acct.seconds or acct.up|down|totalbytes at which a warning message is sent to a service holder.  Typically used in conjunction with prepaid packages and freeside-sqlradius-radacctd.  Defaults to 80.',
+    'type'        => 'text',
+  },
+
+  {
     'key'         => 'cust-fields',
     'section'     => 'UI',
     'description' => 'Which customer fields to display on reports by default',
index 99f8873..400ef06 100644 (file)
@@ -837,6 +837,13 @@ sub tables_hashref {
         'quota',     'varchar',   'NULL',   $char_d, '', '', 
         'slipip',    'varchar',   'NULL',   15, '', '', #four TINYINTs, bah.
         'seconds',   'int', 'NULL',   '', '', '', #uhhhh
+        'seconds_threshold',   'int', 'NULL',   '', '', '',
+        'upbytes',   'int', 'NULL',   '', '', '', 
+        'upbytes_threshold',   'int', 'NULL',   '', '', '',
+        'downbytes', 'int', 'NULL',   '', '', '',
+        'downbytes_threshold',   'int', 'NULL',   '', '', '',
+        'totalbytes','int', 'NULL',   '', '', '',
+        'totalbytes_threshold',   'int', 'NULL',   '', '', '',
         'domsvc',    'int', '',   '', '', '', 
       ],
       'primary_key' => 'svcnum',
@@ -923,6 +930,8 @@ sub tables_hashref {
         'identifier',  'varchar', '', $char_d, '', '', 
         'amount',      @money_type, '', '', 
         'seconds',     'int',     'NULL', '', '', '', 
+        'upbytes',     'int',     'NULL', '', '', '', 
+        'downbytes',   'int',     'NULL', '', '', '', 
         'agentnum',    'int',     'NULL', '', '', '', 
       ],
       'primary_key' => 'prepaynum',
index c9eaf50..0597a38 100644 (file)
@@ -64,6 +64,17 @@ sub parse_lt_gt {
 
 }
 
+sub bytecount_unexact {
+  my $bc = shift;
+  return("$bc bytes")
+    if ($bc < 1000);
+  return(sprintf("%.2f Kbytes", $bc/1000))
+    if ($bc < 1000000);
+  return(sprintf("%.2f Mbytes", $bc/1000000))
+    if ($bc < 1000000000);
+  return(sprintf("%.2f Gbytes", $bc/1000000000));
+}
+
 ###
 # cust_main report subroutines
 ###
index e4ab84d..210ab63 100644 (file)
@@ -692,21 +692,23 @@ sub order_pkgs {
   ''; #no error
 }
 
-=item recharge_prepay IDENTIFIER | PREPAY_CREDIT_OBJ [ , AMOUNTREF, SECONDSREF ]
+=item recharge_prepay IDENTIFIER | PREPAY_CREDIT_OBJ [ , AMOUNTREF, SECONDSREF, UPBYTEREF, DOWNBYTEREF ]
 
 Recharges this (existing) customer with the specified prepaid card (see
 L<FS::prepay_credit>), specified either by I<identifier> or as an
 FS::prepay_credit object.  If there is an error, returns the error, otherwise
 returns false.
 
-Optionally, two scalar references can be passed as well.  They will have their
-values filled in with the amount and number of seconds applied by this prepaid
+Optionally, four scalar references can be passed as well.  They will have their
+values filled in with the amount, number of seconds, and number of upload and
+download bytes applied by this prepaid
 card.
 
 =cut
 
 sub recharge_prepay { 
-  my( $self, $prepay_credit, $amountref, $secondsref ) = @_;
+  my( $self, $prepay_credit, $amountref, $secondsref, 
+      $upbytesref, $downbytesref ) = @_;
 
   local $SIG{HUP} = 'IGNORE';
   local $SIG{INT} = 'IGNORE';
@@ -719,10 +721,14 @@ sub recharge_prepay {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
-  my( $amount, $seconds ) = ( 0, 0 );
+  my( $amount, $seconds, $upbytes, $downbytes ) = ( 0, 0, 0, 0 );
 
-  my $error = $self->get_prepay($prepay_credit, \$amount, \$seconds)
+  my $error = $self->get_prepay($prepay_credit, \$amount,
+                                \$seconds, \$upbytes, \$downbytes)
            || $self->increment_seconds($seconds)
+           || $self->increment_upbytes($upbytes)
+           || $self->increment_downbytes($downbytes)
+           || $self->increment_totalbytes($upbytes + $downbytes)
            || $self->insert_cust_pay_prepay( $amount,
                                              ref($prepay_credit)
                                                ? $prepay_credit->identifier
@@ -736,6 +742,8 @@ sub recharge_prepay {
 
   if ( defined($amountref)  ) { $$amountref  = $amount;  }
   if ( defined($secondsref) ) { $$secondsref = $seconds; }
+  if ( defined($upbytesref) ) { $$upbytesref = $upbytes; }
+  if ( defined($downbytesref) ) { $$downbytesref = $downbytes; }
 
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
   '';
@@ -759,7 +767,7 @@ If there is an error, returns the error, otherwise returns false.
 
 
 sub get_prepay {
-  my( $self, $prepay_credit, $amountref, $secondsref ) = @_;
+  my( $self, $prepay_credit, $amountref, $secondsref, $upref, $downref) = @_;
 
   local $SIG{HUP} = 'IGNORE';
   local $SIG{INT} = 'IGNORE';
@@ -806,12 +814,50 @@ sub get_prepay {
 
   $$amountref  += $prepay_credit->amount;
   $$secondsref += $prepay_credit->seconds;
+  $$upref      += $prepay_credit->upbytes;
+  $$downref    += $prepay_credit->downbytes;
 
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
   '';
 
 }
 
+=item increment_upbytes SECONDS
+
+Updates this customer's single or primary account (see L<FS::svc_acct>) by
+the specified number of upbytes.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub increment_upbytes {
+  _increment_column( shift, 'upbytes', @_);
+}
+
+=item increment_downbytes SECONDS
+
+Updates this customer's single or primary account (see L<FS::svc_acct>) by
+the specified number of downbytes.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub increment_downbytes {
+  _increment_column( shift, 'downbytes', @_);
+}
+
+=item increment_totalbytes SECONDS
+
+Updates this customer's single or primary account (see L<FS::svc_acct>) by
+the specified number of totalbytes.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub increment_totalbytes {
+  _increment_column( shift, 'totalbytes', @_);
+}
+
 =item increment_seconds SECONDS
 
 Updates this customer's single or primary account (see L<FS::svc_acct>) by
@@ -821,10 +867,24 @@ otherwise returns false.
 =cut
 
 sub increment_seconds {
-  my( $self, $seconds ) = @_;
-  warn "$me increment_seconds called: $seconds seconds\n"
+  _increment_column( shift, 'seconds', @_);
+}
+
+=item _increment_column AMOUNT
+
+Updates this customer's single or primary account (see L<FS::svc_acct>) by
+the specified number of seconds or bytes.  If there is an error, returns
+the error, otherwise returns false.
+
+=cut
+
+sub _increment_column {
+  my( $self, $column, $amount ) = @_;
+  warn "$me increment_column called: $column, $amount\n"
     if $DEBUG;
 
+  return '' unless $amount;
+
   my @cust_pkg = grep { $_->part_pkg->svcpart('svc_acct') }
                       $self->ncancelled_pkgs;
 
@@ -854,7 +914,8 @@ sub increment_seconds {
        ' ('. $svc_acct->email. ")\n"
     if $DEBUG > 1;
 
-  $svc_acct->increment_seconds($seconds);
+  $column = "increment_$column";
+  $svc_acct->$column($amount);
 
 }
 
index 04d6512..dadd236 100644 (file)
@@ -615,7 +615,8 @@ sub update_svc_acct {
   my $where = '';
 
   my $sth = $dbh->prepare("
-    SELECT RadAcctId, UserName, Realm, AcctSessionTime
+    SELECT RadAcctId, UserName, Realm, AcctSessionTime,
+           AcctInputOctets, AcctOutputOctets
       FROM radacct
       WHERE FreesideStatus IS NULL
         AND AcctStopTime != 0
@@ -623,7 +624,8 @@ sub update_svc_acct {
   $sth->execute() or die $sth->errstr;
 
   while ( my $row = $sth->fetchrow_arrayref ) {
-    my($RadAcctId, $UserName, $Realm, $AcctSessionTime) = @$row;
+    my($RadAcctId, $UserName, $Realm, $AcctSessionTime,
+       $AcctInputOctets, $AcctOutputOctets) = @$row;
     warn "processing record: ".
          "$RadAcctId ($UserName\@$Realm for ${AcctSessionTime}s"
       if $DEBUG;
@@ -633,7 +635,6 @@ sub update_svc_acct {
     if ( ref($self) =~ /withdomain/ ) { #well...
       $extra_sql = " AND '$Realm' = ( SELECT domain FROM svc_domain
                           WHERE svc_domain.svcnum = svc_acct.domsvc ) ";
-      my $svc_domain = qsearch
     }
 
     my @svc_acct =
@@ -654,18 +655,16 @@ sub update_svc_acct {
     } elsif ( scalar(@svc_acct) > 1 ) {
       warn "WARNING: multiple svc_acct records found $errinfo - skipping\n";
     } else {
-      my $svc_acct = $svc_acct[0];
-      warn "found svc_acct ". $svc_acct->svcnum. " $errinfo\n" if $DEBUG;
-      if ( $svc_acct->seconds !~ /^$/ ) {
-        warn "  svc_acct.seconds found (". $svc_acct->seconds.
-             ") - decrementing\n"
-          if $DEBUG;
-        my $error = $svc_acct->decrement_seconds($AcctSessionTime);
-        die $error if $error;
-        $status = 'done';
-      } else {
-        warn "  no existing seconds value for svc_acct - skiping\n" if $DEBUG;
-      }
+      warn "found svc_acct ". $svc_acct[0]->svcnum. " $errinfo\n" if $DEBUG;
+      _try_decrement($svc_acct[0], 'seconds', $AcctSessionTime) 
+        and $status='done';
+      _try_decrement($svc_acct[0], 'upbytes', $AcctInputOctets)
+        and $status='done';
+      _try_decrement($svc_acct[0], 'downbytes', $AcctOutputOctets)
+        and $status='done';
+      _try_decrement($svc_acct[0], 'totalbytes', $AcctInputOctets + 
+                     $AcctOutputOctets)
+        and $status='done';
     }
 
     warn "setting FreesideStatus to $status $errinfo\n" if $DEBUG; 
@@ -679,5 +678,21 @@ sub update_svc_acct {
 
 }
 
+sub _try_decrement {
+  my ($svc_acct, $column, $amount) = @_;
+  if ( $svc_acct->$column !~ /^$/ ) {
+    warn "  svc_acct.$column found (". $svc_acct->$column.
+         ") - decrementing\n"
+      if $DEBUG;
+    my $method = 'decrement_' . $column;
+    my $error = $svc_acct->$method($amount);
+    die $error if $error;
+    return 'done';
+  } else {
+    warn "  no existing $column value for svc_acct - skipping\n" if $DEBUG;
+  }
+  return '';
+}
+
 1;
 
index 98564cc..3db12f6 100644 (file)
@@ -9,6 +9,8 @@ use vars qw( @ISA $DEBUG $me $conf $skip_fuzzyfiles
              $username_uppercase $username_percent
              $password_noampersand $password_noexclamation
              $welcome_template $welcome_from $welcome_subject $welcome_mimetype
+             $warning_template $warning_from $warning_subject $warning_mimetype
+             $warning_cc
              $smtpmachine
              $radius_password $radius_ip
              $dirhash
@@ -76,6 +78,22 @@ $FS::UID::callback{'FS::svc_acct'} = sub {
     $welcome_subject = '';
     $welcome_mimetype = '';
   }
+  if ( $conf->exists('warning_email') ) {
+    $warning_template = new Text::Template (
+      TYPE   => 'ARRAY',
+      SOURCE => [ map "$_\n", $conf->config('warning_email') ]
+    ) or warn "can't create warning email template: $Text::Template::ERROR";
+    $warning_from = $conf->config('warning_email-from'); # || 'your-isp-is-dum'
+    $warning_subject = $conf->config('warning_email-subject') || 'Warning';
+    $warning_mimetype = $conf->config('warning_email-mimetype') || 'text/plain';
+    $warning_cc = $conf->config('warning_email-cc');
+  } else {
+    $warning_template = '';
+    $warning_from = '';
+    $warning_subject = '';
+    $warning_mimetype = '';
+    $warning_cc = '';
+  }
   $smtpmachine = $conf->config('smtpmachine');
   $radius_password = $conf->config('radius-password') || 'Password';
   $radius_ip = $conf->config('radius-ip') || 'Framed-IP-Address';
@@ -168,6 +186,12 @@ FS::svc_Common.  The following fields are currently supported:
 
 =item seconds - 
 
+=item upbytes - 
+
+=item downbytes - 
+
+=item totalbytes - 
+
 =item domsvc - svcnum from svc_domain
 
 =item radius_I<Radius_Attribute> - I<Radius-Attribute> (reply)
@@ -721,6 +745,10 @@ sub check {
               #|| $self->ut_number('domsvc')
               || $self->ut_foreign_key('domsvc', 'svc_domain', 'svcnum' )
               || $self->ut_textn('sec_phrase')
+              || $self->ut_snumbern('seconds')
+              || $self->ut_snumbern('upbytes')
+              || $self->ut_snumbern('downbytes')
+              || $self->ut_snumbern('totalbytes')
   ;
   return $error if $error;
 
@@ -1215,6 +1243,72 @@ sub acct_snarf {
   qsearch('acct_snarf', { 'svcnum' => $self->svcnum } );
 }
 
+=item decrement_upbytes OCTETS
+
+Decrements the I<upbytes> field of this record by the given amount.  If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub decrement_upbytes {
+  shift->_op_usage('-', 'upbytes', @_);
+}
+
+=item increment_upbytes OCTETS
+
+Increments the I<upbytes> field of this record by the given amount.  If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub increment_upbytes {
+  shift->_op_usage('+', 'upbytes', @_);
+}
+
+=item decrement_downbytes OCTETS
+
+Decrements the I<downbytes> field of this record by the given amount.  If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub decrement_downbytes {
+  shift->_op_usage('-', 'downbytes', @_);
+}
+
+=item increment_downbytes OCTETS
+
+Increments the I<downbytes> field of this record by the given amount.  If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub increment_downbytes {
+  shift->_op_usage('+', 'downbytes', @_);
+}
+
+=item decrement_totalbytes OCTETS
+
+Decrements the I<totalbytes> field of this record by the given amount.  If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub decrement_totalbytes {
+  shift->_op_usage('-', 'totalbytes', @_);
+}
+
+=item increment_totalbytes OCTETS
+
+Increments the I<totalbytes> field of this record by the given amount.  If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub increment_totalbytes {
+  shift->_op_usage('+', 'totalbytes', @_);
+}
+
 =item decrement_seconds SECONDS
 
 Decrements the I<seconds> field of this record by the given amount.  If there
@@ -1223,7 +1317,7 @@ is an error, returns the error, otherwise returns false.
 =cut
 
 sub decrement_seconds {
-  shift->_op_seconds('-', @_);
+  shift->_op_usage('-', 'seconds', @_);
 }
 
 =item increment_seconds SECONDS
@@ -1234,7 +1328,7 @@ is an error, returns the error, otherwise returns false.
 =cut
 
 sub increment_seconds {
-  shift->_op_seconds('+', @_);
+  shift->_op_usage('+', 'seconds', @_);
 }
 
 
@@ -1243,20 +1337,32 @@ my %op2action = (
   '+' => 'unsuspend',
 );
 my %op2condition = (
-  '-' => sub { my($self, $seconds) = @_;
-               $self->seconds - $seconds <= 0;
+  '-' => sub { my($self, $column, $amount) = @_;
+               $self->$column - $amount <= 0;
              },
-  '+' => sub { my($self, $seconds) = @_;
-               $self->seconds + $seconds > 0;
+  '+' => sub { my($self, $column, $amount) = @_;
+               $self->$column + $amount > 0;
              },
 );
+my %op2warncondition = (
+  '-' => sub { my($self, $column, $amount) = @_;
+               my $threshold = $column . '_threshold';
+               $self->$column - $amount <= $self->$threshold + 0;
+             },
+  '+' => sub { my($self, $column, $amount) = @_;
+               $self->$column + $amount > 0;
+             },
+);
+
+sub _op_usage {
+  my( $self, $op, $column, $amount ) = @_;
 
-sub _op_seconds {
-  my( $self, $op, $seconds ) = @_;
-  warn "$me _op_seconds called for svcnum ". $self->svcnum.
-       ' ('. $self->email. "): $op $seconds\n"
+  warn "$me _op_usage called for $column on svcnum ". $self->svcnum.
+       ' ('. $self->email. "): $op $amount\n"
     if $DEBUG;
 
+  return '' unless $amount;
+
   local $SIG{HUP} = 'IGNORE';
   local $SIG{INT} = 'IGNORE';
   local $SIG{QUIT} = 'IGNORE';
@@ -1268,24 +1374,24 @@ sub _op_seconds {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
-  my $sql = "UPDATE svc_acct SET seconds = ".
-            " CASE WHEN seconds IS NULL THEN 0 ELSE seconds END ". #$seconds||0
+  my $sql = "UPDATE svc_acct SET $column = ".
+            " CASE WHEN $column IS NULL THEN 0 ELSE $column END ". #$column||0
             " $op ? WHERE svcnum = ?";
   warn "$me $sql\n"
     if $DEBUG;
 
   my $sth = $dbh->prepare( $sql )
     or die "Error preparing $sql: ". $dbh->errstr;
-  my $rv = $sth->execute($seconds, $self->svcnum);
+  my $rv = $sth->execute($amount, $self->svcnum);
   die "Error executing $sql: ". $sth->errstr
     unless defined($rv);
-  die "Can't update seconds for svcnum". $self->svcnum
+  die "Can't update $column for svcnum". $self->svcnum
     if $rv == 0;
 
   my $action = $op2action{$op};
 
   if ( $conf->exists("svc_acct-usage_$action")
-       && &{$op2condition{$op}}($self, $seconds)    ) {
+       && &{$op2condition{$op}}($self, $column, $amount)    ) {
     #my $error = $self->$action();
     my $error = $self->cust_svc->cust_pkg->$action();
     if ( $error ) {
@@ -1294,6 +1400,30 @@ sub _op_seconds {
     }
   }
 
+  if ($warning_template && &{$op2warncondition{$op}}($self, $column, $amount)) {
+    my $wqueue = new FS::queue {
+      'svcnum' => $self->svcnum,
+      'job'    => 'FS::svc_acct::reached_threshold',
+    };
+
+    my $to = '';
+    if ($op eq '-'){
+      $to = $warning_cc if &{$op2condition{$op}}($self, $column, $amount);
+    }
+
+    # x_threshold race
+    my $error = $wqueue->insert(
+      'svcnum' => $self->svcnum,
+      'op'     => $op,
+      'column' => $column,
+      'to'     => $to,
+    );
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return "Error queuing threshold activity: $error";
+    }
+  }
+
   warn "$me update successful; committing\n"
     if $DEBUG;
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
@@ -1302,6 +1432,20 @@ sub _op_seconds {
 }
 
 
+=item is_rechargeable
+
+Returns true if this svc_account can be "rechaged" and false otherwise.
+
+=cut
+
+sub is_rechargable {
+  my $self = shift;
+  $self->seconds ne ''
+    || $self->upbytes ne ''
+    || $self->downbytes ne ''
+    || $self->totalbytes ne '';
+}
+
 =item seconds_since TIMESTAMP
 
 Returns the number of seconds this account has been online since TIMESTAMP,
@@ -1785,6 +1929,82 @@ END
   $html;
 }
 
+=item reached_threshold
+
+Performs some activities when svc_acct thresholds (such as number of seconds
+remaining) are reached.  
+
+=cut
+
+sub reached_threshold {
+  my %opt = @_;
+
+  my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $opt{'svcnum'} } );
+  die "Cannot find svc_acct with svcnum " . $opt{'svcnum'} unless $svc_acct;
+
+  if ( $opt{'op'} eq '+' ){
+    $svc_acct->setfield( $opt{'column'}.'_threshold',
+                         int($svc_acct->getfield($opt{'column'})
+                             * ( $conf->exists('svc_acct-usage_threshold') 
+                                 ? $conf->config('svc_acct-usage_threshold')/100
+                                 : 0.80
+                               )
+                         )
+                       );
+    my $error = $svc_acct->replace;
+    die $error if $error;
+  }elsif ( $opt{'op'} eq '-' ){
+    
+    my $threshold = $svc_acct->getfield( $opt{'column'}.'_threshold' );
+    return '' if ($threshold eq '' && opt{'column'} eq 'totalbytes');
+
+    $svc_acct->setfield( $opt{'column'}.'_threshold', 0 );
+    my $error = $svc_acct->replace;
+    die $error if $error; # email next time, i guess
+
+    if ( $warning_template ) {
+      eval "use FS::Misc qw(send_email)";
+      die $@ if $@;
+
+      my $cust_pkg  = $svc_acct->cust_svc->cust_pkg;
+      my $cust_main = $cust_pkg->cust_main;
+
+      my $to = join(', ', grep { $_ !~ /^(POST|FAX)$/ } 
+                               $cust_main->invoicing_list,
+                               $svc_acct->email,
+                               ($opt{'to'} ? $opt{'to'} : ())
+                   );
+
+      my $mimetype = $warning_mimetype;
+      $mimetype .= '; charset="iso-8859-1"' unless $opt{mimetype} =~ /charset/;
+
+      my $body       =  $warning_template->fill_in( HASH => {
+                        'custnum'   => $cust_main->custnum,
+                        'username'  => $svc_acct->username,
+                        'password'  => $svc_acct->_password,
+                        'first'     => $cust_main->first,
+                        'last'      => $cust_main->getfield('last'),
+                        'pkg'       => $cust_pkg->part_pkg->pkg,
+                        'column'    => $opt{'column'},
+                        'amount'    => $svc_acct->getfield($opt{'column'}),
+                        'threshold' => $threshold,
+                      } );
+
+
+      my $error = send_email(
+        'from'         => $warning_from,
+        'to'           => $to,
+        'subject'      => $warning_subject,
+        'content-type' => $mimetype,
+        'body'         => [ map "$_\n", split("\n", $body) ],
+      );
+      die $error if $error;
+    }
+  }else{
+    die "unknown op: " . $opt{'op'};
+  }
+}
+
 =back
 
 =head1 BUGS
index aa24f5d..92f3e15 100644 (file)
@@ -31,6 +31,7 @@ push @menu, (
 
 { title=>' ' },
 
+{ title=>'View my usage', url=>'view_usage', size=>'+1', },
 { title=>'Setup my services', url=>'provision', size=>'+1', },
 
 { title=>' ' },
index 0f8979c..1a2c74a 100644 (file)
@@ -65,7 +65,7 @@ $session_id = $cgi->param('session');
 
 #order|pw_list XXX ???
 $cgi->param('action') =~
-    /^(myaccount|view_invoice|make_payment|payment_results|recharge_prepay|recharge_results|logout|change_bill|change_ship|customer_order_pkg|process_order_pkg|provision|provision_svc|process_svc_acct|process_svc_external|delete_svc|change_password|process_change_password)$/
+    /^(myaccount|view_invoice|make_payment|payment_results|recharge_prepay|recharge_results|logout|change_bill|change_ship|customer_order_pkg|process_order_pkg|provision|provision_svc|process_svc_acct|process_svc_external|delete_svc|view_usage||change_password|process_change_password)$/
   or die "unknown action ". $cgi->param('action');
 my $action = $1;
 
@@ -321,6 +321,14 @@ sub delete_svc {
   );
 }
 
+sub view_usage {
+  list_svcs(
+    'session_id'  => $session_id,
+    'svcdb'       => 'svc_acct',
+    'ncancelled'  => 1,
+  );
+}
+
 sub change_password {
   list_svcs(
     'session_id' => $session_id,
diff --git a/fs_selfservice/FS-SelfService/cgi/view_usage.html b/fs_selfservice/FS-SelfService/cgi/view_usage.html
new file mode 100644 (file)
index 0000000..40aec7b
--- /dev/null
@@ -0,0 +1,49 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+
+<FONT SIZE=4>Service usage</FONT><BR><BR>
+
+<FORM ACTION="<%= $selfurl %>" METHOD="POST">
+<INPUT TYPE="hidden" NAME="session" VALUE="<%= $session_id %>">
+
+<TABLE BGCOLOR="#cccccc">
+  <TR>
+    <TH ALIGN="left">Account</TH>
+    <TH ALIGN="right">Time remaining</TH>
+    <TH ALIGN="right">Upload remaining</TH>
+    <TH ALIGN="right">Download remaining</TH>
+    <TH ALIGN="right">Total remaining</TH>
+  </TR>
+<%= foreach my $svc ( @svcs ) {
+    my $totalbytes = '';
+    if ( ($svc->{'upbytes'}   + 0) eq $svc->{'upbytes'}
+      || ($svc->{'downbytes'} + 0) eq $svc->{'downbytes'} ) {
+
+      $totalbytes = $svc->{'upbytes'} + $svc->{'downbytes'};
+    }
+
+  $OUT .= '<TR><TD>';
+    $OUT .= $svc->{'label'}. ': '. $svc->{'value'};
+    $OUT .= '</TD><TD ALIGN="right">';
+    $OUT .= $svc->{'seconds'};
+    $OUT .= '</TD><TD ALIGN="right">';
+    $OUT .=  $svc->{'upbytes'};
+    $OUT .= '</TD><TD ALIGN="right">';
+    $OUT .= $svc->{'downbytes'};
+    $OUT .= '</TD><TD ALIGN="right">';
+    $OUT .= $totalbytes;
+  $OUT .= '</TD></TR>';
+  } %>
+
+</TABLE>
+<BR>
+
+</FORM>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
index f563e25..7ce44c0 100644 (file)
 %  3600 => 'hours',
 %;
 %
-%$cgi->param('multiplier', '60') unless $cgi->param('multiplier');
+%tie my %bytemultiplier, 'Tie::IxHash',
+%  1          => 'bytes',
+%  1000       => 'Kbytes',
+%  1000000    => 'Mbytes',
+%  1000000000 => 'Gbytes',
+%;
+%
+%$cgi->param('multiplier',     '60')      unless $cgi->param('multiplier');
+%$cgi->param('upmultiplier',   '1000000') unless $cgi->param('upmultiplier');
+%$cgi->param('downmultiplier', '1000000') unless $cgi->param('downmultiplier');
 %
 %
 
@@ -46,9 +55,11 @@ Generate
 
 </SELECT>
 
-<BR>Value: 
+<TABLE>
+<TR><TD>Value: 
 $<INPUT TYPE="text" NAME="amount" SIZE=8 MAXLENGTH=7 VALUE="<% $cgi->param('amount') %>">
-and/or
+</TD>
+<TD>and/or
 <INPUT TYPE="text" NAME="seconds" SIZE=6 MAXLENGTH=5 VALUE="<% $cgi->param('seconds') %>">
 <SELECT NAME="multiplier">
 % foreach my $multiplier ( keys %multiplier ) { 
@@ -57,6 +68,30 @@ and/or
 % } 
 
 </SELECT>
+</TD></TR>
+<TR><TD></TD>
+<TD>and/or
+<INPUT TYPE="text" NAME="upbytes" SIZE=6 MAXLENGTH=5 VALUE="<% $cgi->param('upbytes') %>">
+<SELECT NAME="upmultiplier">
+% foreach my $multiplier ( keys %bytemultiplier ) { 
+
+  <OPTION VALUE="<% $multiplier %>"<% $cgi->param('upmultiplier') eq $multiplier ? ' SELECTED' : '' %>><% $bytemultiplier{$multiplier} %>
+% } 
+
+</SELECT> upload
+</TD></TR>
+<TR><TD></TD>
+<TD>and/or
+<INPUT TYPE="text" NAME="downbytes" SIZE=6 MAXLENGTH=5 VALUE="<% $cgi->param('downbytes') %>">
+<SELECT NAME="downmultiplier">
+% foreach my $multiplier ( keys %bytemultiplier ) { 
+
+  <OPTION VALUE="<% $multiplier %>"<% $cgi->param('downmultiplier') eq $multiplier ? ' SELECTED' : '' %>><% $bytemultiplier{$multiplier} %>
+% } 
+
+</SELECT> download
+</TD></TR>
+</TABLE>
 <BR><BR>
 <INPUT TYPE="submit" NAME="submit" VALUE="Generate" onSubmit="this.disabled = true">
 
index fb15fd8..ba42969 100644 (file)
 %  $error = 'Illegal number of prepaid cards: '. $cgi->param('num');
 %}
 %
-%$hashref->{amount} = $cgi->param('amount');
-%$hashref->{seconds} = $cgi->param('seconds') * $cgi->param('multiplier');
+%$hashref->{amount}    = $cgi->param('amount');
+%$hashref->{seconds}   = $cgi->param('seconds') * $cgi->param('multiplier');
+%$hashref->{upbytes}   = $cgi->param('upbytes') * $cgi->param('upmultiplier');
+%$hashref->{downbytes} = $cgi->param('downbytes') * $cgi->param('downmultiplier');
 %
 %$error ||= FS::prepay_credit::generate( $num,
 %                                        scalar($cgi->param('type')), 
@@ -46,6 +48,8 @@
   <% $hashref->{amount} ? sprintf('$%.2f', $hashref->{amount} ) : '' %>
   <% $hashref->{amount} && $hashref->{seconds} ? 'and' : '' %>
   <% $hashref->{seconds} ? duration_exact($hashref->{seconds}) : '' %>
+  <% $hashref->{upbytes}   ? FS::UI::Web::bytecount_unexact($hashref->{upbytes}) : '' %>
+  <% $hashref->{downbytes} ? FS::UI::Web::bytecount_unexact($hashref->{downbytes}) : '' %>
   <br>
 % } 
 
diff --git a/httemplate/misc/process/recharge_svc.html b/httemplate/misc/process/recharge_svc.html
new file mode 100755 (executable)
index 0000000..ae52668
--- /dev/null
@@ -0,0 +1,46 @@
+%
+%
+%#untaint svcnum
+%my $svcnum = $cgi->param('svcnum');
+%$svcnum =~ /^(\d+)$/ || die "Illegal svcnum";
+%$svcnum = $1;
+%
+%#untaint prepaid
+%my $prepaid = $cgi->param('prepaid');
+%$prepaid =~ /^(\w*)$/;
+%$prepaid = $1;
+%
+%my $error = '';
+%my $svc_acct = qsearchs( 'svc_acct', {'svcnum'=>$svcnum} );
+%$error = "Can't recharge service $svcnum. " unless $svc_acct;
+%
+%my $cust_main = $svc_acct->cust_svc->cust_pkg->cust_main;
+%
+%my $oldAutoCommit = $FS::UID::AutoCommit;
+%local $FS::UID::AutoCommit = 0;
+%my $dbh = dbh;
+%
+%
+%unless ($error) {
+%
+%my ($amount, $seconds, $up, $down) = (0, 0, 0, 0);
+%$error = $cust_main->get_prepay($prepaid, \$amount, \$seconds, \$up, \$down)
+%      || $svc_acct->increment_seconds($seconds)
+%      || $svc_acct->increment_upbytes($up)
+%      || $svc_acct->increment_downbytes($down)
+%      || $svc_acct->increment_totalbytes($up + $down)
+%      || $cust_main->insert_cust_pay_prepay( $amount, $prepaid );
+%}
+%
+%if ($error) {
+%  $cgi->param('error', $error);
+%  $dbh->rollback if $oldAutoCommit;
+%  print $cgi->redirect(popurl(2). "recharge_svc.html?". $cgi->query_string );
+%}
+%
+<% header("Package recharged") %>
+  <SCRIPT TYPE="text/javascript">
+    window.top.location.reload();
+  </SCRIPT>
+  </BODY></HTML>
+
diff --git a/httemplate/misc/recharge_svc.html b/httemplate/misc/recharge_svc.html
new file mode 100755 (executable)
index 0000000..61f7384
--- /dev/null
@@ -0,0 +1,48 @@
+<% include('/elements/header-popup.html', 'Recharge Service' ) %>
+
+% if ( $cgi->param('error') ) { 
+  <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT>
+  <BR><BR>
+% } 
+
+<FORM NAME="recharge_popup" ACTION="<% popurl(1) %>process/recharge_svc.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>">
+
+<BR><BR>
+<% "Recharge $svcnum: $label  -  $value" %>
+<% ntable("#cccccc", 2) %>
+
+<TR>
+  <TD>Enter prepaid card: </TD>
+  <TD><INPUT TYPE="text" NAME="prepaid" VALUE="<% $prepaid %>"></TD>
+</TR>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" NAME="submit" VALUE="Recharge">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+my($svcnum, $cust_svc, $label, $value, $prepaid); 
+if ( $cgi->param('error') ) {
+  $svcnum        = $cgi->param('svcnum');
+  $prepaid       = $cgi->param('prepaid');
+} elsif ( $cgi->param('svcnum') =~ /^(\d+)$/ ) {
+  $svcnum  = $1;
+} else {
+  die "illegal query ". $cgi->keywords;
+}
+
+my $title = 'Recharge Service';
+
+$cust_svc = qsearchs('cust_svc', {'svcnum' => $svcnum});
+die "No such service: $svcnum" unless $cust_svc;
+
+($label, $value) = $cust_svc->label;
+
+</%init>
+
index dff8a3d..fecb12f 100644 (file)
@@ -9,7 +9,6 @@
 %my $count_query = 'SELECT COUNT(*) FROM prepay_credit';
 %$count_query .= ' WHERE agentnum = '. $agent->agentnum if $agent;
 %
-%
 <% include( 'elements/search.html',
                  'title'       => 'Unused Prepaid Cards'.
                                   ($agent ? ' for '. $agent->agent : ''),
                                   },
                  'count_query' => $count_query,
                  #'redirect'    => $link,
-                 'header'      => [ '#', qw(Amount Time Agent) ],
+                 'header'      => [ '#', qw(Amount Time Upload Download Agent) ],
                  'fields'      => [
                    'identifier',
                    sub { sprintf('$%.2f', shift->amount ) },
                    sub { my $c = shift; $c ? duration_exact($c->seconds) : '' },
+                   sub { my $c = shift;
+                         $c ? FS::UI::Web::bytecount_unexact($c->upbytes) : ''
+                       },
+                   sub { my $c = shift;
+                         $c ? FS::UI::Web::bytecount_unexact($c->downbytes) : ''
+                       },
                    sub { my $agent = shift->agent;
                          $agent ? $agent->agent : '';
                        },
@@ -36,6 +41,8 @@
                    '',
                    '',
                    '',
+                   '',
+                   '',
                    sub { my $agent = shift->agent;
                          $agent ? [ "${p}view/agent.cgi?", 'agentnum' ] : '';
                        },
index f93a076..0a386f2 100755 (executable)
@@ -343,17 +343,31 @@ Current packages
 %    foreach my $service (@{$svcpart->{services}}) {
 
       <TR>
-        <TD ALIGN="right" VALIGN="top" ROWSPAN=2><%svc_link($svcpart,$service)%></TD>
+        <TD ALIGN="right" VALIGN="top"><%svc_link($svcpart,$service)%></TD>
         <TD STYLE="padding-bottom:0px"><B><%svc_label_link($svcpart,$service)%></B></TD>
       </TR>
 
-%      if ( $curuser->access_right('Unprovision customer service') ) { 
-
         <TR>
-          <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px"><FONT SIZE="-2">(&nbsp;<%svc_unprovision_link($service)%>&nbsp;)</FONT></TD>
-        </TR>
+          <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px"><FONT SIZE="-2">
 
-%      } 
+%      if ( $curuser->access_right('Recharge customer service')
+%        && ($svcpart->{'svcdb'} eq 'svc_acct') 
+%        && ($service->{seconds} ne ''
+%         || $service->{upbytes} ne ''
+%         || $service->{downbytes} ne ''
+%         || $service->{totalbytes} ne '' )
+%         ) { 
+          (&nbsp;<%svc_recharge_link($service)%>&nbsp;)
+%     } 
+          </FONT></TD>
+
+          <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px"><FONT SIZE="-2">
+
+%      if ( $curuser->access_right('Unprovision customer service') ) { 
+          (&nbsp;<%svc_unprovision_link($service)%>&nbsp;)
+%     } 
+          </FONT></TD>
+        </TR>
 %   } 
 
 %   if (    ! $pkg->{'cancel'}
@@ -371,7 +385,6 @@ Current packages
 
 % } 
 
-
 </TABLE>
 </TD>
 % } #end display packages
@@ -440,6 +453,7 @@ Current packages
 %      my $svc = {
 %        'svcnum' => $cust_svc->svcnum,
 %        'label'  => ($cust_svc->label)[1],
+%        $cust_svc->svc_x->hash,
 %      };
 %
 %      #false laziness with above, to catch extraneous services.  whole
@@ -516,6 +530,12 @@ Current packages
 %  qq!'Permanently unprovision and delete this service?')">Unprovision</A>!;
 %}
 %
+%sub svc_recharge_link {
+%  my $svc = shift or return '';
+%
+%  qq!<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}misc/recharge_svc.html?svcnum=$svc->{svcnum}', 392, 336, 'recharge_svc_popup' ), CAPTION, 'Recharge service $svc->{svcnum}', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Recharge</A>!;
+%}
+%
 %# This should be generalized to use config options to determine order.
 %sub pkgsort_pkgnum_cancel {
 %  if ($a->{cancel} and $b->{cancel}) {