From dbb388836b7951a3db49deda05a1ff9ba5125c17 Mon Sep 17 00:00:00 2001 From: jeff Date: Thu, 30 Nov 2006 02:27:57 +0000 Subject: [PATCH] prepaid download/upload tracking --- FS/FS/AccessRight.pm | 1 + FS/FS/ClientAPI/MyAccount.pm | 18 +- FS/FS/Conf.pm | 47 +++- FS/FS/Schema.pm | 9 + FS/FS/UI/Web.pm | 11 + FS/FS/cust_main.pm | 81 ++++++- FS/FS/part_export/sqlradius.pm | 45 ++-- FS/FS/svc_acct.pm | 250 +++++++++++++++++++-- .../FS-SelfService/cgi/myaccount_menu.html | 1 + fs_selfservice/FS-SelfService/cgi/selfservice.cgi | 10 +- fs_selfservice/FS-SelfService/cgi/view_usage.html | 49 ++++ httemplate/edit/prepay_credit.cgi | 41 +++- httemplate/edit/process/prepay_credit.cgi | 8 +- httemplate/misc/process/recharge_svc.html | 46 ++++ httemplate/misc/recharge_svc.html | 48 ++++ httemplate/search/prepay_credit.html | 11 +- httemplate/view/cust_main/packages.html | 34 ++- 17 files changed, 650 insertions(+), 60 deletions(-) create mode 100644 fs_selfservice/FS-SelfService/cgi/view_usage.html create mode 100755 httemplate/misc/process/recharge_svc.html create mode 100755 httemplate/misc/recharge_svc.html diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index 5370e2281..8ba78d5b1 100644 --- a/FS/FS/AccessRight.pm +++ b/FS/FS/AccessRight.pm @@ -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 diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 16b207132..eb49a6d00 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -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... }; } diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index bf853bc19..639f06baa 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -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 Text::Template documentation for details on the template substitution language. The following variables are available', + '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', diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 99f88735a..400ef0646 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -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', diff --git a/FS/FS/UI/Web.pm b/FS/FS/UI/Web.pm index c9eaf5012..0597a385e 100644 --- a/FS/FS/UI/Web.pm +++ b/FS/FS/UI/Web.pm @@ -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 ### diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index e4ab84d55..210ab63c9 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -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), specified either by I 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) 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) 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) 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) 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) 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); } diff --git a/FS/FS/part_export/sqlradius.pm b/FS/FS/part_export/sqlradius.pm index 04d65126d..dadd236e7 100644 --- a/FS/FS/part_export/sqlradius.pm +++ b/FS/FS/part_export/sqlradius.pm @@ -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; diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 98564cce2..3db12f630 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -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 - I (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 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 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 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 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 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 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 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 diff --git a/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html b/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html index aa24f5d8f..92f3e1575 100644 --- a/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html +++ b/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html @@ -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=>' ' }, diff --git a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi index 0f8979cd8..1a2c74a64 100644 --- a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi +++ b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi @@ -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 index 000000000..40aec7bcf --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/view_usage.html @@ -0,0 +1,49 @@ +MyAccount +MyAccount

+<%= $url = "$selfurl?session=$session_id;action="; ''; %> +<%= include('myaccount_menu') %> + + +Service usage

+ +
+ + + + + + + + + + +<%= 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 .= ''; + } %> + +
AccountTime remainingUpload remainingDownload remainingTotal remaining
'; + $OUT .= $svc->{'label'}. ': '. $svc->{'value'}; + $OUT .= ''; + $OUT .= $svc->{'seconds'}; + $OUT .= ''; + $OUT .= $svc->{'upbytes'}; + $OUT .= ''; + $OUT .= $svc->{'downbytes'}; + $OUT .= ''; + $OUT .= $totalbytes; + $OUT .= '
+
+ +
+ + +
+powered by freeside + diff --git a/httemplate/edit/prepay_credit.cgi b/httemplate/edit/prepay_credit.cgi index f563e253b..7ce44c087 100644 --- a/httemplate/edit/prepay_credit.cgi +++ b/httemplate/edit/prepay_credit.cgi @@ -11,7 +11,16 @@ % 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 -
Value: + + + + + + + +
Value: $ -and/or +and/or +
and/or + + upload +
and/or + + download +


diff --git a/httemplate/edit/process/prepay_credit.cgi b/httemplate/edit/process/prepay_credit.cgi index fb15fd8e4..ba4296900 100644 --- a/httemplate/edit/process/prepay_credit.cgi +++ b/httemplate/edit/process/prepay_credit.cgi @@ -15,8 +15,10 @@ % $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}) : '' %>
% } diff --git a/httemplate/misc/process/recharge_svc.html b/httemplate/misc/process/recharge_svc.html new file mode 100755 index 000000000..ae526689e --- /dev/null +++ b/httemplate/misc/process/recharge_svc.html @@ -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") %> + + + diff --git a/httemplate/misc/recharge_svc.html b/httemplate/misc/recharge_svc.html new file mode 100755 index 000000000..61f738455 --- /dev/null +++ b/httemplate/misc/recharge_svc.html @@ -0,0 +1,48 @@ +<% include('/elements/header-popup.html', 'Recharge Service' ) %> + +% if ( $cgi->param('error') ) { + Error: <% $cgi->param('error') %> +

+% } + +
+ + +

+<% "Recharge $svcnum: $label - $value" %> +<% ntable("#cccccc", 2) %> + + + Enter prepaid card: + + + + + +
+ + +
+ + + +<%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; + + + diff --git a/httemplate/search/prepay_credit.html b/httemplate/search/prepay_credit.html index dff8a3d9a..fecb12f2a 100644 --- a/httemplate/search/prepay_credit.html +++ b/httemplate/search/prepay_credit.html @@ -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 : ''), @@ -23,11 +22,17 @@ }, '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' ] : ''; }, diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html index f93a0764b..0a386f265 100755 --- a/httemplate/view/cust_main/packages.html +++ b/httemplate/view/cust_main/packages.html @@ -343,17 +343,31 @@ Current packages % foreach my $service (@{$svcpart->{services}}) { - <%svc_link($svcpart,$service)%> + <%svc_link($svcpart,$service)%> <%svc_label_link($svcpart,$service)%> -% if ( $curuser->access_right('Unprovision customer service') ) { - - ( <%svc_unprovision_link($service)%> ) - + -% } +% if ( $curuser->access_right('Recharge customer service') +% && ($svcpart->{'svcdb'} eq 'svc_acct') +% && ($service->{seconds} ne '' +% || $service->{upbytes} ne '' +% || $service->{downbytes} ne '' +% || $service->{totalbytes} ne '' ) +% ) { + ( <%svc_recharge_link($service)%> ) +% } + + + + +% if ( $curuser->access_right('Unprovision customer service') ) { + ( <%svc_unprovision_link($service)%> ) +% } + + % } % if ( ! $pkg->{'cancel'} @@ -371,7 +385,6 @@ Current packages % } - % } #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!; %} % +%sub svc_recharge_link { +% my $svc = shift or return ''; +% +% qq!Recharge!; +%} +% %# This should be generalized to use config options to determine order. %sub pkgsort_pkgnum_cancel { % if ($a->{cancel} and $b->{cancel}) { -- 2.11.0