From 8ca45ec1f1ab960ee1e262ee764f05d214728d05 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Tue, 4 Sep 2018 08:30:35 -0400 Subject: RT# 75817 - Added password change link to cust main page --- httemplate/edit/cust_main-contacts.html | 20 +++++----- httemplate/elements/change_password.html | 15 +++----- httemplate/elements/contact.html | 10 ++--- httemplate/elements/validate_password.html | 55 ++-------------------------- httemplate/misc/process/change-password.html | 6 ++- httemplate/view/cust_main/contacts_new.html | 14 ++++++- 6 files changed, 42 insertions(+), 78 deletions(-) diff --git a/httemplate/edit/cust_main-contacts.html b/httemplate/edit/cust_main-contacts.html index 3783cb9e9..7c5a78209 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. :/ - +<& '/elements/validate_password_js.html', &> + <& elements/edit.html, 'name_singular' => 'customer contacts', #yes, we're editing all of them 'table' => 'cust_main', 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'}) { (<% emt( $change_title ) %>) % } -
+
% if (!$opt{'noformtag'}) { -
+ % } <% $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 = ' - - + + '; $pre .= $opt{'pre_pwd_field_label'}; } diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html index 7b6c853d4..5588890c8 100644 --- a/httemplate/elements/contact.html +++ b/httemplate/elements/contact.html @@ -76,12 +76,10 @@ placeholder = "<% $value |h %>" > % my $contactnum = $curr_value ? $curr_value : '0'; - <& '/elements/validate_password.html', - 'fieldid' => "changepw".$id."_password", - 'svcnum' => '', - 'contactnum' => $contactnum, - 'submitid' => "submit", - &> + + <%init> 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/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(); @@ -39,6 +40,16 @@ Enabled %# disable %# re-email + + <& /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.'_', + &> + % } else { Disabled %# enable @@ -63,6 +74,7 @@ % } else { % $bgcolor = $bgcolor1; % } +% $count++; % } %} @@ -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; -- cgit v1.2.1 From 0c9c9e95f2f32e7d55dd53f2903c21eb8ce7af6b Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Tue, 4 Sep 2018 08:32:21 -0400 Subject: RT# 75817 - added new file to hold javascript for password validation --- httemplate/elements/validate_password_js.html | 71 +++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 httemplate/elements/validate_password_js.html 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'. + + + +<& '/elements/xmlhttp.html', + 'url' => $p.'misc/xmlhttp-validate_password.html', + 'subs' => [ 'validate_password' ], + 'method' => 'POST', # important not to put passwords in url +&> + + +<%init> +my %opt = @_; + \ No newline at end of file -- cgit v1.2.1 From 4e00b1e784fdb369fefd538f27c1ac465e5abc9a Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Fri, 7 Sep 2018 10:55:19 -0400 Subject: RT# 75817 - fixed saving of password for new contacts, and password validation on dynamicly created rows --- FS/FS/contact.pm | 13 +++++++--- httemplate/edit/cust_main-contacts.html | 7 +++++ httemplate/edit/elements/edit.html | 2 +- httemplate/edit/process/elements/process.html | 2 +- httemplate/elements/contact.html | 37 +++++++++++++++------------ 5 files changed, 39 insertions(+), 22 deletions(-) diff --git a/FS/FS/contact.pm b/FS/FS/contact.pm index fa047f59d..81dfdbc01 100644 --- a/FS/FS/contact.pm +++ b/FS/FS/contact.pm @@ -199,8 +199,6 @@ sub insert { } - $error ||= $self->insert_password_history; - if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -302,6 +300,15 @@ sub insert { } } + 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; ''; @@ -811,7 +818,7 @@ sub authenticate_password { $hash eq $check_hash; - } else { + } else { return 0 if $self->_password eq ''; diff --git a/httemplate/edit/cust_main-contacts.html b/httemplate/edit/cust_main-contacts.html index 7c5a78209..abef7505d 100644 --- a/httemplate/edit/cust_main-contacts.html +++ b/httemplate/edit/cust_main-contacts.html @@ -60,6 +60,13 @@ this one isn't being maintained well. :/ 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/elements/process.html b/httemplate/edit/process/elements/process.html index d2b037053..a3ea7ee59 100644 --- a/httemplate/edit/process/elements/process.html +++ b/httemplate/edit/process/elements/process.html @@ -127,7 +127,7 @@ Example: % " attempting to set redirect$session to ". $cgi->query_string."\n"; % } % -<% $cgi->redirect("$error_redirect?redirect=$session") %> +<% $cgi->redirect("$error_redirect"."redirect=$session") %> % % } else { % diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html index 5588890c8..5fa8ae485 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 { @@ -75,23 +77,8 @@ VALUE = "" placeholder = "<% $value |h %>" > -% my $contactnum = $curr_value ? $curr_value : '0'; - - - % } elsif ( $field eq 'invoice_dest' || $field eq 'message_dest' ) { % my $curr_value = $cgi->param($name . '_' . $field); @@ -136,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'} ) { @@ -203,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; + } +); + -- cgit v1.2.1 From 40062272379e4c32add419f839da60742719da2d Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Sun, 9 Sep 2018 16:12:23 -0400 Subject: RT# 75817 - cleaned up password validation error message --- httemplate/elements/contact.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html index 5fa8ae485..909ff7893 100644 --- a/httemplate/elements/contact.html +++ b/httemplate/elements/contact.html @@ -103,7 +103,7 @@
<% $label{$field} %> % if ( $field eq 'password' ) { -
+
% } % } -- cgit v1.2.1 From 6edc36f4ffe2d2a53d4c8cea4318f3a95eda54b4 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Sun, 9 Sep 2018 17:34:35 -0400 Subject: RT# 75817 - fixed redirect error --- httemplate/edit/process/cust_main-contacts.html | 2 +- httemplate/edit/process/elements/process.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 @@ <% 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/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html index a3ea7ee59..d2b037053 100644 --- a/httemplate/edit/process/elements/process.html +++ b/httemplate/edit/process/elements/process.html @@ -127,7 +127,7 @@ Example: % " attempting to set redirect$session to ". $cgi->query_string."\n"; % } % -<% $cgi->redirect("$error_redirect"."redirect=$session") %> +<% $cgi->redirect("$error_redirect?redirect=$session") %> % % } else { % -- cgit v1.2.1 From 3c1586b6d7ae963d7ead013ac84ad5d063741eed Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Sun, 9 Sep 2018 20:03:06 -0400 Subject: RT# 80624 Edge browser bug warning at login --- httemplate/loginout/login.html | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/httemplate/loginout/login.html b/httemplate/loginout/login.html index b787a3eea..138c6adc1 100644 --- a/httemplate/loginout/login.html +++ b/httemplate/loginout/login.html @@ -34,8 +34,38 @@ + + + + <%init> -- cgit v1.2.1 From 28a945ff8daa67036c73ac2287a3f4ebdea6d822 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Mon, 10 Sep 2018 14:25:44 -0400 Subject: RT# 81131 - updated Saisei export to create a virtual ap for multiple ips per customer --- FS/FS/part_export/saisei.pm | 60 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/FS/FS/part_export/saisei.pm b/FS/FS/part_export/saisei.pm index 1b7295d04..6db43c11d 100644 --- a/FS/FS/part_export/saisei.pm +++ b/FS/FS/part_export/saisei.pm @@ -201,12 +201,28 @@ sub _export_insert { 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'}; } @@ -216,8 +232,8 @@ sub _export_insert { 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 { @@ -817,6 +833,44 @@ sub process_sector { 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; -- cgit v1.2.1 From 829aa888318799d2ff4871c92b0d457abda49714 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Tue, 11 Sep 2018 03:23:52 -0400 Subject: RT# 78547 Allow for simulated billing within a transaction --- FS/FS/Misc/Savepoint.pm | 2 +- FS/FS/UID.pm | 12 +++++++++++- FS/FS/cust_bill.pm | 6 ++++++ FS/FS/cust_main.pm | 34 ++++++++++++++++++++++++++-------- FS/FS/cust_main/Billing.pm | 17 +++++++++++++++-- FS/FS/cust_main/Billing_Realtime.pm | 31 ++++++++++++++++++++----------- FS/FS/part_export/nena2.pm | 8 ++++++++ 7 files changed, 87 insertions(+), 23 deletions(-) diff --git a/FS/FS/Misc/Savepoint.pm b/FS/FS/Misc/Savepoint.pm index b15b36ded..f8e2c5ff5 100644 --- a/FS/FS/Misc/Savepoint.pm +++ b/FS/FS/Misc/Savepoint.pm @@ -55,7 +55,7 @@ Savepoints cannot work while AutoCommit is enabled. 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 diff --git a/FS/FS/UID.pm b/FS/FS/UID.pm index 50a917895..693e5d952 100644 --- a/FS/FS/UID.pm +++ b/FS/FS/UID.pm @@ -5,7 +5,7 @@ use strict; 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 ); @@ -26,7 +26,17 @@ $freeside_uid = scalar(getpwnam('freeside')); $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 diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 47f71c458..7158cb285 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -41,6 +41,7 @@ use FS::cust_bill_void; use FS::reason; use FS::reason_type; use FS::L10N; +use FS::Misc::Savepoint; $DEBUG = 0; $me = '[FS::cust_bill]'; @@ -974,6 +975,9 @@ sub apply_payments_and_credits { 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 } @@ -1062,6 +1066,7 @@ sub apply_payments_and_credits { my $error = $app->insert(%options); if ( $error ) { + savepoint_rollback_and_release( $savepoint_label ); $dbh->rollback if $oldAutoCommit; return "Error inserting ". $app->table. " record: $error"; } @@ -1069,6 +1074,7 @@ sub apply_payments_and_credits { } + savepoint_release( $savepoint_label ); $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; #no error diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index ea524dae4..2e8fe8159 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -79,6 +79,7 @@ use FS::sales; 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 @@ -2212,11 +2213,15 @@ sub cancel_pkgs { 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'); @@ -2233,7 +2238,8 @@ sub cancel_pkgs { 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 ); } @@ -2253,11 +2259,13 @@ sub cancel_pkgs { '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, @@ -2271,17 +2279,22 @@ sub cancel_pkgs { 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) { @@ -2297,8 +2310,11 @@ sub cancel_pkgs { @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 ) { @@ -2319,10 +2335,12 @@ sub cancel_pkgs { } 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; } } diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm index 71d5c9b81..1be7d39f9 100644 --- a/FS/FS/cust_main/Billing.pm +++ b/FS/FS/cust_main/Billing.pm @@ -26,6 +26,7 @@ use FS::pkg_category; 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 @@ -1753,7 +1754,10 @@ sub collect { $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 ), @@ -1961,9 +1965,13 @@ sub do_cust_event { } $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 ) { @@ -2288,16 +2296,21 @@ sub apply_payments_and_credits { 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 diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm index d286f635e..714a2e687 100644 --- a/FS/FS/cust_main/Billing_Realtime.pm +++ b/FS/FS/cust_main/Billing_Realtime.pm @@ -16,6 +16,7 @@ use FS::cust_bill_pay; use FS::cust_refund; use FS::banned_pay; use FS::payment_gateway; +use FS::Misc::Savepoint; $realtime_bop_decline_quiet = 0; @@ -27,6 +28,7 @@ $me = '[FS::cust_main::Billing_Realtime]'; our $BOP_TESTING = 0; our $BOP_TESTING_SUCCESS = 1; +our $BOP_TESTING_TIMESTAMP = ''; install_callback FS::UID sub { $conf = new FS::Conf; @@ -405,7 +407,7 @@ sub realtime_bop { 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; @@ -682,7 +684,7 @@ sub realtime_bop { 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}, @@ -757,7 +759,7 @@ sub realtime_bop { 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; @@ -946,7 +948,7 @@ sub _realtime_bop_result { '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, @@ -967,12 +969,16 @@ sub _realtime_bop_result { 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'} ? @@ -981,7 +987,8 @@ sub _realtime_bop_result { 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". @@ -996,9 +1003,10 @@ sub _realtime_bop_result { 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; @@ -1008,7 +1016,8 @@ sub _realtime_bop_result { $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"; @@ -1030,8 +1039,8 @@ sub _realtime_bop_result { 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"; @@ -1039,7 +1048,7 @@ sub _realtime_bop_result { return $e; } else { - + savepoint_release( $savepoint_label ); $dbh->commit or die $dbh->errstr if $oldAutoCommit; if ( $options{'apply'} ) { diff --git a/FS/FS/part_export/nena2.pm b/FS/FS/part_export/nena2.pm index f6a730ebc..cc4069c72 100644 --- a/FS/FS/part_export/nena2.pm +++ b/FS/FS/part_export/nena2.pm @@ -10,6 +10,7 @@ use Date::Format qw(time2str); use Parse::FixedLength; use File::Temp qw(tempfile); use vars qw(%info %options $initial_load_hack $DEBUG); +use Carp qw( carp ); my %upload_targets; @@ -396,6 +397,13 @@ sub process { 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; -- cgit v1.2.1 From b89e874e8258288d15c98ed3799a9fede6515fd5 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Tue, 11 Sep 2018 03:33:33 -0400 Subject: RT# 78547 Upcoming Auto-Bill Transaction Report --- FS/FS/cust_payby.pm | 25 +++ httemplate/search/future_autobill.html | 213 ++++++++++++++++---------- httemplate/search/report_future_autobill.html | 66 +++++--- 3 files changed, 205 insertions(+), 99 deletions(-) diff --git a/FS/FS/cust_payby.pm b/FS/FS/cust_payby.pm index c497059fa..301eb6106 100644 --- a/FS/FS/cust_payby.pm +++ b/FS/FS/cust_payby.pm @@ -914,8 +914,33 @@ sub search_sql { =back +=item count_autobill_cards + +Returns the number of unexpired cards configured for autobill + +=cut + +sub count_autobill_cards { + shift->count(" + weight > 0 + AND payby IN ('CARD','DCRD') + AND paydate > '".DateTime->now->ymd."' + "); +} + +=item count_autobill_checks + +Returns the number of check accounts configured for autobill + =cut +sub count_autobill_checks { + shift->count(" + weight > 0 + AND payby IN ('CHEK','DCHEK') + "); +} + sub _upgrade_data { my $class = shift; diff --git a/httemplate/search/future_autobill.html b/httemplate/search/future_autobill.html index 711a25f82..d6438d9dc 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. <& elements/grid-report.html, - title => 'Upcoming auto-bill transactions', + title => $report_title, rows => \@rows, cells => \@cells, table_width => "", @@ -32,11 +30,12 @@ necessary, to solve the performance problem. &> <%init> + use FS::UID qw( dbh myconnect ); -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 $target_dt; my @target_dates; @@ -45,14 +44,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 +58,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 +70,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,17 +85,6 @@ 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', @@ -111,62 +92,121 @@ die "access denied" weight => { op => '>', value => '0' }, }, order_by => " ORDER BY weight DESC ", - extra_sql => " AND ( payby = 'CHEK' OR ( paydate > '".$target_dt->ymd."')) ", + extra_sql => " + AND ( + payby IN ('CHEK','DCHK') + OR ( paydate > '".$target_dt->ymd."') + ) + ", }); + 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 + + # Create new database handle and supress all 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; - # Generate report data into @rows + # 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; + + 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 => ''.$cust_main->name.'', - 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{} . encode_entities( $cust_main->name ). '', + 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"; + 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 = [ @@ -186,4 +226,21 @@ die "access denied" # grid-report.html requires a parallel @rows parameter to accompany @cells @rows = map { {class => 'gridreport'} } 1..scalar(@cells); + # Dynamic report title + my $title_types = ''; + my $card_count = FS::cust_payby->count_autobill_cards; + my $check_count = FS::cust_payby->count_autobill_checks; + if ( $card_count && $check_count ) { + $title_types = 'Card and Check'; + } elsif ( $card_count ) { + $title_types = 'Card'; + } elsif ( $check_count ) { + $title_types = 'Check'; + } + + my $report_title = sprintf( + 'Upcoming Auto Bill %s Transactions', + $title_types, + ); + diff --git a/httemplate/search/report_future_autobill.html b/httemplate/search/report_future_autobill.html index 1a0c9f48a..ff2f85715 100644 --- a/httemplate/search/report_future_autobill.html +++ b/httemplate/search/report_future_autobill.html @@ -3,40 +3,64 @@ Display date selector for the future_autobill.html report -<% include('/elements/header.html', 'Future Auto-Bill Transactions' ) %> +<% include('/elements/header.html', $report_title ) %> -
- -<& /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', -&> +
+ NOTE: This report is disabled due to tax engine configuration +
-
+% } else { -
- + + + <& /elements/tr-input-date-field.html, + { + name => 'target_date', + value => $target_date, + label => emt('Target billing date').': ', + required => 1 + } + &> - +
+ +
+ + + + + +% } <% include('/elements/footer.html') %> <%init> +use FS::cust_payby; 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('/'); + +# Dynamic report title +my $title_types = ''; +my $card_count = FS::cust_payby->count_autobill_cards; +my $check_count = FS::cust_payby->count_autobill_checks; +if ( $card_count && $check_count ) { + $title_types = 'Card and Check'; +} elsif ( $card_count ) { + $title_types = 'Card'; +} elsif ( $check_count ) { + $title_types = 'Check'; +} + +my $report_title = sprintf( + 'Upcoming Auto Bill %s Transactions', + $title_types, +); + -- cgit v1.2.1 From 988d4dcd7e27b38f15edf00ae5b9dd75b9bd0c35 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Tue, 11 Sep 2018 05:51:11 -0400 Subject: RT# 78547 Future autobill report - agent virt, dynamic title --- FS/FS/cust_payby.pm | 75 ++++++++++++++++++++++----- httemplate/search/future_autobill.html | 45 ++++++---------- httemplate/search/report_future_autobill.html | 25 +++------ 3 files changed, 86 insertions(+), 59 deletions(-) diff --git a/FS/FS/cust_payby.pm b/FS/FS/cust_payby.pm index 301eb6106..9d8be120a 100644 --- a/FS/FS/cust_payby.pm +++ b/FS/FS/cust_payby.pm @@ -1,5 +1,6 @@ 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 ); @@ -914,31 +915,79 @@ sub search_sql { =back -=item count_autobill_cards +=item has_autobill_cards Returns the number of unexpired cards configured for autobill =cut -sub count_autobill_cards { - shift->count(" - weight > 0 - AND payby IN ('CARD','DCRD') - AND paydate > '".DateTime->now->ymd."' - "); +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 count_autobill_checks +=item has_autobill_checks Returns the number of check accounts configured for autobill =cut -sub count_autobill_checks { - shift->count(" - weight > 0 - AND payby IN ('CHEK','DCHEK') - "); +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 { diff --git a/httemplate/search/future_autobill.html b/httemplate/search/future_autobill.html index d6438d9dc..d4ad8e524 100644 --- a/httemplate/search/future_autobill.html +++ b/httemplate/search/future_autobill.html @@ -30,13 +30,17 @@ results. &> <%init> - use FS::UID qw( dbh myconnect ); + use FS::UID qw( dbh ); 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; @@ -87,17 +91,17 @@ results. # 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 IN ('CHEK','DCHK') + 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(); @@ -109,7 +113,7 @@ results. eval { # Sandbox - # Create new database handle and supress all COMMIT statements + # Supress COMMIT statements my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; local $FS::UID::ForceObeyAutoCommit = 1; @@ -201,7 +205,7 @@ results. # Makes the report slighly slower, but ensures only one customer row # locked at a time - warn "-- custnum $custnum -- rollback()\n"; + warn "-- custnum $custnum -- rollback()\n" if $DEBUG; dbh->rollback if $oldAutoCommit; } # /foreach $custnum @@ -226,21 +230,4 @@ results. # grid-report.html requires a parallel @rows parameter to accompany @cells @rows = map { {class => 'gridreport'} } 1..scalar(@cells); - # Dynamic report title - my $title_types = ''; - my $card_count = FS::cust_payby->count_autobill_cards; - my $check_count = FS::cust_payby->count_autobill_checks; - if ( $card_count && $check_count ) { - $title_types = 'Card and Check'; - } elsif ( $card_count ) { - $title_types = 'Card'; - } elsif ( $check_count ) { - $title_types = 'Check'; - } - - my $report_title = sprintf( - 'Upcoming Auto Bill %s Transactions', - $title_types, - ); - diff --git a/httemplate/search/report_future_autobill.html b/httemplate/search/report_future_autobill.html index ff2f85715..ccde299e9 100644 --- a/httemplate/search/report_future_autobill.html +++ b/httemplate/search/report_future_autobill.html @@ -25,6 +25,12 @@ Display date selector for the future_autobill.html report } &> + <% include('/elements/tr-select-agent.html', + 'label' => 'For agent: ', + 'disable_empty' => 0, + ) + %> +
@@ -39,28 +45,13 @@ Display date selector for the future_autobill.html report <%init> use FS::cust_payby; +use FS::CurrentUser; die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); my $target_date = DateTime->now->add(days => 1)->mdy('/'); - -# Dynamic report title -my $title_types = ''; -my $card_count = FS::cust_payby->count_autobill_cards; -my $check_count = FS::cust_payby->count_autobill_checks; -if ( $card_count && $check_count ) { - $title_types = 'Card and Check'; -} elsif ( $card_count ) { - $title_types = 'Card'; -} elsif ( $check_count ) { - $title_types = 'Check'; -} - -my $report_title = sprintf( - 'Upcoming Auto Bill %s Transactions', - $title_types, -); +my $report_title = FS::cust_payby->future_autobill_report_title; -- cgit v1.2.1 From dc04294e8be2526714fb652fed479d379e444a32 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Tue, 11 Sep 2018 06:06:33 -0400 Subject: RT# 78547 Future autobill report - dynamic navigation --- httemplate/elements/menu.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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')) { -- cgit v1.2.1 From 30fabfe8ce8c9a48fa96c6a50d48b32161b1680f Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Tue, 11 Sep 2018 11:27:42 -0400 Subject: RT# 39340 - configured the minimal selfservice to get mac address from radius account table --- FS/FS/ClientAPI/MyAccount.pm | 30 ++++++++++++++++++++++++++++-- FS/FS/ClientAPI_XMLRPC.pm | 1 + min_selfservice/index.php | 2 +- min_selfservice/login.php | 8 +++----- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 263b3116b..57d42982d 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -184,6 +184,29 @@ sub skin_info { } +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; @@ -239,8 +262,11 @@ sub login { } 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; diff --git a/FS/FS/ClientAPI_XMLRPC.pm b/FS/FS/ClientAPI_XMLRPC.pm index dcf34fdaa..db0537c02 100644 --- a/FS/FS/ClientAPI_XMLRPC.pm +++ b/FS/FS/ClientAPI_XMLRPC.pm @@ -227,6 +227,7 @@ sub ss2clientapi { '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', }; diff --git a/min_selfservice/index.php b/min_selfservice/index.php index c7e20c503..25ec33472 100644 --- a/min_selfservice/index.php +++ b/min_selfservice/index.php @@ -10,7 +10,7 @@
- Sorry we were unable to locate your account with ip . + Sorry we were unable to locate your account with MAC address .
diff --git a/min_selfservice/login.php b/min_selfservice/login.php index 91e19cd7f..b4e2b2651 100644 --- a/min_selfservice/login.php +++ b/min_selfservice/login.php @@ -4,16 +4,14 @@ require('freeside.class.php'); $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 ) { -- cgit v1.2.1 From c7ca3214418c5ccf928d385b9f8ca08ceab87790 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Wed, 12 Sep 2018 18:56:59 -0400 Subject: RT# 80624 FS::access_user methods for access_user_pref relation --- FS/FS/access_user.pm | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm index a9fdf5b1e..f23aa77f9 100644 --- a/FS/FS/access_user.pm +++ b/FS/FS/access_user.pm @@ -12,6 +12,7 @@ use FS::Record qw( qsearch qsearchs dbh ); use FS::agent; use FS::cust_main; use FS::sales; +use Carp qw( croak ); $DEBUG = 0; $me = '[FS::access_user]'; @@ -814,6 +815,103 @@ sub set_page_pref { return $error; } +=item get_pref NAME + +Fetch the prefvalue column from L 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 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 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 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 -- cgit v1.2.1 From fe293dc0ca4ea3939de9c2ad28d3aaf7c8980644 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Wed, 12 Sep 2018 23:32:52 -0400 Subject: RT# 80624 Detect and warn for bad MS Edge version --- httemplate/elements/header.html | 1 + .../misc/edge_browser_check-fail_notice.html | 25 ++++++++++++++++ httemplate/misc/edge_browser_check-header.html | 32 ++++++++++++++++++++ httemplate/misc/edge_browser_check-iframe.html | 35 ++++++++++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 httemplate/misc/edge_browser_check-fail_notice.html create mode 100644 httemplate/misc/edge_browser_check-header.html create mode 100644 httemplate/misc/edge_browser_check-iframe.html 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/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" &> + +
+
+ ⚠ +
+

+ Edge Browser Bug +

+

+ Your copy of Microsoft Edge has a data corrupting bug. +

+

+ Microsoft fixed this bug with the July RS4 Windows 10 Update. + Please update your copy of Windows. +

+

+ Alternatively, you may choose to use + Mozilla Firefox + or Google Chrome. They + are not affected by this bug. +

+
+ +<& /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..e7666aec4 --- /dev/null +++ b/httemplate/misc/edge_browser_check-header.html @@ -0,0 +1,32 @@ +% if ( $force_redirect ) { + +% } elsif ( $do_check ) { + + +% } +<%init> +my $curuser = $FS::CurrentUser::CurrentUser; +my $cgi = FS::UID::cgi(); +my $DEBUG = 0; + +my $do_check = 0; +$do_check = 1 + if $curuser + && !$cgi->param('edge_browser_check') + && $curuser->get_pref('edge_bug_vulnerable') ne 'N'; + +my $force_redirect = $curuser->get_pref('edge_bug_vulnerable') eq 'Y' ? 1 : 0; + \ No newline at end of file diff --git a/httemplate/misc/edge_browser_check-iframe.html b/httemplate/misc/edge_browser_check-iframe.html new file mode 100644 index 000000000..e804fc676 --- /dev/null +++ b/httemplate/misc/edge_browser_check-iframe.html @@ -0,0 +1,35 @@ +
+ + + +
+ + + + +<%init> +my $cgi = FS::UID::cgi(); +my $curuser = $FS::CurrentUser::CurrentUser; + +if ( $curuser ) { + my $canary = $cgi->param('edge_browser_canary'); + $curuser->set_pref( + 'edge_bug_vulnerable', + + $canary eq 'test' ? 'Y' : 'Y', + + # Don't test this user's session for the next 10m + time() + 600, + ); +} + + \ No newline at end of file -- cgit v1.2.1 From 5f3b423601b5ddd7605e1eaf4bd838c0fd6f5fcf Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Wed, 12 Sep 2018 23:48:35 -0400 Subject: RT# 80624 Remove notice from login screen --- httemplate/loginout/login.html | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/httemplate/loginout/login.html b/httemplate/loginout/login.html index 138c6adc1..b787a3eea 100644 --- a/httemplate/loginout/login.html +++ b/httemplate/loginout/login.html @@ -34,38 +34,8 @@ - - - - <%init> -- cgit v1.2.1 From 0682747829a56d487155e28675c133cb90f991de Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Thu, 13 Sep 2018 00:44:54 -0400 Subject: RT# 80624 Browser check repeats if session changes --- httemplate/misc/edge_browser_check-header.html | 10 +++++++--- httemplate/misc/edge_browser_check-iframe.html | 7 +++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/httemplate/misc/edge_browser_check-header.html b/httemplate/misc/edge_browser_check-header.html index e7666aec4..a88962be9 100644 --- a/httemplate/misc/edge_browser_check-header.html +++ b/httemplate/misc/edge_browser_check-header.html @@ -18,7 +18,10 @@ % } <%init> -my $curuser = $FS::CurrentUser::CurrentUser; +my $curuser = $FS::CurrentUser::CurrentUser; +my $session = $FS::CurrentUser::CurrentSession; +my $sessionkey = $session->sessionkey if $session; + my $cgi = FS::UID::cgi(); my $DEBUG = 0; @@ -26,7 +29,8 @@ my $do_check = 0; $do_check = 1 if $curuser && !$cgi->param('edge_browser_check') - && $curuser->get_pref('edge_bug_vulnerable') ne 'N'; + && $sessionkey + && $curuser->get_pref('edge_bug_vulnerable') ne $sessionkey; my $force_redirect = $curuser->get_pref('edge_bug_vulnerable') eq 'Y' ? 1 : 0; - \ No newline at end of file + diff --git a/httemplate/misc/edge_browser_check-iframe.html b/httemplate/misc/edge_browser_check-iframe.html index e804fc676..61ae9a0bd 100644 --- a/httemplate/misc/edge_browser_check-iframe.html +++ b/httemplate/misc/edge_browser_check-iframe.html @@ -19,16 +19,15 @@ <%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' ? 'Y' : 'Y', - - # Don't test this user's session for the next 10m - time() + 600, + $canary eq 'test' ? $sessionkey : 'Y', ); } -- cgit v1.2.1