[freeside-commits] freeside/FS/FS AccessRight.pm, 1.13, 1.14 Conf.pm, 1.170, 1.171 cust_main.pm, 1.252, 1.253 Schema.pm, 1.34, 1.35 svc_acct.pm, 1.206, 1.207

Jeff Finucane,420,, jeff at wavetail.420.am
Wed Nov 29 18:27:57 PST 2006


Update of /home/cvs/cvsroot/freeside/FS/FS
In directory wavetail:/tmp/cvs-serv12489/FS/FS

Modified Files:
	AccessRight.pm Conf.pm cust_main.pm Schema.pm svc_acct.pm 
Log Message:
prepaid download/upload tracking

Index: Schema.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/Schema.pm,v
retrieving revision 1.34
retrieving revision 1.35
diff -u -d -r1.34 -r1.35
--- Schema.pm	24 Oct 2006 18:26:37 -0000	1.34
+++ Schema.pm	30 Nov 2006 02:27:54 -0000	1.35
@@ -837,6 +837,13 @@
         '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 @@
         'identifier',  'varchar', '', $char_d, '', '', 
         'amount',      @money_type, '', '', 
         'seconds',     'int',     'NULL', '', '', '', 
+        'upbytes',     'int',     'NULL', '', '', '', 
+        'downbytes',   'int',     'NULL', '', '', '', 
         'agentnum',    'int',     'NULL', '', '', '', 
       ],
       'primary_key' => 'prepaynum',

Index: Conf.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/Conf.pm,v
retrieving revision 1.170
retrieving revision 1.171
diff -u -d -r1.170 -r1.171
--- Conf.pm	14 Nov 2006 02:54:06 -0000	1.170
+++ Conf.pm	30 Nov 2006 02:27:54 -0000	1.171
@@ -1419,6 +1419,42 @@
   },
 
   {
+    '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 @@
   {
     '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: svc_acct.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/svc_acct.pm,v
retrieving revision 1.206
retrieving revision 1.207
diff -u -d -r1.206 -r1.207
--- svc_acct.pm	27 Nov 2006 01:08:47 -0000	1.206
+++ svc_acct.pm	30 Nov 2006 02:27:54 -0000	1.207
@@ -9,6 +9,8 @@
              $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 @@
     $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 @@
 
 =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 @@
               #|| $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 @@
   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 @@
 =cut
 
 sub decrement_seconds {
-  shift->_op_seconds('-', @_);
+  shift->_op_usage('-', 'seconds', @_);
 }
 
 =item increment_seconds SECONDS
@@ -1234,7 +1328,7 @@
 =cut
 
 sub increment_seconds {
-  shift->_op_seconds('+', @_);
+  shift->_op_usage('+', 'seconds', @_);
 }
 
 
@@ -1243,20 +1337,32 @@
   '+' => '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_seconds {
-  my( $self, $op, $seconds ) = @_;
-  warn "$me _op_seconds called for svcnum ". $self->svcnum.
-       ' ('. $self->email. "): $op $seconds\n"
+sub _op_usage {
+  my( $self, $op, $column, $amount ) = @_;
+
+  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 @@
   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 @@
     }
   }
 
+  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 @@
 }
 
 
+=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 @@
   $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: AccessRight.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/AccessRight.pm,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -d -r1.13 -r1.14
--- AccessRight.pm	27 Oct 2006 17:01:29 -0000	1.13
+++ AccessRight.pm	30 Nov 2006 02:27:54 -0000	1.14
@@ -115,6 +115,7 @@
 # customer service rights
 ###
   'Provision customer service',
+  'Recharge customer service',
   'Unprovision customer service',
 
   'View/link unlinked services', #not agent-virtualizable without more work

Index: cust_main.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/cust_main.pm,v
retrieving revision 1.252
retrieving revision 1.253
diff -u -d -r1.252 -r1.253
--- cust_main.pm	5 Nov 2006 19:34:09 -0000	1.252
+++ cust_main.pm	30 Nov 2006 02:27:54 -0000	1.253
@@ -692,21 +692,23 @@
   ''; #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 @@
   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 @@
 
   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 @@
 
 
 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 @@
 
   $$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 @@
 =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 @@
        ' ('. $svc_acct->email. ")\n"
     if $DEBUG > 1;
 
-  $svc_acct->increment_seconds($seconds);
+  $column = "increment_$column";
+  $svc_acct->$column($amount);
 
 }
 



More information about the freeside-commits mailing list