diff options
Diffstat (limited to 'httemplate')
-rw-r--r-- | httemplate/edit/cust_main-contacts.html | 27 | ||||
-rw-r--r-- | httemplate/edit/elements/edit.html | 2 | ||||
-rw-r--r-- | httemplate/edit/process/cust_main-contacts.html | 2 | ||||
-rw-r--r-- | httemplate/elements/change_password.html | 15 | ||||
-rw-r--r-- | httemplate/elements/contact.html | 43 | ||||
-rw-r--r-- | httemplate/elements/header.html | 1 | ||||
-rw-r--r-- | httemplate/elements/menu.html | 4 | ||||
-rw-r--r-- | httemplate/elements/validate_password.html | 55 | ||||
-rw-r--r-- | httemplate/elements/validate_password_js.html | 71 | ||||
-rw-r--r-- | httemplate/misc/edge_browser_check-fail_notice.html | 25 | ||||
-rw-r--r-- | httemplate/misc/edge_browser_check-header.html | 36 | ||||
-rw-r--r-- | httemplate/misc/edge_browser_check-iframe.html | 34 | ||||
-rw-r--r-- | httemplate/misc/process/change-password.html | 6 | ||||
-rw-r--r-- | httemplate/search/future_autobill.html | 210 | ||||
-rw-r--r-- | httemplate/search/report_future_autobill.html | 57 | ||||
-rw-r--r-- | httemplate/view/cust_main/contacts_new.html | 14 |
16 files changed, 402 insertions, 200 deletions
diff --git a/httemplate/edit/cust_main-contacts.html b/httemplate/edit/cust_main-contacts.html index 3783cb9e9..abef7505d 100644 --- a/httemplate/edit/cust_main-contacts.html +++ b/httemplate/edit/cust_main-contacts.html @@ -5,18 +5,20 @@ this one isn't being maintained well. :/ </%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', @@ -58,6 +60,13 @@ function checkPasswordValidation(fieldid) { 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')); diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index 8ba703a2f..b7f2e7adb 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -669,7 +669,7 @@ Example: 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 = ''; diff --git a/httemplate/edit/process/cust_main-contacts.html b/httemplate/edit/process/cust_main-contacts.html index 5b8319f5a..6b7f1c2db 100644 --- a/httemplate/edit/process/cust_main-contacts.html +++ b/httemplate/edit/process/cust_main-contacts.html @@ -8,7 +8,7 @@ </%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, diff --git a/httemplate/elements/change_password.html b/httemplate/elements/change_password.html index 7d95e19dc..65b7d8502 100644 --- a/httemplate/elements/change_password.html +++ b/httemplate/elements/change_password.html @@ -11,9 +11,9 @@ % 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 %> @@ -44,11 +44,8 @@ function <%$pre%>toggle(toggle, clear) { 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 = @@ -56,7 +53,7 @@ function <%$pre%>toggle(toggle, clear) { % } } -function checkPasswordValidation() { +function <%$pre%>checkPasswordValidation(resultId) { var validationResult = document.getElementById('<%$pre%>password_result').innerHTML; if (validationResult.match(/Password valid!/)) { return true; @@ -83,8 +80,8 @@ if ($opt{'svc_acct'}) { } 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'}; } diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html index 7b6c853d4..909ff7893 100644 --- a/httemplate/elements/contact.html +++ b/httemplate/elements/contact.html @@ -1,4 +1,6 @@ -% unless ( $opt{'js_only'} ) { +% if ( $opt{'js_only'} ) { +<% $js %> +% } else { <INPUT TYPE="hidden" NAME="<%$name%>" ID="<%$id%>" VALUE="<% $curr_value %>"> @@ -75,25 +77,8 @@ 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); @@ -118,7 +103,7 @@ <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> % } @@ -138,6 +123,7 @@ my $name = $opt{'element_name'} || $opt{'field'} || 'contactnum'; 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'} ) { @@ -205,4 +191,19 @@ $label{'comment'} = 'Comment'; 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> diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html index c6b10e301..6df45fb07 100644 --- a/httemplate/elements/header.html +++ b/httemplate/elements/header.html @@ -4,3 +4,4 @@ % } else { <& header-full.html, @_ &> % } +<& /misc/edge_browser_check-header.html &> diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index eb065b668..cae0cdbfb 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -418,7 +418,9 @@ if( $curuser->access_right('Financial reports') ) { $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')) { diff --git a/httemplate/elements/validate_password.html b/httemplate/elements/validate_password.html index 73c0db281..6aada2fee 100644 --- a/httemplate/elements/validate_password.html +++ b/httemplate/elements/validate_password.html @@ -14,59 +14,10 @@ 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) { - 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> diff --git a/httemplate/elements/validate_password_js.html b/httemplate/elements/validate_password_js.html new file mode 100644 index 000000000..64db0a97b --- /dev/null +++ b/httemplate/elements/validate_password_js.html @@ -0,0 +1,71 @@ +<%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 diff --git a/httemplate/misc/edge_browser_check-fail_notice.html b/httemplate/misc/edge_browser_check-fail_notice.html new file mode 100644 index 000000000..fb42ffe8e --- /dev/null +++ b/httemplate/misc/edge_browser_check-fail_notice.html @@ -0,0 +1,25 @@ +<& /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 diff --git a/httemplate/misc/edge_browser_check-header.html b/httemplate/misc/edge_browser_check-header.html new file mode 100644 index 000000000..a88962be9 --- /dev/null +++ b/httemplate/misc/edge_browser_check-header.html @@ -0,0 +1,36 @@ +% 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> diff --git a/httemplate/misc/edge_browser_check-iframe.html b/httemplate/misc/edge_browser_check-iframe.html new file mode 100644 index 000000000..61ae9a0bd --- /dev/null +++ b/httemplate/misc/edge_browser_check-iframe.html @@ -0,0 +1,34 @@ +<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 diff --git a/httemplate/misc/process/change-password.html b/httemplate/misc/process/change-password.html index a3e060168..37ad6d915 100644 --- a/httemplate/misc/process/change-password.html +++ b/httemplate/misc/process/change-password.html @@ -18,7 +18,7 @@ <% $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')) %> % } % } @@ -34,6 +34,10 @@ my $curuser = $FS::CurrentUser::CurrentUser; $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; diff --git a/httemplate/search/future_autobill.html b/httemplate/search/future_autobill.html index 711a25f82..d4ad8e524 100644 --- a/httemplate/search/future_autobill.html +++ b/httemplate/search/future_autobill.html @@ -2,20 +2,18 @@ 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 => "", @@ -32,11 +30,16 @@ necessary, to solve the performance problem. &> <%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; @@ -45,14 +48,13 @@ die "access denied" 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, ); @@ -60,9 +62,9 @@ die "access denied" 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; @@ -72,18 +74,12 @@ die "access denied" # 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; @@ -93,80 +89,128 @@ die "access denied" 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 = [ diff --git a/httemplate/search/report_future_autobill.html b/httemplate/search/report_future_autobill.html index 1a0c9f48a..ccde299e9 100644 --- a/httemplate/search/report_future_autobill.html +++ b/httemplate/search/report_future_autobill.html @@ -3,40 +3,55 @@ 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> + diff --git a/httemplate/view/cust_main/contacts_new.html b/httemplate/view/cust_main/contacts_new.html index fe412cc00..9252b2197 100644 --- a/httemplate/view/cust_main/contacts_new.html +++ b/httemplate/view/cust_main/contacts_new.html @@ -22,6 +22,7 @@ % 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">); @@ -39,6 +40,16 @@ 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> @@ -63,6 +74,7 @@ % } else { % $bgcolor = $bgcolor1; % } +% $count++; % } </TABLE> %} @@ -80,6 +92,6 @@ my @cust_contacts = $cust_main->cust_contact; # 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> |