}
+sub get_mac_address {
+ my $p = shift;
+
+## access radius exports acct tables to get mac
+ my @part_export = ();
+ @part_export = (
+ qsearch( 'part_export', { 'exporttype' => 'sqlradius' } ),
+ qsearch( 'part_export', { 'exporttype' => 'sqlradius_withdomain' } ),
+ qsearch( 'part_export', { 'exporttype' => 'broadband_sqlradius' } ),
+ );
+
+ my @sessions;
+ foreach my $part_export (@part_export) {
+ push @sessions, ( @{ $part_export->usage_sessions( {
+ 'ip' => $p->{'ip'},
+ } ) } );
+ }
+
+ my $mac = $sessions[0]->{'callingstationid'};
+
+ return { 'mac_address' => $mac, };
+}
+
sub login_info {
my $p = shift;
} elsif ( $p->{'domain'} eq 'ip_mac' ) {
- my $svc_broadband = qsearchs( 'svc_broadband', { 'mac_addr' => $p->{'username'} } );
- return { error => 'IP address not found' }
+ my $mac_address = $p->{'username'};
+ $mac_address =~ s/\://g;
+
+ my $svc_broadband = qsearchs( 'svc_broadband', { 'mac_addr' => $mac_address } );
+ return { error => 'MAC address not found '.$p->{'username'} }
unless $svc_broadband;
$svc_x = $svc_broadband;
'quotation_add_pkg' => 'MyAccount/quotation/quotation_add_pkg',
'quotation_remove_pkg' => 'MyAccount/quotation/quotation_remove_pkg',
'quotation_order' => 'MyAccount/quotation/quotation_order',
+ 'get_mac_address' => 'MyAccount/get_mac_address',
'freesideinc_service' => 'Freeside/freesideinc_service',
};
Savepoint labels must be valid sql identifiers. If your choice of label
would not make a valid column name, it probably will not make a valid label.
-Savepint labels must be unique within the transaction.
+Savepoint labels must be unique within the transaction.
=cut
use vars qw(
@EXPORT_OK $DEBUG $me $cgi $freeside_uid $conf_dir $cache_dir
$secrets $datasrc $db_user $db_pass $schema $dbh $driver_name
- $AutoCommit %callback @callback $callback_hack
+ $AutoCommit $ForceObeyAutoCommit %callback @callback $callback_hack
);
use subs qw( getsecrets );
use Carp qw( carp croak cluck confess );
$conf_dir = "%%%FREESIDE_CONF%%%";
$cache_dir = "%%%FREESIDE_CACHE%%%";
+# Code wanting to issue a COMMIT statement to the database is expected to
+# obey the convention of checking this flag first. Setting $AutoCommit = 0
+# should (usually) suppress COMMIT statements.
$AutoCommit = 1; #ours, not DBI
+
+# Not all methods obey $AutoCommit, by design choice. Setting
+# $ForceObeyAutoCommit = 1 will override that design choice for:
+# &FS::cust_main::Billing::collect
+# &FS::cust_main::Billing::do_cust_event
+$ForceObeyAutoCommit = 0;
+
$callback_hack = 0;
=head1 NAME
use FS::agent;
use FS::cust_main;
use FS::sales;
+use Carp qw( croak );
$DEBUG = 0;
$me = '[FS::access_user]';
return $error;
}
+=item get_pref NAME
+
+Fetch the prefvalue column from L<FS::access_user_pref> for prefname NAME
+
+Returns undef when no value has been saved, or when record has expired
+
+=cut
+
+sub get_pref {
+ my ( $self, $prefname ) = @_;
+ croak 'prefname parameter requrired' unless $prefname;
+
+ my $pref_row = $self->get_pref_row( $prefname )
+ or return undef;
+
+ return undef
+ if $pref_row->expiration
+ && $pref_row->expiration < time();
+
+ $pref_row->prefvalue;
+}
+
+=item get_pref_row NAME
+
+Fetch the row object from L<FS::access_user_pref> for prefname NAME
+
+returns undef when no row has been created
+
+=cut
+
+sub get_pref_row {
+ my ( $self, $prefname ) = @_;
+ croak 'prefname parameter required' unless $prefname;
+
+ qsearchs(
+ access_user_pref => {
+ usernum => $self->usernum,
+ prefname => $prefname,
+ }
+ );
+}
+
+=item set_pref NAME, VALUE, [EXPIRATION_EPOCH]
+
+Add or update user preference in L<FS::access_user_pref> table
+
+Passing an undefined VALUE will delete the user preference
+
+Returns VALUE
+
+=cut
+
+sub set_pref {
+ my $self = shift;
+ my ( $prefname, $prefvalue, $expiration ) = @_;
+
+ return $self->delete_pref( $prefname )
+ unless defined $prefvalue;
+
+ if ( my $pref_row = $self->get_pref_row( $prefname )) {
+ return $prefvalue
+ if $pref_row->prefvalue eq $prefvalue;
+
+ $pref_row->prefvalue( $prefvalue );
+ $pref_row->expiration( $expiration || '');
+
+ if ( my $error = $pref_row->replace ) { croak $error }
+
+ return $prefvalue;
+ }
+
+ my $pref_row = FS::access_user_pref->new({
+ usernum => $self->usernum,
+ prefname => $prefname,
+ prefvalue => $prefvalue,
+ expiration => $expiration,
+ });
+ if ( my $error = $pref_row->insert ) { croak $error }
+
+ $prefvalue;
+}
+
+=item delete_pref NAME
+
+Delete user preference from L<FS::access_user_pref> table
+
+=cut
+
+sub delete_pref {
+ my ( $self, $prefname ) = @_;
+
+ my $pref_row = $self->get_pref_row( $prefname )
+ or return;
+
+ if ( my $error = $pref_row->delete ) { croak $error }
+}
+
=back
=head1 BUGS
}
- $error ||= $self->insert_password_history;
-
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
}
+ if ( $self->get('password') ) {
+ my $error = $self->is_password_allowed($self->get('password'))
+ || $self->change_password($self->get('password'));
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
'';
$hash eq $check_hash;
- } else {
+ } else {
return 0 if $self->_password eq '';
use FS::reason;
use FS::reason_type;
use FS::L10N;
+use FS::Misc::Savepoint;
$DEBUG = 0;
$me = '[FS::cust_bill]';
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
+ my $savepoint_label = 'cust_bill__apply_payments_and_credits';
+ savepoint_create( $savepoint_label );
+
$self->select_for_update; #mutex
my @payments = grep { $_->unapplied > 0 }
my $error = $app->insert(%options);
if ( $error ) {
+ savepoint_rollback_and_release( $savepoint_label );
$dbh->rollback if $oldAutoCommit;
return "Error inserting ". $app->table. " record: $error";
}
}
+ savepoint_release( $savepoint_label );
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
''; #no error
use FS::cust_payby;
use FS::contact;
use FS::reason;
+use FS::Misc::Savepoint;
# 1 is mostly method/subroutine entry and options
# 2 traces progress of some operations
my( $self, %opt ) = @_;
# we're going to cancel services, which is not reversible
+ # unless exports are suppressed
die "cancel_pkgs cannot be run inside a transaction"
- if $FS::UID::AutoCommit == 0;
+ if !$FS::UID::AutoCommit && !$FS::svc_Common::noexport_hack;
+ my $oldAutoCommit = $FS::UID::AutoCommit;
local $FS::UID::AutoCommit = 0;
+ savepoint_create('cancel_pkgs');
+
return ( 'access denied' )
unless $FS::CurrentUser::CurrentUser->access_right('Cancel customer');
my $ban = new FS::banned_pay $cust_payby->_new_banned_pay_hashref;
my $error = $ban->insert;
if ($error) {
- dbh->rollback;
+ savepoint_rollback_and_release('cancel_pkgs');
+ dbh->rollback if $oldAutoCommit;
return ( $error );
}
'time' => $cancel_time );
if ($error) {
warn "Error billing during cancel, custnum ". $self->custnum. ": $error";
- dbh->rollback;
+ savepoint_rollback_and_release('cancel_pkgs');
+ dbh->rollback if $oldAutoCommit;
return ( "Error billing during cancellation: $error" );
}
}
- dbh->commit;
+ savepoint_release('cancel_pkgs');
+ dbh->commit if $oldAutoCommit;
my @errors;
# try to cancel each service, the same way we would for individual packages,
warn "$me removing ".scalar(@sorted_cust_svc)." service(s) for customer ".
$self->custnum."\n"
if $DEBUG;
+ my $i = 0;
foreach my $cust_svc (@sorted_cust_svc) {
+ my $savepoint = 'cancel_pkgs_'.$i++;
+ savepoint_create( $savepoint );
my $part_svc = $cust_svc->part_svc;
next if ( defined($part_svc) and $part_svc->preserve );
# immediate cancel, no date option
# transactionize individually
my $error = try { $cust_svc->cancel } catch { $_ };
if ( $error ) {
- dbh->rollback;
+ savepoint_rollback_and_release( $savepoint );
+ dbh->rollback if $oldAutoCommit;
push @errors, $error;
} else {
- dbh->commit;
+ savepoint_release( $savepoint );
+ dbh->commit if $oldAutoCommit;
}
}
if (@errors) {
@cprs = @{ delete $opt{'cust_pkg_reason'} };
}
my $null_reason;
+ $i = 0;
foreach (@pkgs) {
my %lopt = %opt;
+ my $savepoint = 'cancel_pkgs_'.$i++;
+ savepoint_create( $savepoint );
if (@cprs) {
my $cpr = shift @cprs;
if ( $cpr ) {
}
my $error = $_->cancel(%lopt);
if ( $error ) {
- dbh->rollback;
+ savepoint_rollback_and_release( $savepoint );
+ dbh->rollback if $oldAutoCommit;
push @errors, 'pkgnum '.$_->pkgnum.': '.$error;
} else {
- dbh->commit;
+ savepoint_release( $savepoint );
+ dbh->commit if $oldAutoCommit;
}
}
use FS::FeeOrigin_Mixin;
use FS::Log;
use FS::TaxEngine;
+use FS::Misc::Savepoint;
# 1 is mostly method/subroutine entry and options
# 2 traces progress of some operations
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
#never want to roll back an event just because it returned an error
- local $FS::UID::AutoCommit = 1; #$oldAutoCommit;
+ # unless $FS::UID::ForceObeyAutoCommit is set
+ local $FS::UID::AutoCommit = 1
+ unless !$oldAutoCommit
+ && $FS::UID::ForceObeyAutoCommit;
$self->do_cust_event(
'debug' => ( $options{'debug'} || 0 ),
}
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
#never want to roll back an event just because it or a different one
# returned an error
- local $FS::UID::AutoCommit = 1; #$oldAutoCommit;
+ # unless $FS::UID::ForceObeyAutoCommit is set
+ local $FS::UID::AutoCommit = 1
+ unless !$oldAutoCommit
+ && $FS::UID::ForceObeyAutoCommit;
foreach my $cust_event ( @$due_cust_event ) {
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
+ my $savepoint_label = 'Billing__apply_payments_and_credits';
+ savepoint_create( $savepoint_label );
+
$self->select_for_update; #mutex
foreach my $cust_bill ( $self->open_cust_bill ) {
my $error = $cust_bill->apply_payments_and_credits(%options);
if ( $error ) {
+ savepoint_rollback_and_release( $savepoint_label );
$dbh->rollback if $oldAutoCommit;
return "Error applying: $error";
}
}
+ savepoint_release( $savepoint_label );
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
''; #no error
use FS::cust_refund;
use FS::banned_pay;
use FS::payment_gateway;
+use FS::Misc::Savepoint;
$realtime_bop_decline_quiet = 0;
our $BOP_TESTING = 0;
our $BOP_TESTING_SUCCESS = 1;
+our $BOP_TESTING_TIMESTAMP = '';
install_callback FS::UID sub {
$conf = new FS::Conf;
confess "Can't call realtime_bop within another transaction ".
'($FS::UID::AutoCommit is false)'
- unless $FS::UID::AutoCommit;
+ unless $FS::UID::AutoCommit || $BOP_TESTING;
local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG;
my $cust_pay_pending = new FS::cust_pay_pending {
'custnum' => $self->custnum,
'paid' => $options{amount},
- '_date' => '',
+ '_date' => $BOP_TESTING ? $BOP_TESTING_TIMESTAMP : '',
'payby' => $bop_method2payby{$options{method}},
'payinfo' => $options{payinfo},
'paymask' => $options{paymask},
return { reference => $cust_pay_pending->paypendingnum,
map { $_ => $transaction->$_ } qw ( popup_url collectitems ) };
- } elsif ( $transaction->is_success() && $action2 ) {
+ } elsif ( !$BOP_TESTING && $transaction->is_success() && $action2 ) {
$cust_pay_pending->status('authorized');
my $cpp_authorized_err = $cust_pay_pending->replace;
'custnum' => $self->custnum,
'invnum' => $options{'invnum'},
'paid' => $cust_pay_pending->paid,
- '_date' => '',
+ '_date' => $BOP_TESTING ? $BOP_TESTING_TIMESTAMP : '',
'payby' => $cust_pay_pending->payby,
'payinfo' => $options{'payinfo'},
'paymask' => $options{'paymask'} || $cust_pay_pending->paymask,
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
+ my $savepoint_label = '_realtime_bop_result';
+ savepoint_create( $savepoint_label );
+
#start a transaction, insert the cust_pay and set cust_pay_pending.status to done in a single transction
my $error = $cust_pay->insert($options{'manual'} ? ( 'manual' => 1 ) : () );
if ( $error ) {
- $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ savepoint_rollback( $savepoint_label );
+
$cust_pay->invnum(''); #try again with no specific invnum
$cust_pay->paynum('');
my $error2 = $cust_pay->insert( $options{'manual'} ?
if ( $error2 ) {
# gah. but at least we have a record of the state we had to abort in
# from cust_pay_pending now.
- $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ savepoint_rollback_and_release( $savepoint_label );
+
my $e = "WARNING: $options{method} captured but payment not recorded -".
" error inserting payment (". $payment_gateway->gateway_module.
"): $error2".
my $jobnum = $cust_pay_pending->jobnum;
if ( $jobnum ) {
my $placeholder = qsearchs( 'queue', { 'jobnum' => $jobnum } );
-
+
unless ( $placeholder ) {
- $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ savepoint_rollback_and_release( $savepoint_label );
+
my $e = "WARNING: $options{method} captured but job $jobnum not ".
"found for paypendingnum ". $cust_pay_pending->paypendingnum. "\n";
warn $e;
$error = $placeholder->delete;
if ( $error ) {
- $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ savepoint_rollback_and_release( $savepoint_label );
+
my $e = "WARNING: $options{method} captured but could not delete ".
"job $jobnum for paypendingnum ".
$cust_pay_pending->paypendingnum. ": $error\n";
my $cpp_done_err = $cust_pay_pending->replace;
if ( $cpp_done_err ) {
+ savepoint_rollback_and_release( $savepoint_label );
- $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
my $e = "WARNING: $options{method} captured but payment not recorded - ".
"error updating status for paypendingnum ".
$cust_pay_pending->paypendingnum. ": $cpp_done_err \n";
return $e;
} else {
-
+ savepoint_release( $savepoint_label );
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
if ( $options{'apply'} ) {
package FS::cust_payby;
use base qw( FS::payinfo_Mixin FS::cust_main_Mixin FS::Record );
+use feature 'state';
use strict;
use Scalar::Util qw( blessed );
=back
+=item has_autobill_cards
+
+Returns the number of unexpired cards configured for autobill
+
+=cut
+
+sub has_autobill_cards {
+ scalar FS::Record::qsearch({
+ table => 'cust_payby',
+ addl_from => 'JOIN cust_main USING (custnum)',
+ order_by => 'LIMIT 1',
+ hashref => {
+ paydate => { op => '>', value => DateTime->now->ymd },
+ weight => { op => '>', value => 0 },
+ },
+ extra_sql =>
+ "AND payby IN ('CARD', 'DCRD') ".
+ 'AND '.
+ $FS::CurrentUser::CurrentUser->agentnums_sql( table => 'cust_main' ),
+ });
+}
+
+=item has_autobill_checks
+
+Returns the number of check accounts configured for autobill
+
+=cut
+
+sub has_autobill_checks {
+ scalar FS::Record::qsearch({
+ table => 'cust_payby',
+ addl_from => 'JOIN cust_main USING (custnum)',
+ order_by => 'LIMIT 1',
+ hashref => {
+ weight => { op => '>', value => 0 },
+ },
+ extra_sql =>
+ "AND payby IN ('CHEK','DCHEK','DCHK') ".
+ 'AND '.
+ $FS::CurrentUser::CurrentUser->agentnums_sql( table => 'cust_main' ),
+ });
+}
+
+=item future_autobill_report_title
+
+Determine if the future_autobill report should be available.
+If so, return a dynamic title for it
+
=cut
+sub future_autobill_report_title {
+ # Perhaps this function belongs somewhere else
+ state $title;
+ return $title if defined $title;
+
+ # Report incompatible with tax engines
+ return $title = '' if FS::TaxEngine->new->info->{batch};
+
+ my $has_cards = has_autobill_cards();
+ my $has_checks = has_autobill_checks();
+ my $_title = 'Future %s transactions';
+
+ if ( $has_cards && $has_checks ) {
+ $title = sprintf $_title, 'credit card and electronic check';
+ } elsif ( $has_cards ) {
+ $title = sprintf $_title, 'credit card';
+ } elsif ( $has_checks ) {
+ $title = sprintf $_title, 'electronic check';
+ } else {
+ $title = '';
+ }
+
+ $title;
+}
+
sub _upgrade_data {
my $class = shift;
use Parse::FixedLength;
use File::Temp qw(tempfile);
use vars qw(%info %options $initial_load_hack $DEBUG);
+use Carp qw( carp );
my %upload_targets;
my $self = shift;
my $batch = shift;
local $DEBUG = $self->option('debug');
+
+ if ( $FS::svc_Common::noexport_hack ) {
+ carp 'FS::part_export::nena2::process() suppressed by noexport_hack'
+ if $DEBUG;
+ return;
+ }
+
local $FS::UID::AutoCommit = 0;
my $error;
my $accesspoint = process_sector($self, $sector_opt);
return $self->api_error if $self->{'__saisei_error'};
+## get custnum and pkgpart from cust_pkg for virtual access point
+ my $cust_pkg = FS::Record::qsearchs({
+ 'table' => 'cust_pkg',
+ 'hashref' => { 'pkgnum' => $svc_broadband->{Hash}->{pkgnum}, },
+ });
+ my $virtual_ap_name = $cust_pkg->{Hash}->{custnum}.'_'.$cust_pkg->{Hash}->{pkgpart}.'_'.$svc_broadband->{Hash}->{speed_down}.'_'.$svc_broadband->{Hash}->{speed_up};
+
+ my $virtual_ap_opt = {
+ 'virtual_name' => $virtual_ap_name,
+ 'sector_name' => $sector_name,
+ 'virtual_uprate_limit' => $svc_broadband->{Hash}->{speed_up},
+ 'virtual_downrate_limit' => $svc_broadband->{Hash}->{speed_down},
+ };
+ my $virtual_ap = process_virtual_ap($self, $virtual_ap_opt);
+ return $self->api_error if $self->{'__saisei_error'};
+
## tie host to user add sector name as access point.
$self->api_add_host_to_user(
$user->{collection}->[0]->{name},
$rateplan->{collection}->[0]->{name},
$svc_broadband->{Hash}->{ip_addr},
- $accesspoint->{collection}->[0]->{name},
+ $virtual_ap->{collection}->[0]->{name},
) unless $self->{'__saisei_error'};
}
sub _export_replace {
my ($self, $svc_broadband) = @_;
- $self->_export_insert($svc_broadband);
- return '';
+ my $error = $self->_export_insert($svc_broadband);
+ return $error;
}
sub _export_delete {
return $accesspoint;
}
+sub process_virtual_ap {
+ my ($self, $opt) = @_;
+
+ my $existing_virtual_ap;
+ my $virtual_name = $opt->{virtual_name};
+
+ #check if sector has been set up as an access point.
+ $existing_virtual_ap = $self->api_get_accesspoint($virtual_name);
+
+ # modify the existing virtual accesspoint if changing it. this should never happen
+ $self->api_modify_existing_accesspoint (
+ $virtual_name,
+ $opt->{sector_name},
+ $opt->{virtual_uprate_limit},
+ $opt->{virtual_downrate_limit},
+ ) if $existing_virtual_ap && $opt->{modify_existing};
+
+ #if virtual ap does not exist as an access point create it.
+ $self->api_create_accesspoint(
+ $virtual_name,
+ $opt->{virtual_uprate_limit},
+ $opt->{virtual_downrate_limit},
+ ) unless $existing_virtual_ap;
+
+my $update_sector;
+if ($existing_virtual_ap && ($existing_virtual_ap->{collection}->[0]->{uplink}->{link}->{name} ne $opt->{sector_name})) {
+ $update_sector = 1;
+}
+
+ # Attach newly created virtual ap to tower sector ap or if sector has changed.
+ $self->api_modify_accesspoint($virtual_name, $opt->{sector_name}) unless ($self->{'__saisei_error'} || ($existing_virtual_ap && !$update_sector));
+
+ # set access point to existing one or newly created one.
+ my $accesspoint = $existing_virtual_ap ? $existing_virtual_ap : $self->api_get_accesspoint($virtual_name);
+
+ return $accesspoint;
+}
+
sub export_provisioned_services {
my $job = shift;
my $param = shift;
</%doc>
- <SCRIPT>
-function checkPasswordValidation(fieldid) {
- var validationResult = document.getElementById(fieldid+'_result').innerHTML;
- if (validationResult.match(/Password valid!/)) {
- return true;
+<SCRIPT>
+ function checkPasswordValidation(fieldid) {
+ var validationResult = document.getElementById(fieldid+'_result').innerHTML;
+ if (validationResult.match(/Password valid!/)) {
+ return true;
+ }
+ else {
+ return false;
+ }
}
- else {
- return false;
- }
-}
</SCRIPT>
+<& '/elements/validate_password_js.html', &>
+
<& elements/edit.html,
'name_singular' => 'customer contacts', #yes, we're editing all of them
'table' => 'cust_main',
my $curuser = $FS::CurrentUser::CurrentUser;
my $conf = new FS::Conf;
+if ( $cgi->param('redirect') ) {
+ my $session = $cgi->param('redirect');
+ my $pref = $curuser->option("redirect$session");
+ die "unknown redirect session $session\n" unless length($pref);
+ $cgi = new CGI($pref);
+}
+
my $custnum;
if ( $cgi->param('error') ) {
$custnum = scalar($cgi->param('custnum'));
var newrow = <% include(@layer_opt, html_only=>1) |js_string %>;
% #until the rest have html/js_only
-% if ( ($type eq 'selectlayers') || ($type eq 'selectlayersx') || ($type =~ /^select-cgp_rule_/) ) {
+% if ( ($type eq 'selectlayers') || ($type eq 'selectlayersx') || ($type =~ /^select-cgp_rule_/) || ($type eq 'contact') ) {
var newfunc = <% include(@layer_opt, js_only=>1) |js_string %>;
% } else {
var newfunc = '';
</%doc>
<% include('elements/process.html',
'table' => 'cust_main',
- 'error_redirect' => popurl(3). 'edit/cust_main-contacts.html?',
+ 'error_redirect' => popurl(3). 'edit/cust_main-contacts.html',
'agent_virt' => 1,
'skip_process' => 1, #we don't want to make any changes to cust_main
'precheck_callback' => $precheck_callback,
% if (!$opt{'no_label_display'}) {
<A ID="<%$pre%>link" HREF="javascript:void(0)" onclick="<%$pre%>toggle(true)">(<% emt( $change_title ) %>)</A>
% }
-<DIV ID="<%$pre%>form" CLASS="passwordbox">
+<DIV ID="<%$pre%>div" CLASS="passwordbox">
% if (!$opt{'noformtag'}) {
- <FORM METHOD="POST" ACTION="<%$fsurl%>misc/process/change-password.html" onsubmit="return checkPasswordValidation()">
+ <FORM ID="<%$pre%>form" METHOD="POST" ACTION="<%$fsurl%>misc/process/change-password.html" onsubmit="return <%$pre%>checkPasswordValidation()">
% }
<% $change_id_input %>
if (clear) {
document.getElementById('<%$pre%>password').value = '';
document.getElementById('<%$pre%>password_result').innerHTML = '';
-% if ($opt{'contact_num'}) {
- document.getElementById('<% $opt{'pre_pwd_field_label'} %>selfservice_access').value = 'Y';
-% }
}
- document.getElementById('<%$pre%>form').style.display =
+ document.getElementById('<%$pre%>div').style.display =
toggle ? 'inline-block' : 'none';
% if (!$opt{'no_label_display'}) {
document.getElementById('<%$pre%>link').style.display =
% }
}
-function checkPasswordValidation() {
+function <%$pre%>checkPasswordValidation(resultId) {
var validationResult = document.getElementById('<%$pre%>password_result').innerHTML;
if (validationResult.match(/Password valid!/)) {
return true;
}
elsif ($opt{'contact_num'}) {
$change_id_input = '
- <INPUT TYPE="hidden" NAME="'.$opt{'pre_pwd_field_label'}.'contactnum" VALUE="' . $opt{'contact_num'} . '">
- <INPUT TYPE="hidden" NAME="'.$opt{'pre_pwd_field_label'}.'custnum" VALUE="' . $opt{'custnum'} . '">
+ <INPUT TYPE="hidden" NAME="contactnum" VALUE="' . $opt{'contact_num'} . '">
+ <INPUT TYPE="hidden" NAME="custnum" VALUE="' . $opt{'custnum'} . '">
';
$pre .= $opt{'pre_pwd_field_label'};
}
-% unless ( $opt{'js_only'} ) {
+% if ( $opt{'js_only'} ) {
+<% $js %>
+% } else {
<INPUT TYPE="hidden" NAME="<%$name%>" ID="<%$id%>" VALUE="<% $curr_value %>">
VALUE = ""
placeholder = "<% $value |h %>"
>
-% my $contactnum = $curr_value ? $curr_value : '0';
- <& '/elements/validate_password.html',
- 'fieldid' => "changepw".$id."_password",
- 'svcnum' => '',
- 'contactnum' => $contactnum,
- 'submitid' => "submit",
- &>
-
- <SCRIPT TYPE="text/javascript">
- var selfService = document.getElementById("<%$id%>_selfservice_access").value;
-
- if (selfService !== "Y") { document.getElementById("changepw<%$id%>_password").disabled = 'true'; }
- document.getElementById("<%$id%>_selfservice_access").onchange = function() {
- if (this.value == "P" || this.value == "E" || this.value =="Y") {
- document.getElementById("changepw<%$id%>_password").disabled = '';
- }
- else { document.getElementById("changepw<%$id%>_password").disabled = 'true'; }
- return false;
- }
+ <SCRIPT>
+ <% $js %>
</SCRIPT>
% } elsif ( $field eq 'invoice_dest' || $field eq 'message_dest' ) {
% my $curr_value = $cgi->param($name . '_' . $field);
<BR>
<FONT SIZE="-1"><% $label{$field} %></FONT>
% if ( $field eq 'password' ) {
- <div id="changepw<%$id%>_<%$field%>_result"></div>
+ <DIV ID="changepw<%$id%>_<%$field%>_result" STYLE="font-size: smaller"></DIV>
% }
</TD>
% }
my $id = $opt{'id'} || 'contactnum';
my $curr_value = $opt{'curr_value'} || $opt{'value'};
+my $contactnum = $curr_value ? $curr_value : '0';
my $onchange = '';
if ( $opt{'onchange'} ) {
my @fields = $opt{'name_only'} ? qw( first last ) : keys %label;
+my $js = qq(
+ add_password_validation('changepw$id\_password', 'submit', '', '$contactnum');
+
+ var selfService = document.getElementById("$id\_selfservice_access").value;
+
+ if (selfService !== "Y") { document.getElementById("changepw$id\_password").disabled = 'true'; }
+ document.getElementById("$id\_selfservice_access").onchange = function() {
+ if (this.value == "P" || this.value == "E" || this.value =="Y") {
+ document.getElementById("changepw$id\_password").disabled = '';
+ }
+ else { document.getElementById("changepw$id\_password").disabled = 'true'; }
+ return false;
+ }
+);
+
</%init>
% } else {
<& header-full.html, @_ &>
% }
+<& /misc/edge_browser_check-header.html &>
$report_financial{'Customer Accounting Summary'} = [ $fsurl.'search/report_customer_accounting_summary.html', 'Customer accounting summary report' ];
- $report_financial{'Upcoming Auto-Bill Transactions'} = [ $fsurl.'search/report_future_autobill.html', 'Upcoming auto-bill transactions' ];
+ if ( my $report_title = FS::cust_payby->future_autobill_report_title ) {
+ $report_financial{$report_title} = [ $fsurl.'search/report_future_autobill.html', "$report_title for customers with automatic payment methods (by date)" ];
+ }
} elsif($curuser->access_right('Receivables report')) {
</%doc>
-<& '/elements/xmlhttp.html',
- 'url' => $p.'misc/xmlhttp-validate_password.html',
- 'subs' => [ 'validate_password' ],
- 'method' => 'POST', # important not to put passwords in url
-&>
-<SCRIPT>
-function add_password_validation (fieldid, submitid) {
- var inputfield = document.getElementById(fieldid);
- inputfield.onkeydown = function(e) {
- var key;
- if (window.event) { key = window.event.keyCode; }
- else { key = e.which; } // for ff browsers
- // some browsers allow the enter key to submit a form even if the submit button is disabled
- // below prevents enter key from submiting form if password has not been validated.
- if (key == '13') {
- var check = checkPasswordValidation(fieldid);
- return check;
- }
- }
- inputfield.onkeyup = function () {
- var fieldid = this.id+'_result';
- var resultfield = document.getElementById(fieldid);
- if (this.value) {
- resultfield.innerHTML = '<SPAN STYLE="color: blue;">Validating password...</SPAN>';
- validate_password('fieldid',fieldid,'svcnum','<% $opt{'svcnum'} %>','contactnum','<% $opt{'contactnum'} %>','password',this.value,
- function (result) {
- result = JSON.parse(result);
- var resultfield = document.getElementById(result.fieldid);
- if (resultfield) {
- var errorimg = '<IMG SRC="<% $p %>images/error.png" style="width: 1em; display: inline-block; padding-right: .5em">';
- var validimg = '<IMG SRC="<% $p %>images/tick.png" style="width: 1em; display: inline-block; padding-right: .5em">';
- if (result.valid) {
- resultfield.innerHTML = validimg+'<SPAN STYLE="color: green;">Password valid!</SPAN>';
- if (submitid){ document.getElementById(submitid).disabled = false; }
- } else if (result.error) {
- resultfield.innerHTML = errorimg+'<SPAN STYLE="color: red;">'+result.error+'</SPAN>';
- if (submitid){ document.getElementById(submitid).disabled = true; }
- } else {
- result.syserror = result.syserror || 'Server error';
- resultfield.innerHTML = errorimg+'<SPAN STYLE="color: red;">'+result.syserror+'</SPAN>';
- if (submitid){ document.getElementById(submitid).disabled = true; }
- }
- }
- }
- );
- } else {
- resultfield.innerHTML = '';
- if (submitid){ document.getElementById(submitid).disabled = false; }
- }
- };
-}
+<& '/elements/validate_password_js.html', %opt &>
-add_password_validation('<% $opt{'fieldid'} %>', '<% $opt{'submitid'} %>');
+<SCRIPT>
+ add_password_validation('<% $opt{'fieldid'} %>', '<% $opt{'submitid'} %>', '<% $opt{'svcnum'} %>', '<% $opt{'contactnum'} %>');
</SCRIPT>
<%init>
--- /dev/null
+<%doc>
+
+JavaScript to perform password validation
+
+ <& '/elements/validate_password_js.html',
+ contactnum => $contactnum,
+ svcnum => $svcnum
+ &>
+
+The ID of the input field can be anything; the ID of the DIV in which to display results
+should be the input id plus '_result'.
+
+</%doc>
+
+<& '/elements/xmlhttp.html',
+ 'url' => $p.'misc/xmlhttp-validate_password.html',
+ 'subs' => [ 'validate_password' ],
+ 'method' => 'POST', # important not to put passwords in url
+&>
+<SCRIPT>
+function add_password_validation (fieldid, submitid, svcnum, contactnum) {
+ var inputfield = document.getElementById(fieldid);
+ inputfield.onkeydown = function(e) {
+ var key;
+ if (window.event) { key = window.event.keyCode; }
+ else { key = e.which; } // for ff browsers
+ // some browsers allow the enter key to submit a form even if the submit button is disabled
+ // below prevents enter key from submiting form if password has not been validated.
+ if (key == '13') {
+ var check = checkPasswordValidation(fieldid);
+ return check;
+ }
+ }
+ inputfield.onkeyup = function () {
+ var fieldid = this.id+'_result';
+ var resultfield = document.getElementById(fieldid);
+ if (this.value) {
+ resultfield.innerHTML = '<SPAN STYLE="color: blue;">Validating password...</SPAN>';
+ validate_password('fieldid',fieldid,'svcnum','<% $opt{'svcnum'} %>','contactnum', contactnum,'password',this.value,
+ function (result) {
+ result = JSON.parse(result);
+ var resultfield = document.getElementById(result.fieldid);
+ if (resultfield) {
+ var errorimg = '<IMG SRC="<% $p %>images/error.png" style="width: 1em; display: inline-block; padding-right: .5em">';
+ var validimg = '<IMG SRC="<% $p %>images/tick.png" style="width: 1em; display: inline-block; padding-right: .5em">';
+ if (result.valid) {
+ resultfield.innerHTML = validimg+'<SPAN STYLE="color: green;">Password valid!</SPAN>';
+ if (submitid){ document.getElementById(submitid).disabled = false; }
+ } else if (result.error) {
+ resultfield.innerHTML = errorimg+'<SPAN STYLE="color: red;">'+result.error+'</SPAN>';
+ if (submitid){ document.getElementById(submitid).disabled = true; }
+ } else {
+ result.syserror = result.syserror || 'Server error';
+ resultfield.innerHTML = errorimg+'<SPAN STYLE="color: red;">'+result.syserror+'</SPAN>';
+ if (submitid){ document.getElementById(submitid).disabled = true; }
+ }
+ }
+ }
+ );
+ } else {
+ resultfield.innerHTML = '';
+ if (submitid){ document.getElementById(submitid).disabled = false; }
+ }
+ };
+}
+
+</SCRIPT>
+
+<%init>
+my %opt = @_;
+</%init>
\ No newline at end of file
--- /dev/null
+<& /elements/header.html, "Edge browser bug" &>
+
+<div id="edgebug" style="border: solid 1px #888; border-radius: 4px; margin: 5em; max-width: 400px; text-align: left; padding: 0 1em; background-color: #ffe; box-shadow: 2px 2px 4px">
+ <div style="text-align: center; font-size: 3em; color: #933; text-shadow: 1px 1px 2px black;">
+ ⚠
+ </div>
+ <h4 style="border-bottom: solid 1px #888; margin: 1em 0; text-align: center;">
+ Edge Browser Bug
+ </h4>
+ <p>
+ Your copy of Microsoft Edge has a data corrupting bug.
+ </p>
+ <p>
+ Microsoft fixed this bug with the <b>July RS4 Windows 10 Update</b>.
+ Please update your copy of Windows.
+ </p>
+ <p>
+ Alternatively, you may choose to use
+ <a href="https://mozilla.org/en-US/firefox/new/">Mozilla Firefox</a>
+ or <a href="https://chrome.google.com">Google Chrome</a>. They
+ are not affected by this bug.
+ </p>
+</div>
+
+<& /elements/footer.html &>
\ No newline at end of file
--- /dev/null
+% if ( $force_redirect ) {
+ <script type="text/javascript">
+ if ( <% $DEBUG %> || /Edge\/17\.17134/.test( navigator.userAgent )) {
+ if ( window.location.href.indexOf("fail_notice") == -1 ) {
+ window.location.href = "<% $fsurl %>misc/edge_browser_check-fail_notice.html";
+ }
+ }
+ </script>
+% } elsif ( $do_check ) {
+ <iframe id="edge_browser_check_iframe" style="display:none;"></iframe>
+ <script type="text/javascript">
+ if ( <% $DEBUG %> || /Edge\/17\.17134/.test( navigator.userAgent )) {
+ $("#edge_browser_check_iframe").attr(
+ 'src',
+ '<% $fsurl %>misc/edge_browser_check-iframe.html?edge_browser_check=1'
+ );
+ }
+ </script>
+% }
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $session = $FS::CurrentUser::CurrentSession;
+my $sessionkey = $session->sessionkey if $session;
+
+my $cgi = FS::UID::cgi();
+my $DEBUG = 0;
+
+my $do_check = 0;
+$do_check = 1
+ if $curuser
+ && !$cgi->param('edge_browser_check')
+ && $sessionkey
+ && $curuser->get_pref('edge_bug_vulnerable') ne $sessionkey;
+
+my $force_redirect = $curuser->get_pref('edge_bug_vulnerable') eq 'Y' ? 1 : 0;
+</%init>
--- /dev/null
+<form id="canary-form" action="<% $fsurl %>misc/edge_browser_check-iframe.html" method="POST">
+<input type="text" id="canary-result" value="<% scalar $cgi->param('edge_browser_canary') %>">
+<select name="edge_browser_canary">
+ <option>test
+ <option>test
+</select>
+<input id="canary-submit" type="submit">
+</form>
+
+<script type="text/javascript" src="<% $fsurl %>elements/jquery.js"></script>
+<script type="text/javascript">
+ $( function() {
+ if ( ! $("#canary-result").val() ) {
+ $("#canary-form").submit();
+ }
+ });
+</script>
+
+<%init>
+my $cgi = FS::UID::cgi();
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $session = $FS::CurrentUser::CurrentSession;
+my $sessionkey = $session->sessionkey if $session;
+
+if ( $curuser ) {
+ my $canary = $cgi->param('edge_browser_canary');
+ $curuser->set_pref(
+ 'edge_bug_vulnerable',
+
+ $canary eq 'test' ? $sessionkey : 'Y',
+ );
+}
+
+</%init>
\ No newline at end of file
<% $cgi->redirect($fsurl.'view/svc_acct.cgi?'.$cgi->query_string) %>
% }
% elsif ($contactnum) {
- <% $cgi->redirect($fsurl.'edit/cust_main-contacts.html?'.$cgi->param('custnum')) %>
+ <% $cgi->redirect($fsurl.'view/cust_main.cgi?'.$cgi->param('custnum')) %>
% }
% }
$cgi->param('svcnum') =~ /^(\d+)$/ or die "illegal svcnum" if $cgi->param('svcnum');
my $svcnum = $1;
+foreach my $prefix (grep /^(.*)(password)$/, $cgi->param) {
+ $cgi->param('password' => $cgi->param($prefix));
+}
+
$cgi->param('contactnum') =~ /^(\d+)$/ or die "illegal contactnum" if $cgi->param('contactnum');
my $contactnum = $1;
Report listing upcoming auto-bill transactions
-Spec requested the ability to run this report with a longer date range,
-and see which charges will process on which day. Checkbox multiple_billing_dates
-enables this functionality.
+For every customer with a valid auto-bill payment method,
+report runs bill_and_collect() for each customer, for each
+day, from today through the report target date. After
+recording the results, all operations are rolled back.
-Performance:
-This is a dynamically generated report. The time this report takes to run
-will depends on the number of customers. Installations with a high number
-of auto-bill customers may find themselves unable to run this report
-because of browser timeout. Report could be implemented as a queued job if
-necessary, to solve the performance problem.
+This report relies on the ability to safely run bill_and_collect(),
+with all exports and messaging disabled, and then to roll back the
+results.
</%doc>
<& elements/grid-report.html,
- title => 'Upcoming auto-bill transactions',
+ title => $report_title,
rows => \@rows,
cells => \@cells,
table_width => "",
&>
<%init>
+ use FS::UID qw( dbh );
-use FS::UID qw( dbh myconnect );
+ die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
-die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+ my $DEBUG = $cgi->param('DEBUG') || 0;
+
+ my $report_title = FS::cust_payby->future_autobill_report_title;
+ my $agentnum = $cgi->param('agentnum')
+ if $cgi->param('agentnum') =~ /^\d+/;
my $target_dt;
my @target_dates;
my %noon = (
hour => 12,
minute => 0,
- second => 0
+ second => 0,
);
-
my $now_dt = DateTime->now;
$now_dt = DateTime->new(
- month => $now_dt->month,
- day => $now_dt->day,
- year => $now_dt->year,
+ month => $now_dt->month,
+ day => $now_dt->day,
+ year => $now_dt->year,
%noon,
);
if ($cgi->param('target_date')) {
my ($mm, $dd, $yy) = split /[\-\/]/,$cgi->param('target_date');
$target_dt = DateTime->new(
- month => $mm,
- day => $dd,
- year => $yy,
+ month => $mm,
+ day => $dd,
+ year => $yy,
%noon,
) if $mm && $dd & $yy;
# without a target date, default to tomorrow
unless ($target_dt) {
- $target_dt = DateTime->from_epoch( epoch => time() + 86400) ;
- $target_dt = DateTime->new(
- month => $target_dt->month,
- day => $target_dt->day,
- year => $target_dt->year,
- %noon
- );
+ $target_dt = $now_dt->clone->add( days => 1 );
}
- # If multiple_billing_dates checkbox selected, create a range of dates
- # from today until the given report date. Otherwise, use target date only.
- if ($cgi->param('multiple_billing_dates')) {
+ # Create a range of dates from today until the given report date
+ # (leaving the probably useless 'quick-report' mode, but disabled)
+ if ( 1 || $cgi->param('multiple_billing_dates')) {
my $walking_dt = DateTime->from_epoch(epoch => $now_dt->epoch);
until ($walking_dt->epoch > $target_dt->epoch) {
push @target_dates, $walking_dt->epoch;
push @target_dates, $target_dt->epoch;
}
- # List all customers with an auto-bill method
- #
- # my %cust_payby = map {$_->custnum => $_} qsearch({
- # table => 'cust_payby',
- # hashref => {
- # weight => { op => '>', value => '0' },
- # paydate => { op => '>', value => $target_dt->ymd },
- # },
- # order_by => " ORDER BY weight DESC ",
- # });
-
# List all customers with an auto-bill method that's not expired
my %cust_payby = map {$_->custnum => $_} qsearch({
- table => 'cust_payby',
- hashref => {
- weight => { op => '>', value => '0' },
- },
- order_by => " ORDER BY weight DESC ",
- extra_sql => " AND ( payby = 'CHEK' OR ( paydate > '".$target_dt->ymd."')) ",
+ table => 'cust_payby',
+ addl_from => 'JOIN cust_main USING (custnum)',
+ hashref => { weight => { op => '>', value => '0' }},
+ order_by => " ORDER BY weight DESC ",
+ extra_sql =>
+ "AND (
+ payby IN ('CHEK','DCHK','DCHEK')
+ OR ( paydate > '".$target_dt->ymd."')
+ )
+ AND " . $FS::CurrentUser::CurrentUser->agentnums_sql
+ . ($agentnum ? "AND cust_main.agentnum = $agentnum" : ''),
});
+ my $fakebill_time = time();
my %abreport;
my @rows;
local $@;
local $SIG{__DIE__};
- my $temp_dbh = myconnect();
- eval { # Creating sandbox dbh where all connections are to be rolled back
- local $FS::UID::dbh = $temp_dbh;
+
+ eval { # Sandbox
+
+ # Supress COMMIT statements
+ my $oldAutoCommit = $FS::UID::AutoCommit;
local $FS::UID::AutoCommit = 0;
+ local $FS::UID::ForceObeyAutoCommit = 1;
+
+ # Suppress notices generated by billing events
+ local $FS::Misc::DISABLE_ALL_NOTICES = 1;
+
+ # Bypass payment processing, recording a fake payment
+ local $FS::cust_main::Billing_Realtime::BOP_TESTING = 1;
+ local $FS::cust_main::Billing_Realtime::BOP_TESTING_SUCCESS = 1;
- # Generate report data into @rows
+ warn sprintf "Report involves %s customers", scalar keys %cust_payby
+ if $DEBUG;
+
+ # Run bill_and_collect(), for each customer with an autobill payment method,
+ # for each day represented in the report
for my $custnum (keys %cust_payby) {
my $cust_main = qsearchs('cust_main', {custnum => $custnum});
+ warn "-- Processing custnum $custnum\n"
+ if $DEBUG;
+
# walk forward through billing dates
for my $query_epoch (@target_dates) {
+ $FS::cust_main::Billing_Realtime::BOP_TESTING_TIMESTAMP = $query_epoch;
my $return_bill = [];
- eval { # Don't let an error on one customer crash the report
- my $error = $cust_main->bill(
- time => $query_epoch,
- return_bill => $return_bill,
- no_usage_reset => 1,
- );
- die "$error (simulating future billing)" if $error;
- };
- warn ("$@: (future_autobill custnum:$custnum)");
-
- if (@{$return_bill}) {
- my $inv = $return_bill->[0];
- push @rows,{
- name => $cust_main->name,
- _date => $inv->_date,
- cells => [
- { class => 'gridreport', value => $custnum },
- { class => 'gridreport',
- value => '<a href="/view/cust_main.cgi?"'.$custnum.'">'.$cust_main->name.'</a>',
- bypass_filter => 1,
- },
- { class => 'gridreport', value => $inv->charged, format => 'money' },
- { class => 'gridreport', value => DateTime->from_epoch(epoch=>$inv->_date)->ymd },
- { class => 'gridreport', value => ($cust_payby{$custnum}->payby || $cust_payby{$custnum}->paytype) },
- { class => 'gridreport', value => $cust_payby{$custnum}->paymask },
- ]
- };
- }
+ warn "---- Set billtime to ".
+ DateTime->from_epoch( epoch => $query_epoch )."\n"
+ if $DEBUG;
+
+ my $error = $cust_main->bill_and_collect(
+ time => $query_epoch,
+ return_bill => $return_bill,
+ no_usage_reset => 1,
+ fake => 1,
+ );
+ warn "!!! $error (simulating future billing)\n" if $error;
}
- $temp_dbh->rollback;
- } # /foreach $custnum
+ # Generate report rows from recorded payments in cust_pay
+ for my $cust_pay (
+ qsearch( cust_pay => {
+ custnum => $custnum,
+ _date => { op => '>=', value => $fakebill_time },
+ })
+ ) {
+ push @rows,{
+ name => $cust_main->name,
+ _date => $cust_pay->_date,
+ cells => [
+
+ # Customer number
+ { class => 'gridreport', value => $custnum },
+
+ # Customer name / customer link
+ { class => 'gridreport',
+ value => qq{<a href="${fsurl}view/cust_main.cgi?${custnum}">} . encode_entities( $cust_main->name ). '</a>',
+ bypass_filter => 1
+ },
+
+ # Amount
+ { class => 'gridreport',
+ value => $cust_pay->paid,
+ format => 'money'
+ },
+
+ # Transaction Date
+ { class => 'gridreport',
+ value => DateTime->from_epoch( epoch => $cust_pay->_date )->ymd
+ },
+
+ # Payment Method
+ { class => 'gridreport',
+ value => encode_entities( $cust_pay->paycardtype || $cust_pay->payby ),
+ },
+
+ # Masked Payment Instrument
+ { class => 'gridreport',
+ value => encode_entities( $cust_pay->paymask ),
+ },
+ ]
+ };
+
+ } # /foreach payment
+
+ # Roll back database at the end of each customer
+ # Makes the report slighly slower, but ensures only one customer row
+ # locked at a time
+
+ warn "-- custnum $custnum -- rollback()\n" if $DEBUG;
+ dbh->rollback if $oldAutoCommit;
+
+ } # /foreach $custnum
}; # /eval
- warn("$@") if $@;
+ warn("future_autobill.html report generated error $@") if $@;
# Sort output by date, and format for output to grid-report.html
my @cells = [
Display date selector for the future_autobill.html report
</%doc>
-<% include('/elements/header.html', 'Future Auto-Bill Transactions' ) %>
+<% include('/elements/header.html', $report_title ) %>
-<FORM ACTION="future_autobill.html" METHOD="GET">
-<TABLE>
-<& /elements/tr-input-date-field.html,
- {
- name => 'target_date',
- value => $target_date,
- label => emt('Target billing date').': ',
- required => 1
- }
-&>
+% if ( FS::TaxEngine->new->info->{batch} ) {
-<& /elements/tr-checkbox.html,
- 'label' => emt('Multiple billing dates (slow)').': ',
- 'field' => 'multiple_billing_dates',
- 'value' => '1',
-&>
+ <div style="font-color: red">
+ NOTE: This report is disabled due to tax engine configuration
+ </div>
-</TABLE>
+% } else {
-<BR>
-<INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>">
+ <FORM ACTION="future_autobill.html" METHOD="GET">
+ <TABLE>
+ <& /elements/tr-input-date-field.html,
+ {
+ name => 'target_date',
+ value => $target_date,
+ label => emt('Target billing date').': ',
+ required => 1
+ }
+ &>
-</FORM>
+ <% include('/elements/tr-select-agent.html',
+ 'label' => 'For agent: ',
+ 'disable_empty' => 0,
+ )
+ %>
+
+ </TABLE>
+
+ <BR>
+
+ <INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>">
+
+ </FORM>
+
+% }
<% include('/elements/footer.html') %>
<%init>
+use FS::cust_payby;
+use FS::CurrentUser;
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
-my $target_date = DateTime->from_epoch(epoch=>(time()+86400))->mdy('/');
+my $target_date = DateTime->now->add(days => 1)->mdy('/');
+my $report_title = FS::cust_payby->future_autobill_report_title;
</%init>
+
% my $bgcolor1 = '#ffffff';
% my $bgcolor2 = '#eeeeee';
% my $bgcolor = $bgcolor2;
+% my $count = 0;
% foreach my $cust_contact ( @cust_contacts ) {
% my $contact = $cust_contact->contact;
% my $td = qq(<TD CLASS="grid" BGCOLOR="$bgcolor">);
Enabled
%# <FONT SIZE="-1"><A HREF="XXX">disable</A>
%# <A HREF="XXX">re-email</A></FONT>
+ <FONT SIZE="-1">
+ <& /elements/change_password.html,
+ 'contact_num' => $cust_contact->contactnum,
+ 'custnum' => $cust_contact->custnum,
+ 'no_label_display' => '',
+ 'label' => 'change password',
+ 'curr_value' => '',
+ 'pre_pwd_field_label' => 'contact'.$count.'_',
+ &>
+ </FONT>
% } else {
Disabled
%# <FONT SIZE="-1"><A HREF="XXX">enable</A></FONT>
% } else {
% $bgcolor = $bgcolor1;
% }
+% $count++;
% }
</TABLE>
%}
# residential customers have a default "invisible" contact, but if they
# somehow get more than one contact, show them
-my $display = scalar(@cust_contacts) > 1;
+my $display = scalar(@cust_contacts) > 0;
</%init>
<TABLE BORDER=0 CELLSPACING=2 CELLPADDING=0>
<TR>
<TD>
- Sorry we were unable to locate your account with ip <? echo $username; ?> .
+ Sorry we were unable to locate your account with MAC address <? echo $username; ?> .
</TD>
</TR>
</TABLE>
$freeside = new FreesideSelfService();
$ip = $_SERVER['REMOTE_ADDR'];
-# need a routine here to get mac address from radius account table based on ip address. Every else should be good to go.
-$mac_addr = '1234567890FF';
+
+$mac_addr = $freeside->get_mac_address( array('ip' => $ip, ) );
$response = $freeside->login( array(
- 'username' => $mac_addr,
+ 'username' => $mac_addr['mac_address'],
'domain' => 'ip_mac',
) );
-#error_log("[login] received response from freeside: $response");
-
$error = $response['error'];
if ( $error ) {