From bce697fa3f64f1cc7f568fde28a912605cac7eb9 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Mon, 4 Jun 2018 20:10:15 -0400 Subject: RT# 77917 - Updated event option Agent to allow for selection of multiple agents. Conflicts: FS/FS/Upgrade.pm --- FS/FS/Upgrade.pm | 4 ++++ FS/FS/part_event/Condition/agent.pm | 9 ++++----- FS/FS/part_event_condition_option.pm | 24 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/FS/FS/Upgrade.pm b/FS/FS/Upgrade.pm index e729896b3..f26c6a338 100644 --- a/FS/FS/Upgrade.pm +++ b/FS/FS/Upgrade.pm @@ -454,6 +454,10 @@ sub upgrade_data { #mark certain taxes as system-maintained, # and fix whitespace 'cust_main_county' => [], + + #upgrade part_event_condition_option agentnum to a multiple hash value + 'part_event_condition_option' =>[], + ; \%hash; diff --git a/FS/FS/part_event/Condition/agent.pm b/FS/FS/part_event/Condition/agent.pm index bdd4e12de..917cf468b 100644 --- a/FS/FS/part_event/Condition/agent.pm +++ b/FS/FS/part_event/Condition/agent.pm @@ -13,7 +13,7 @@ sub description { sub option_fields { ( - 'agentnum' => { label=>'Agent', type=>'select-agent', }, + 'agentnum' => { label=>'Agent', type=>'select-agent', multiple => '1' }, ); } @@ -22,16 +22,15 @@ sub condition { my $cust_main = $self->cust_main($object); - my $agentnum = $self->option('agentnum'); - - $cust_main->agentnum == $agentnum; + my $hashref = $self->option('agentnum') || {}; + grep $hashref->{ $_->agentnum }, $cust_main->agent; } sub condition_sql { my( $class, $table, %opt ) = @_; - "cust_main.agentnum = " . $class->condition_sql_option_integer('agentnum', $opt{'driver_name'}); + "cust_main.agentnum IN " . $class->condition_sql_option_option_integer('agentnum', $opt{'driver_name'}); } 1; diff --git a/FS/FS/part_event_condition_option.pm b/FS/FS/part_event_condition_option.pm index 3256dc0bd..15a6a5553 100644 --- a/FS/FS/part_event_condition_option.pm +++ b/FS/FS/part_event_condition_option.pm @@ -138,6 +138,30 @@ sub optionvalue { } } +use FS::upgrade_journal; +sub _upgrade_data { #class method + my ($class, %opts) = @_; + + # migrate part_event_condition_option agentnum to part_event_condition_option_option agentnum + unless ( FS::upgrade_journal->is_done('agentnum_to_hash') ) { + + foreach my $condition_option (qsearch('part_event_condition_option', { optionname => 'agentnum', })) { + my $optionvalue = $condition_option->get("optionvalue"); + if ($optionvalue eq 'HASH' ) { next; } + else { + my $options = {"$optionvalue" => '1',}; + $condition_option->optionvalue(ref($options)); + my $error = $condition_option->replace($options); + die $error if $error; + } + } + + FS::upgrade_journal->set_done('agentnum_to_hash'); + + } + +} + =back =head1 SEE ALSO -- cgit v1.2.1 From 956f5fc611a223847baf628e596511af1d0e2b0e Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Tue, 5 Jun 2018 08:50:17 -0400 Subject: RT# 77917 - fixed upgrade to work when all agents were selected. --- FS/FS/part_event_condition_option.pm | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/FS/FS/part_event_condition_option.pm b/FS/FS/part_event_condition_option.pm index 15a6a5553..f1d1b6a15 100644 --- a/FS/FS/part_event_condition_option.pm +++ b/FS/FS/part_event_condition_option.pm @@ -146,14 +146,23 @@ sub _upgrade_data { #class method unless ( FS::upgrade_journal->is_done('agentnum_to_hash') ) { foreach my $condition_option (qsearch('part_event_condition_option', { optionname => 'agentnum', })) { + my %options; my $optionvalue = $condition_option->get("optionvalue"); if ($optionvalue eq 'HASH' ) { next; } + elsif ($optionvalue eq '') { + foreach my $agent (qsearch('agent', {})) { + $options{$agent->agentnum} = '1'; + } + + } else { - my $options = {"$optionvalue" => '1',}; - $condition_option->optionvalue(ref($options)); - my $error = $condition_option->replace($options); - die $error if $error; + $options{$optionvalue} = '1'; } + + $condition_option->optionvalue(ref(\%options)); + my $error = $condition_option->replace(\%options); + die $error if $error; + } FS::upgrade_journal->set_done('agentnum_to_hash'); -- cgit v1.2.1 From a56ea36329a7d1bced2200ed0976e9147cf6e25c Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Wed, 13 Jun 2018 02:35:35 +0000 Subject: RT# 32233 Mask ssn and stateid in selfservice --- FS/FS/ClientAPI/MyAccount.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 476ef0789..f32523e35 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -600,6 +600,8 @@ sub customer_info_short { for (@cust_main_editable_fields) { $return{$_} = $cust_main->get($_); } + $return{$_} = $cust_main->masked($_) for qw/ss stateid/; + #maybe a little more expensive, but it should be cached by now for (@location_editable_fields) { $return{$_} = $cust_main->bill_location->get($_); @@ -3880,4 +3882,3 @@ sub _custoragent_session_custnum { } 1; - -- cgit v1.2.1 From 3d48c56539401f62cc46173479bb29879ec6d019 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Thu, 14 Jun 2018 14:46:38 -0500 Subject: RT# 80543 Crash creating new quotation --- FS/FS/Template_Mixin.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index cefb4bcc3..5cc2f05a9 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -3099,7 +3099,9 @@ sub _items_fee { my @cust_bill_pkg = grep { $_->feepart } $self->cust_bill_pkg; my $escape_function = $options{escape_function}; - my $locale = $self->cust_main->locale; + my $locale = $self->quotationnum + ? $self->prospect_main->locale + : $self->cust_main->locale; my @items; foreach my $cust_bill_pkg (@cust_bill_pkg) { -- cgit v1.2.1 From 20bfaf53d36f64261b9c6fe7a548b64ad40f5868 Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Tue, 31 Jan 2017 19:17:13 -0800 Subject: further optimize condition_sql for "Invoice eligible for automatic collection" condition, RT#74451 --- .../part_event/Condition/cust_bill_hasnt_noauto.pm | 38 +++++++--------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/FS/FS/part_event/Condition/cust_bill_hasnt_noauto.pm b/FS/FS/part_event/Condition/cust_bill_hasnt_noauto.pm index 027625569..d782c12c1 100644 --- a/FS/FS/part_event/Condition/cust_bill_hasnt_noauto.pm +++ b/FS/FS/part_event/Condition/cust_bill_hasnt_noauto.pm @@ -26,32 +26,18 @@ sub condition { sub condition_sql { my( $class, $table, %opt ) = @_; - # XXX: can be made faster with optimizations? - # -remove some/all sub-selects? - # -remove the two main separate selects? - - "0 = (select count(1) from cust_pkg - where cust_pkg.no_auto = 'Y' and cust_pkg.pkgnum in - (select distinct cust_bill_pkg.pkgnum - from cust_bill_pkg, cust_pkg - where cust_bill_pkg.pkgnum = cust_pkg.pkgnum - and cust_bill_pkg.invnum = cust_bill.invnum - and cust_bill_pkg.pkgnum > 0 - ) - ) - AND - 0 = (select count(1) from part_pkg - where part_pkg.no_auto = 'Y' and part_pkg.pkgpart in - (select cust_pkg.pkgpart from cust_pkg - where pkgnum in - (select distinct cust_bill_pkg.pkgnum - from cust_bill_pkg, cust_pkg - where cust_bill_pkg.pkgnum = cust_pkg.pkgnum - and cust_bill_pkg.invnum = cust_bill.invnum - and cust_bill_pkg.pkgnum > 0 - ) - ) - ) + # can be made still faster with optimizations? + + "NOT EXISTS ( SELECT 1 FROM cust_pkg + LEFT JOIN part_pkg USING (pkgpart) + WHERE ( cust_pkg.no_auto = 'Y' OR part_pkg.no_auto = 'Y' ) + AND cust_pkg.pkgnum IN + ( SELECT DISTINCT cust_bill_pkg.pkgnum + FROM cust_bill_pkg + WHERE cust_bill_pkg.invnum = cust_bill.invnum + AND cust_bill_pkg.pkgnum > 0 + ) + ) "; } -- cgit v1.2.1 From d65053a0357531b623d0ff715d3426301f242b2c Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Mon, 13 Nov 2017 12:45:15 -0500 Subject: RT# 77470 - added reason option to advanced package report with suspended status is selected. Also added links to suspend and unsuspend when status active or suspended is used. Conflicts: FS/FS/cust_pkg/Search.pm httemplate/elements/tr-select-reason.html httemplate/elements/tr-td-label.html --- FS/FS/cust_pkg.pm | 6 ++ httemplate/elements/header-popup.html | 1 + httemplate/elements/popup-topreload.html | 17 ++++ httemplate/elements/topreload.js | 6 ++ httemplate/elements/tr-select-reason.html | 35 +++++--- httemplate/elements/tr-td-label.html | 12 ++- httemplate/misc/bulk_suspend_pkg.cgi | 94 ++++++++++++++++++++++ httemplate/misc/bulk_unsuspend_pkg.cgi | 66 +++++++++++++++ httemplate/misc/process/bulk_suspend_pkg.cgi | 106 +++++++++++++++++++++++++ httemplate/misc/process/bulk_unsuspend_pkg.cgi | 91 +++++++++++++++++++++ httemplate/search/cust_pkg.cgi | 18 ++++- httemplate/search/report_cust_pkg.html | 19 +++++ 12 files changed, 460 insertions(+), 11 deletions(-) create mode 100644 httemplate/elements/popup-topreload.html create mode 100644 httemplate/elements/topreload.js create mode 100644 httemplate/misc/bulk_suspend_pkg.cgi create mode 100644 httemplate/misc/bulk_unsuspend_pkg.cgi create mode 100644 httemplate/misc/process/bulk_suspend_pkg.cgi create mode 100644 httemplate/misc/process/bulk_unsuspend_pkg.cgi diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index fa60afb45..3b746fc2d 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -5893,6 +5893,8 @@ sub search { push @where, $FS::CurrentUser::CurrentUser->agentnums_sql('table'=>'cust_main'); } + push @where, "cust_pkg_reason.reasonnum = '".$params->{reasonnum}."'" if $params->{reasonnum}; + my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : ''; my $addl_from = 'LEFT JOIN part_pkg USING ( pkgpart ) '. @@ -5900,6 +5902,10 @@ sub search { 'LEFT JOIN cust_location USING ( locationnum ) '. FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg'); + if ($params->{reasonnum}) { + $addl_from .= 'LEFT JOIN cust_pkg_reason ON (cust_pkg_reason.pkgnum = cust_pkg.pkgnum) '; + } + my $select; my $count_query; if ( $params->{'select_zip5'} ) { diff --git a/httemplate/elements/header-popup.html b/httemplate/elements/header-popup.html index 906b1ee51..37402014b 100644 --- a/httemplate/elements/header-popup.html +++ b/httemplate/elements/header-popup.html @@ -34,6 +34,7 @@ Example: % } % } + <% $head |n %> > diff --git a/httemplate/elements/popup-topreload.html b/httemplate/elements/popup-topreload.html new file mode 100644 index 000000000..7a166f6de --- /dev/null +++ b/httemplate/elements/popup-topreload.html @@ -0,0 +1,17 @@ +<%doc> + +Example: + + <& /elements/popup-topreload, mt('Action completed') &> + + +<& /elements/header-popup.html, encode_entities($message) &> + +<& /elements/footer-popup.html &> +<%init> + +my $message = shift; + + \ No newline at end of file diff --git a/httemplate/elements/topreload.js b/httemplate/elements/topreload.js new file mode 100644 index 000000000..84faee05c --- /dev/null +++ b/httemplate/elements/topreload.js @@ -0,0 +1,6 @@ + window.topreload = function() { + if (window != window.top) { + window.top.location.reload(); + } + } + \ No newline at end of file diff --git a/httemplate/elements/tr-select-reason.html b/httemplate/elements/tr-select-reason.html index 3b9bb2299..2fe676b1f 100755 --- a/httemplate/elements/tr-select-reason.html +++ b/httemplate/elements/tr-select-reason.html @@ -5,17 +5,22 @@ Example: include( '/elements/tr-select-reason.html', #required - 'field' => 'reasonnum', - 'reason_class' => 'C', # currently 'C', 'R', 'F', 'S' or 'X' - # for cancel, credit, refund, suspend or void credit + 'field' => 'reasonnum', # field name + 'reason_class' => 'C', # one of those in %FS::reason_type::class_name + 'label' => 'Your Label', # field display label #recommended 'cgi' => $cgi, #easiest way for things to be properly "sticky" on errors #optional - 'control_button' => 'element_name', #button to be enabled when a reason is - #selected + 'control_button' => 'element_name', #button to be enabled when a reason is + #selected 'id' => 'element_id', + 'hide_add' => '1', # setting this will hide the add new reason link, + # even if the user has access to add a new reason. + 'hide_onload' => '1', # setting this will hide reason select box on page load, + # allowing for it do be displayed later. + 'pre_options' => [ 0 => 'all'], # an array of pre options. Defaults to 0 => 'select reason...' #deprecated ways to keep things "sticky" on errors # (requires duplicate code in each using file to parse cgi params) @@ -68,24 +73,28 @@ Example: %# sadly can't just use add_inline here, as we have non-text fields + <& tr-select-table.html, - 'label' => 'Reason', + 'label' => $label, 'field' => $name, 'id' => $id, 'table' => 'reason', 'records' => \@reasons, + 'label_callback' => sub { my $reason = shift; + $reason->type . ' : ' . $reason->reason }, 'name_col' => 'label', 'disable_empty' => 1, - 'pre_options' => [ 0 => 'Select reason...' ], + 'pre_options' => \@pre_options, 'post_options' => \@post_options, 'curr_value' => $init_reason, 'onchange' => $id.'_changed()', + 'hide_onload' => $opt{'hide_onload'}, &> % # "add new reason" fields % # should be a
, but that doesn't fit well into the table -% if ( $curuser->access_right($add_access_right) ) { +% if ( $curuser->access_right($add_access_right) && !$hide_addnew ) { @@ -184,6 +193,8 @@ my %opt = @_; my $name = $opt{'field'}; my $class = $opt{'reason_class'}; +my $label = $opt{'label'} ? $opt{'label'} : 'Reason'; +my $hide_addnew = $opt{'hide_addnew'} ? $opt{'hide_addnew'} : ''; my $init_reason; if ( $opt{'cgi'} ) { @@ -195,6 +206,8 @@ if ( $opt{'cgi'} ) { my $id = $opt{'id'} || $name; $id =~ s/\./_/g; # for edit/part_event +my $label_id = $opt{'label_id'} || ''; + my $add_access_right; if ($class eq 'C') { $add_access_right = 'Add on-the-fly cancel reason'; @@ -222,10 +235,14 @@ my @reasons = qsearch({ ' ON (reason.reason_type = reason_type.typenum)', 'hashref' => { disabled => '' }, 'extra_sql' => " AND reason_type.class = '$class'", + 'order_by' => ' ORDER BY type, reason', }); +my @pre_options = ( 0 => 'Select reason...' ); +@pre_options = @{ $opt{'pre_options'} if $opt{'pre_options'} }; + my @post_options; -if ( $curuser->access_right($add_access_right) ) { +if ( $curuser->access_right($add_access_right) && !$hide_addnew ) { @post_options = ( -1 => 'Add new reason' ); } diff --git a/httemplate/elements/tr-td-label.html b/httemplate/elements/tr-td-label.html index 8125541c7..542f4559c 100644 --- a/httemplate/elements/tr-td-label.html +++ b/httemplate/elements/tr-td-label.html @@ -1,4 +1,12 @@ - +<%doc> + +Actually + +Note that this puts the 'label' argument into the document verbatim, with no +escaping or localization. + + +>
$label
* ' : ''; diff --git a/httemplate/misc/bulk_suspend_pkg.cgi b/httemplate/misc/bulk_suspend_pkg.cgi new file mode 100644 index 000000000..e41ea2b1a --- /dev/null +++ b/httemplate/misc/bulk_suspend_pkg.cgi @@ -0,0 +1,94 @@ +<% include('/elements/header-popup.html', "Suspend Packages") %> + +% if ( $cgi->param('error') ) { + Error: <% $cgi->param('error') %> +

+% } + +
+ +%# some false laziness w/search/cust_pkg.cgi + + +% for my $param ( +% qw( +% agentnum cust_status cust_main_salesnum salesnum custnum magic status +% custom pkgbatch zip reasonnum +% 477part 477rownum date +% report_option +% ), +% grep { /^location_\w+$/ || /^report_option_any/ } $cgi->param +% ) { + +% } +% +% for my $param (qw( censustract censustract2 ) ) { +% next unless grep { $_ eq $param } $cgi->param; + +% } +% +% for my $param (qw( pkgpart classnum refnum towernum )) { +% foreach my $value ($cgi->param($param)) { + +% } +% } +% +% foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel active )) { +% + "> + "> + "> + "> + "> +% } + +<% ntable('#cccccc') %> + +% my $date_init = 0; + <& /elements/tr-input-date-field.html, { + 'name' => 'suspend_date', + 'value' => $date, + 'label' => mt("Suspend package on"), + 'format' => $date_format, + } &> +% $date_init = 1; + + <& /elements/tr-select-reason.html, + field => 'suspend_reasonnum', + reason_class => 'S', + &> + +% if ( $FS::CurrentUser::CurrentUser->access_right('Unsuspend customer package')) { + + <& /elements/tr-input-date-field.html, { + 'name' => 'suspend_resume_date', + 'value' => '', + 'label' => mt('Unsuspend on'), + 'format' => $date_format, + 'noinit' => $date_init, + } &> +% } + +
+ +
+ + + + + + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Bulk change customer packages'); + +#use Date::Parse qw(str2time); +# + +my $conf = new FS::Conf; +my $date_format = $conf->config('date_format') || '%m/%d/%Y'; + +my $date = time; + + \ No newline at end of file diff --git a/httemplate/misc/bulk_unsuspend_pkg.cgi b/httemplate/misc/bulk_unsuspend_pkg.cgi new file mode 100644 index 000000000..8fbc41841 --- /dev/null +++ b/httemplate/misc/bulk_unsuspend_pkg.cgi @@ -0,0 +1,66 @@ +<% include('/elements/header-popup.html', "Unsuspend Packages") %> + +% if ( $cgi->param('error') ) { + Error: <% $cgi->param('error') %> +

+% } + + + +%# some false laziness w/search/cust_pkg.cgi + + +% for my $param ( +% qw( +% agentnum cust_status cust_main_salesnum salesnum custnum magic status +% custom pkgbatch zip reasonnum +% 477part 477rownum date +% report_option +% ), +% grep { /^location_\w+$/ || /^report_option_any/ } $cgi->param +% ) { + +% } +% +% for my $param (qw( censustract censustract2 ) ) { +% next unless grep { $_ eq $param } $cgi->param; + +% } +% +% for my $param (qw( pkgpart classnum refnum towernum )) { +% foreach my $value ($cgi->param($param)) { + +% } +% } +% +% foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel active )) { +% + "> + "> + "> + "> + "> +% } + +<% ntable('#cccccc') %> + + + + + + +
Confirm Unsuspend Packages
+ +
+ + + + + + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Bulk change customer packages'); + + diff --git a/httemplate/misc/process/bulk_suspend_pkg.cgi b/httemplate/misc/process/bulk_suspend_pkg.cgi new file mode 100644 index 000000000..2ac9c212f --- /dev/null +++ b/httemplate/misc/process/bulk_suspend_pkg.cgi @@ -0,0 +1,106 @@ +% if ($error) { +<% $cgi->redirect(popurl(2)."bulk_suspend_pkg.cgi?".$cgi->query_string ) %> +% } +<% include('/elements/popup-topreload.html', "Packages Suspended") %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Bulk change customer packages'); + +my $error; + +if (!$error) { + + my %search_hash = (); + + $search_hash{'query'} = $cgi->param('query'); + + #scalars + for (qw( agentnum cust_status cust_main_salesnum salesnum custnum magic status + custom cust_fields pkgbatch zip reasonnum + 477part 477rownum date + )) + { + $search_hash{$_} = $cgi->param($_) if length($cgi->param($_)); + } + + #arrays + for my $param (qw( pkgpart classnum refnum towernum )) { + $search_hash{$param} = [ $cgi->param($param) ] + if grep { $_ eq $param } $cgi->param; + } + + #scalars that need to be passed if empty + for my $param (qw( censustract censustract2 )) { + $search_hash{$param} = $cgi->param($param) || '' + if grep { $_ eq $param } $cgi->param; + } + + #location flags (checkboxes) + my @loc = grep /^\w+$/, $cgi->param('loc'); + $search_hash{"location_$_"} = 1 foreach @loc; + + my $report_option = $cgi->param('report_option'); + $search_hash{report_option} = $report_option if $report_option; + + for my $param (grep /^report_option_any/, $cgi->param) { + $search_hash{$param} = $cgi->param($param); + } + + ### + # parse dates + ### + + #false laziness w/report_cust_pkg.html and bulk_pkg_increment_bill.cgi + my %disable = ( + 'all' => {}, + 'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, }, + 'active' => { 'susp'=>1, 'cancel'=>1 }, + 'suspended' => { 'cancel' => 1 }, + 'cancelled' => {}, + '' => {}, + ); + + foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel active )) { + + $search_hash{$field.'_null'} = scalar( $cgi->param($field.'_null') ); + + my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field); + + next if $beginning == 0 && $ending == 4294967295 + or $disable{$cgi->param('status')}->{$field}; + + $search_hash{$field} = [ $beginning, $ending ]; + + } + + my $sql_query = FS::cust_pkg->search(\%search_hash); + $sql_query->{'select'} = 'cust_pkg.pkgnum'; + + ## set suspend info + $cgi->param('suspend_reasonnum') =~ /^(\d+)$/ or die "Illegal Reason"; + my $suspend_reasonnum = $1; + + my $suspend_date = time; + parse_datetime($cgi->param('suspend_date')) =~ /^(\d+)$/ or die "Illegal date"; + $suspend_date = $1; + + my $suspend_resume_date = ''; + (parse_datetime($cgi->param('suspend_resume_date')) =~ /^(\d+)$/ or die "Illegal resume date") if $cgi->param('suspend_resume_date'); + $suspend_resume_date = $1; + + foreach my $pkgnum (map { $_->pkgnum } qsearch($sql_query)) { + my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); + + $error = $cust_pkg->suspend('reason' => $suspend_reasonnum, + 'date' => $suspend_date, + 'resume_date' => $suspend_resume_date, + ); + } + +} + +$cgi->param("error", substr($error, 0, 512)); # arbitrary length believed + # suited for all supported + # browsers + \ No newline at end of file diff --git a/httemplate/misc/process/bulk_unsuspend_pkg.cgi b/httemplate/misc/process/bulk_unsuspend_pkg.cgi new file mode 100644 index 000000000..13389f43a --- /dev/null +++ b/httemplate/misc/process/bulk_unsuspend_pkg.cgi @@ -0,0 +1,91 @@ +% if ($error) { +<% $cgi->redirect(popurl(2)."bulk_unsuspend_pkg.cgi?".$cgi->query_string ) %> +% } +<% include('/elements/popup-topreload.html', "Packages Unsuspended") %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Bulk change customer packages'); + +my $error; +$error = 'Unsuspend packages not confirmed' if !$cgi->param('confirm'); + +if (!$error) { + + my %search_hash = (); + + $search_hash{'query'} = $cgi->param('query'); + + #scalars + for (qw( agentnum cust_status cust_main_salesnum salesnum custnum magic status + custom cust_fields pkgbatch zip reasonnum + 477part 477rownum date + )) + { + $search_hash{$_} = $cgi->param($_) if length($cgi->param($_)); + } + + #arrays + for my $param (qw( pkgpart classnum refnum towernum )) { + $search_hash{$param} = [ $cgi->param($param) ] + if grep { $_ eq $param } $cgi->param; + } + + #scalars that need to be passed if empty + for my $param (qw( censustract censustract2 )) { + $search_hash{$param} = $cgi->param($param) || '' + if grep { $_ eq $param } $cgi->param; + } + + #location flags (checkboxes) + my @loc = grep /^\w+$/, $cgi->param('loc'); + $search_hash{"location_$_"} = 1 foreach @loc; + + my $report_option = $cgi->param('report_option'); + $search_hash{report_option} = $report_option if $report_option; + + for my $param (grep /^report_option_any/, $cgi->param) { + $search_hash{$param} = $cgi->param($param); + } + + ### + # parse dates + ### + + #false laziness w/report_cust_pkg.html and bulk_pkg_increment_bill.cgi + my %disable = ( + 'all' => {}, + 'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, }, + 'active' => { 'susp'=>1, 'cancel'=>1 }, + 'suspended' => { 'cancel' => 1 }, + 'cancelled' => {}, + '' => {}, + ); + + foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel active )) { + + $search_hash{$field.'_null'} = scalar( $cgi->param($field.'_null') ); + + my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field); + + next if $beginning == 0 && $ending == 4294967295 + or $disable{$cgi->param('status')}->{$field}; + + $search_hash{$field} = [ $beginning, $ending ]; + + } + + my $sql_query = FS::cust_pkg->search(\%search_hash); + $sql_query->{'select'} = 'cust_pkg.pkgnum'; + + foreach my $pkgnum (map { $_->pkgnum } qsearch($sql_query)) { + my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); + $error = $cust_pkg->unsuspend; + } + +} + +$cgi->param("error", substr($error, 0, 512)); # arbitrary length believed + # suited for all supported + # browsers + \ No newline at end of file diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi index 2459c44b2..3eb0332d2 100755 --- a/httemplate/search/cust_pkg.cgi +++ b/httemplate/search/cust_pkg.cgi @@ -159,7 +159,7 @@ $search_hash{'query'} = $cgi->keywords; #scalars for (qw( agentnum cust_status cust_main_salesnum salesnum custnum magic status - custom cust_fields pkgbatch zip + reasonnum custom cust_fields pkgbatch zip 477part 477rownum date )) { @@ -270,6 +270,22 @@ my $html_init = sub { 'height' => 210, ). '
'; + $text .= include( '/elements/popup_link.html', + 'label' => emt('Suspend these packages'), + 'action' => "${p}misc/bulk_suspend_pkg.cgi?$query", + 'actionlabel' => emt('Suspend Packages'), + 'width' => 569, + 'height' => 210, + ). '
' if $search_hash{status} eq 'active'; + + $text .= include( '/elements/popup_link.html', + 'label' => emt('Unsuspend these packages'), + 'action' => "${p}misc/bulk_unsuspend_pkg.cgi?$query", + 'actionlabel' => emt('Unsuspend Packages'), + 'width' => 569, + 'height' => 210, + ). '
' if $search_hash{status} eq 'suspended'; + if ( $curuser->access_right('Edit customer package dates') ) { $text .= include( '/elements/popup_link.html', 'label' => emt('Increment next bill date'), diff --git a/httemplate/search/report_cust_pkg.html b/httemplate/search/report_cust_pkg.html index ed5af2481..8c910e603 100755 --- a/httemplate/search/report_cust_pkg.html +++ b/httemplate/search/report_cust_pkg.html @@ -67,10 +67,29 @@ 'onchange' => 'status_changed(this);', &> + <& /elements/tr-select-reason.html, + 'field' => 'reasonnum', + 'reason_class' => 'S', + 'label' => 'Suspended Reason', + 'label_id' => 'reasonnum_label', + 'hide_addnew' => '1', + 'hide_onload' => '1', + 'cgi' => $cgi, + 'control_button' => 'confirm_suspend_cust_button', + 'pre_options' => [ 0 => 'all' ], + &> + % } + + <& /elements/footer.html &> <%init> @@ -219,12 +255,16 @@ my %opt = @_; $opt{'acl'} ||= 'Bulk send customer notices'; +my $email_to; + die "access denied" unless $FS::CurrentUser::CurrentUser->access_right($opt{'acl'}); my $conf = FS::Conf->new; my @no_search_fields = qw( action table from subject html_body text_body popup url ); +my $send_to_domain = $conf->config('send-to-domain'); + my $form_action = $opt{'form_action'} || 'email-customers.html'; my $process_url = $opt{'process_url'} || 'process/email-customers.html'; my $title = $opt{'title'} || 'Send customer notices'; @@ -310,6 +350,11 @@ if ( $cgi->param('action') eq 'preview' ) { # contact_class_X params in preview foreach my $param ( $cgi->param ) { + if ($cgi->param('emailtovoice_contact')) { + $email_to = $cgi->param('emailtovoice_contact') . '@' . $send_to_domain; + push @contact_classnum, 'emailtovoice'; + push @contact_classname, $email_to; + } if ( $param =~ /^contact_class_(\w+)$/ ) { push @contact_classnum, $1; if ( $1 eq 'invoice' ) { @@ -327,10 +372,12 @@ if ( $cgi->param('action') eq 'preview' ) { my @contact_checkboxes = ( [ 'invoice' => { label => 'Invoice recipients' } ] ); + foreach my $class (qsearch('contact_class', { disabled => '' })) { push @contact_checkboxes, [ $class->classnum, { label => $class->classname } ]; } + -- cgit v1.2.1 From 1050eff6d30a010c88bbd2e4aa28f1898b059ee3 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Tue, 22 Aug 2017 10:36:04 -0400 Subject: RT# 73964 - Changed global config send-to-domain to email-to-voice_domain --- FS/FS/Conf.pm | 2 +- FS/FS/cust_main_Mixin.pm | 2 +- FS/FS/part_event/Action/notice_to_emailtovoice.pm | 6 +++--- httemplate/misc/email-customers.html | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 31a5ec89d..1e9a5f48d 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1012,7 +1012,7 @@ my $validate_email = sub { $_[0] =~ }, { - 'key' => 'send-to-domain', + 'key' => 'email-to-voice_domain', 'section' => 'email_to_voice_services', 'description' => 'The Domain to send email to voice to', 'type' => 'text', diff --git a/FS/FS/cust_main_Mixin.pm b/FS/FS/cust_main_Mixin.pm index acce28c9c..aa0395a4a 100644 --- a/FS/FS/cust_main_Mixin.pm +++ b/FS/FS/cust_main_Mixin.pm @@ -406,7 +406,7 @@ sub email_search_result { my($class, $param) = @_; my $conf = FS::Conf->new; - my $send_to_domain = $conf->config('send-to-domain'); + my $send_to_domain = $conf->config('email-to-voice_domain'); my $msgnum = $param->{msgnum}; my $from = delete $param->{from}; diff --git a/FS/FS/part_event/Action/notice_to_emailtovoice.pm b/FS/FS/part_event/Action/notice_to_emailtovoice.pm index ae766e81b..a3a5e9824 100644 --- a/FS/FS/part_event/Action/notice_to_emailtovoice.pm +++ b/FS/FS/part_event/Action/notice_to_emailtovoice.pm @@ -23,7 +23,7 @@ sub eventtable_hashref { sub option_fields { #my $conf = new FS::Conf; - #my $to_domain = $conf->config('send-to-domain'); + #my $to_domain = $conf->config('email-to-voice_domain'); ( 'to_name' => { 'label' => 'Address To', @@ -33,7 +33,7 @@ sub option_fields { 'fax' => 'Fax #', 'daytime' => 'Day Time #', }, - 'post_field_label' => "@" , #. $to_domain , + 'post_field_label' => "@", # . $to_domain , }, 'msgnum' => { 'label' => 'Template', @@ -53,7 +53,7 @@ sub do_action { my( $self, $object ) = @_; my $conf = new FS::Conf; - my $to_domain = $conf->config('send-to-domain') + my $to_domain = $conf->config('email-to-voice_domain') or die "Can't send notice with out send-to-domain, being set in global config \n"; my $cust_main = $self->cust_main($object); diff --git a/httemplate/misc/email-customers.html b/httemplate/misc/email-customers.html index 6e37d74fc..1c756cdb9 100644 --- a/httemplate/misc/email-customers.html +++ b/httemplate/misc/email-customers.html @@ -263,7 +263,7 @@ die "access denied" my $conf = FS::Conf->new; my @no_search_fields = qw( action table from subject html_body text_body popup url ); -my $send_to_domain = $conf->config('send-to-domain'); +my $send_to_domain = $conf->config('email-to-voice_domain'); my $form_action = $opt{'form_action'} || 'email-customers.html'; my $process_url = $opt{'process_url'} || 'process/email-customers.html'; -- cgit v1.2.1 From 63681a4b7352cab8402cb732dc691d35c01d21e2 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Tue, 29 Aug 2017 11:50:18 -0400 Subject: RT# 73964 - updated config description to better explain the email-to-voice_domain configuration option --- FS/FS/Conf.pm | 2 +- FS/FS/part_event/Action/notice_to_emailtovoice.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 1e9a5f48d..c22dec026 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1014,7 +1014,7 @@ my $validate_email = sub { $_[0] =~ { 'key' => 'email-to-voice_domain', 'section' => 'email_to_voice_services', - 'description' => 'The Domain to send email to voice to', + 'description' => 'The domain name used to send emails to a 3rd party email to voice service. You will be able to select a phone number to append to this domain on the email customer page or when using the email to voice billing event action.', 'type' => 'text', 'per_agent' => 1, }, diff --git a/FS/FS/part_event/Action/notice_to_emailtovoice.pm b/FS/FS/part_event/Action/notice_to_emailtovoice.pm index a3a5e9824..3eaa73850 100644 --- a/FS/FS/part_event/Action/notice_to_emailtovoice.pm +++ b/FS/FS/part_event/Action/notice_to_emailtovoice.pm @@ -33,7 +33,7 @@ sub option_fields { 'fax' => 'Fax #', 'daytime' => 'Day Time #', }, - 'post_field_label' => "@", # . $to_domain , + 'post_field_label' => ' Make sure you have setup your email-to-voice_domain config option in your Configuration settings.', }, 'msgnum' => { 'label' => 'Template', -- cgit v1.2.1 From 3ffc2c0afc7d48282449be768bd2c36c2410e3c9 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Mon, 11 Sep 2017 11:17:52 -0400 Subject: RT# 73964 - updated config description again to an even better description of the email-to-voice_domain configuration option --- FS/FS/Conf.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index c22dec026..d2351c005 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1014,7 +1014,7 @@ my $validate_email = sub { $_[0] =~ { 'key' => 'email-to-voice_domain', 'section' => 'email_to_voice_services', - 'description' => 'The domain name used to send emails to a 3rd party email to voice service. You will be able to select a phone number to append to this domain on the email customer page or when using the email to voice billing event action.', + 'description' => 'The domain name that phone numbers will be attached to for sending email to voice emails via a 3rd party email to voice service. You will get this domain from your email to voice service provider. This is utilized on the email customer page or when using the email to voice billing event action. There you will be able to select the phone number for the email to voice service.', 'type' => 'text', 'per_agent' => 1, }, -- cgit v1.2.1 From f9ea5ad4c0ab886ee546012e8a6adec5a3dbf763 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Fri, 22 Jun 2018 14:55:34 -0400 Subject: RT# 73964 - V3 fixes for backport --- FS/FS/cust_main_Mixin.pm | 2 +- httemplate/misc/email-customers.html | 35 ++++++++++++++++++++++++++--------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/FS/FS/cust_main_Mixin.pm b/FS/FS/cust_main_Mixin.pm index aa0395a4a..ccd1b84c9 100644 --- a/FS/FS/cust_main_Mixin.pm +++ b/FS/FS/cust_main_Mixin.pm @@ -487,7 +487,7 @@ sub email_search_result { if (!@classes) { @classes = ( 'invoice' ); } - my @to = $cust_main->contact_list_email(@classes); + my @to = $to ? split(',', $to) : $cust_main->contact_list_email(@classes); next if !@to; %message = ( diff --git a/httemplate/misc/email-customers.html b/httemplate/misc/email-customers.html index 1c756cdb9..1c22f8ffd 100644 --- a/httemplate/misc/email-customers.html +++ b/httemplate/misc/email-customers.html @@ -46,7 +46,6 @@ should be used to set msgnum or from/subject/html_body cgi params - % if ( $cgi->param('action') eq 'send' ) { @@ -72,6 +71,7 @@ should be used to set msgnum or from/subject/html_body cgi params + % if ( $msg_template ) { <% include('/elements/tr-fixed.html', 'label' => 'Template:', @@ -172,7 +172,7 @@ Template:
Email to voice
- +% } +% elsif ($payby eq 'CHEK') { +% +% my @cust_payby = (); +% if ( $payby eq 'CARD' ) { +% @cust_payby = $cust_main->cust_payby('CARD','DCRD'); +% } elsif ( $payby eq 'CHEK' ) { +% @cust_payby = $cust_main->cust_payby('CHEK','DCHK'); % } else { +% die "unknown payby $payby"; +% } +% +% my $custpaybynum = length(scalar($cgi->param('custpaybynum'))) +% ? scalar($cgi->param('custpaybynum')) +% : scalar(@cust_payby) && $cust_payby[0]->custpaybynum; +<& /elements/tr-select-cust_payby.html, + 'cust_payby' => \@cust_payby, + 'curr_value' => $custpaybynum, + 'onchange' => 'cust_payby_changed(this)', +&> + +% } else { % } @@ -157,6 +178,9 @@ if ( $cgi->param('paynum') =~ /^(\d+)$/ ) { } die "no custnum or paynum specified!" unless $custnum; +my $cust_main = qsearchs( 'cust_main', { 'custnum'=>$custnum } ); +die "unknown custnum $custnum" unless $cust_main; + my $_date = time; my $p1 = popurl(1); diff --git a/httemplate/edit/process/cust_refund.cgi b/httemplate/edit/process/cust_refund.cgi index 764f2deb7..b1b5c80bd 100755 --- a/httemplate/edit/process/cust_refund.cgi +++ b/httemplate/edit/process/cust_refund.cgi @@ -21,6 +21,8 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Refund payment') || $FS::CurrentUser::CurrentUser->access_right('Post refund'); +my $conf = new FS::Conf; + $cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!"; my $custnum = $1; my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) @@ -42,6 +44,149 @@ if ( $error ) { } elsif ( $payby =~ /^(CARD|CHEK)$/ ) { my %options = (); my $bop = $FS::payby::payby2bop{$1}; + + my %payby2fields = ( + 'CARD' => [ qw( address1 address2 city county state zip country ) ], + 'CHEK' => [ qw( ss paytype paystate stateid stateid_state ) ], + ); + my %type = ( 'CARD' => 'credit card', + 'CHEK' => 'electronic check (ACH)', + ); + +my( $cust_payby, $payinfo, $paycvv, $month, $year, $payname ); +my $paymask = ''; +if ( (my $custpaybynum = scalar($cgi->param('custpaybynum'))) > 0 ) { + + ## + # use stored cust_payby info + ## + + $cust_payby = qsearchs('cust_payby', { custnum => $custnum, + custpaybynum => $custpaybynum, } ) + or die "unknown custpaybynum $custpaybynum"; + + # not needed for realtime_bop, but still needed for batch_card + $payinfo = $cust_payby->payinfo; + $paymask = $cust_payby->paymask; + $paycvv = $cust_payby->paycvv; # pass it if we got it, running a transaction will clear it + ( $month, $year ) = $cust_payby->paydate_mon_year; + $payname = $cust_payby->payname; + +} else { + + ## + # use new info + ## + + $cgi->param('year') =~ /^(\d+)$/ + or errorpage("illegal year ". $cgi->param('year')); + $year = $1; + + $cgi->param('month') =~ /^(\d+)$/ + or errorpage("illegal month ". $cgi->param('month')); + $month = $1; + + $cgi->param('payname') =~ /^([\w \,\.\-\']+)$/ + or errorpage(gettext('illegal_name'). " payname: ". $cgi->param('payname')); + $payname = $1; + + if ( $payby eq 'CHEK' ) { + + $cgi->param('payinfo1') =~ /^(\d+)$/ + or errorpage("Illegal account number ". $cgi->param('payinfo1')); + my $payinfo1 = $1; + $cgi->param('payinfo2') =~ /^(\d+)$/ + or errorpage("Illegal ABA/routing number ". $cgi->param('payinfo2')); + my $payinfo2 = $1; + if ( $conf->config('echeck-country') eq 'CA' ) { + $cgi->param('payinfo3') =~ /^(\d{5})$/ + or errorpage("Illegal branch number ". $cgi->param('payinfo2')); + $payinfo2 = "$1.$payinfo2"; + } + $payinfo = $payinfo1 . '@'. $payinfo2; + + } elsif ( $payby eq 'CARD' ) { + + $payinfo = $cgi->param('payinfo'); + + $payinfo =~ s/\D//g; + $payinfo =~ /^(\d{13,19}|\d{8,9})$/ + or errorpage(gettext('invalid_card')); + $payinfo = $1; + validate($payinfo) + or errorpage(gettext('invalid_card')); + + unless ( $cust_main->tokenized($payinfo) ) { #token + + my $cardtype = cardtype($payinfo); + + errorpage(gettext('unknown_card_type')) + if $cardtype eq "Unknown"; + + my %bop_card_types = map { $_=>1 } values %{ card_types() }; + errorpage("$cardtype not accepted") unless $bop_card_types{$cardtype}; + + } + + if ( length($cgi->param('paycvv') ) ) { + if ( cardtype($payinfo) eq 'American Express card' ) { + $cgi->param('paycvv') =~ /^(\d{4})$/ + or errorpage("CVV2 (CID) for American Express cards is four digits."); + $paycvv = $1; + } else { + $cgi->param('paycvv') =~ /^(\d{3})$/ + or errorpage("CVV2 (CVC2/CID) is three digits."); + $paycvv = $1; + } + } elsif ( $conf->exists('backoffice-require_cvv') ){ + errorpage("CVV2 is required"); + } + + } else { + die "unknown payby $payby"; + } + + # save first, for proper tokenization + if ( $cgi->param('save') ) { + + my %saveopt; + if ( $payby eq 'CARD' ) { + my $bill_location = FS::cust_location->new; + $bill_location->set( $_ => scalar($cgi->param($_)) ) + foreach @{$payby2fields{$payby}}; + $saveopt{'bill_location'} = $bill_location; + $saveopt{'paycvv'} = $paycvv; # save_cust_payby contains conf logic for when to use this + $saveopt{'paydate'} = "$year-$month-01"; + } else { + # ss/stateid/stateid_state won't be saved, but should be harmless to pass + %saveopt = map { $_ => scalar($cgi->param($_)) } @{$payby2fields{$payby}}; + } + + my $error = $cust_main->save_cust_payby( + 'saved_cust_payby' => \$cust_payby, + 'payment_payby' => $payby, + 'auto' => scalar($cgi->param('auto')), + 'weight' => scalar($cgi->param('weight')), + 'payinfo' => $payinfo, + 'payname' => $payname, + %saveopt + ); + + errorpage("error saving info, payment not processed: $error") + if $error; + + } elsif ( $payby eq 'CARD' ) { # not saving + + $paymask = FS::payinfo_Mixin->mask_payinfo('CARD',$payinfo); # for untokenized but tokenizable payinfo + + } + +} + +## +# now run the refund +## + $cgi->param('refund') =~ /^(\d*)(\.\d{2})?$/ or die "illegal refund amount ". $cgi->param('refund'); my $refund = "$1$2"; @@ -49,10 +194,42 @@ if ( $error ) { my $paynum = $1; my $paydate = $cgi->param('exp_year'). '-'. $cgi->param('exp_month'). '-01'; $options{'paydate'} = $paydate if $paydate =~ /^\d{2,4}-\d{1,2}-01$/; - $error = $cust_main->realtime_refund_bop( $bop, 'amount' => $refund, + + if ( $cgi->param('batch') ) { + + $error ||= $cust_main->batch_card( + 'payby' => $payby, + 'amount' => $refund, + 'payinfo' => $payinfo, + 'paydate' => "$year-$month-01", + 'payname' => $payname, + 'paycode' => 'C', + map { $_ => scalar($cgi->param($_)) } + @{$payby2fields{$payby}} + ); + errorpage($error) if $error; + +#### post refund ##### + my %hash = map { + $_, scalar($cgi->param($_)) + } fields('cust_refund'); + $paynum = $cgi->param('paynum'); + $paynum =~ /^(\d*)$/ or die "Illegal paynum!"; + if ($paynum) { + my $cust_pay = qsearchs('cust_pay',{ 'paynum' => $paynum }); + die "Could not find paynum $paynum" unless $cust_pay; + $error = $cust_pay->refund(\%hash); + } else { + my $new = new FS::cust_refund ( \%hash ); + $error = $new->insert; + } + # if not a batch refund run realtime. + } else { + $error = $cust_main->realtime_refund_bop( $bop, 'amount' => $refund, 'paynum' => $paynum, 'reasonnum' => $reasonnum, %options ); + } } else { my %hash = map { $_, scalar($cgi->param($_)) -- cgit v1.2.1 From b4647550d33068067925f7f3fe8d6fe4f02a67e4 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Tue, 12 Jun 2018 09:51:58 -0400 Subject: RT# 74435 - fixed errors in posting a echeck refund when no account is listed. Conflicts: FS/FS/cust_main/Billing_Batch.pm httemplate/elements/tr-select-cust_payby.html httemplate/misc/payment.cgi httemplate/misc/process/payment.cgi --- FS/FS/cust_main.pm | 4 ++-- httemplate/edit/cust_refund.cgi | 34 +++++++++++++++++++++++++++++++++ httemplate/edit/process/cust_refund.cgi | 8 +++++--- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index d4214d1c8..c5cee8d94 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -2790,7 +2790,7 @@ sub batch_card { } ); foreach (qw( address1 address2 city state zip country latitude longitude - payby payinfo paydate payname paycode )) + payby payinfo paydate payname paycode paytype )) { $options{$_} = '' unless exists($options{$_}); } @@ -2817,7 +2817,7 @@ sub batch_card { 'exp' => $options{paydate} || $self->paydate, 'payname' => $options{payname} || $self->payname, 'amount' => $amount, # consolidating - 'paycode' => $options{paycode} || $cust_payby->paycode, + 'paycode' => $options{paycode} || '', } ); $cust_pay_batch->paybatchnum($old_cust_pay_batch->paybatchnum) diff --git a/httemplate/edit/cust_refund.cgi b/httemplate/edit/cust_refund.cgi index e1975ed70..473648f42 100755 --- a/httemplate/edit/cust_refund.cgi +++ b/httemplate/edit/cust_refund.cgi @@ -85,6 +85,7 @@
Refund + <% ntable("#cccccc", 2) %>
@@ -105,6 +106,19 @@ % } % elsif ($payby eq 'CHEK') { % + % my @cust_payby = (); % if ( $payby eq 'CARD' ) { % @cust_payby = $cust_main->cust_payby('CARD','DCRD'); @@ -123,10 +137,30 @@ 'onchange' => 'cust_payby_changed(this)', &> +
Check #
+

+

+> + + +<& /elements/cust_payby_new.html, + 'cust_payby' => \@cust_payby, + 'curr_value' => $custpaybynum, +&> + +
+
+ % } else { + % } +

+ <& /elements/tr-select-reason.html, 'field' => 'reasonnum', 'reason_class' => 'F', diff --git a/httemplate/edit/process/cust_refund.cgi b/httemplate/edit/process/cust_refund.cgi index b1b5c80bd..8dbe4fc63 100755 --- a/httemplate/edit/process/cust_refund.cgi +++ b/httemplate/edit/process/cust_refund.cgi @@ -71,6 +71,7 @@ if ( (my $custpaybynum = scalar($cgi->param('custpaybynum'))) > 0 ) { $paycvv = $cust_payby->paycvv; # pass it if we got it, running a transaction will clear it ( $month, $year ) = $cust_payby->paydate_mon_year; $payname = $cust_payby->payname; + $cgi->param(-name=>"paytype", -value=>$cust_payby->paytype) unless $cgi->param("paytype"); } else { @@ -192,8 +193,9 @@ if ( (my $custpaybynum = scalar($cgi->param('custpaybynum'))) > 0 ) { my $refund = "$1$2"; $cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!"; my $paynum = $1; - my $paydate = $cgi->param('exp_year'). '-'. $cgi->param('exp_month'). '-01'; - $options{'paydate'} = $paydate if $paydate =~ /^\d{2,4}-\d{1,2}-01$/; + my $paydate; + if ($cust_payby->paydate) { $paydate = "$year-$month-01"; } + else { $paydate = "2037-12-01"; } if ( $cgi->param('batch') ) { @@ -201,7 +203,7 @@ if ( (my $custpaybynum = scalar($cgi->param('custpaybynum'))) > 0 ) { 'payby' => $payby, 'amount' => $refund, 'payinfo' => $payinfo, - 'paydate' => "$year-$month-01", + 'paydate' => $paydate, 'payname' => $payname, 'paycode' => 'C', map { $_ => scalar($cgi->param($_)) } -- cgit v1.2.1 From a90d069b1f1d814a91bf7cb3eb0d7ac9fdec7e08 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Mon, 9 Jul 2018 08:27:53 -0400 Subject: RT# 74435 - Fixed error with refund link on payment history page not allowing batch refund when batch payment was made. Conflicts: FS/FS/cust_main/Billing_Realtime.pm httemplate/view/cust_main/menu.html --- FS/FS/cust_main/Billing_Realtime.pm | 2 +- httemplate/edit/cust_refund.cgi | 72 ++++---------- httemplate/edit/process/cust_refund.cgi | 170 ++++---------------------------- 3 files changed, 38 insertions(+), 206 deletions(-) diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm index ae41c70c4..c503b45e6 100644 --- a/FS/FS/cust_main/Billing_Realtime.pm +++ b/FS/FS/cust_main/Billing_Realtime.pm @@ -1479,7 +1479,7 @@ sub realtime_refund_bop { $self->agent->payment_gateway( 'method' => $options{method}, #'payinfo' => $payinfo, ); - my( $processor, $login, $password, $namespace ) = + ( $processor, $login, $password, $namespace ) = map { my $method = "gateway_$_"; $payment_gateway->$method } qw( module username password namespace ); diff --git a/httemplate/edit/cust_refund.cgi b/httemplate/edit/cust_refund.cgi index 473648f42..55f06642b 100755 --- a/httemplate/edit/cust_refund.cgi +++ b/httemplate/edit/cust_refund.cgi @@ -104,63 +104,25 @@ % } -% elsif ($payby eq 'CHEK') { -% - -% my @cust_payby = (); -% if ( $payby eq 'CARD' ) { -% @cust_payby = $cust_main->cust_payby('CARD','DCRD'); -% } elsif ( $payby eq 'CHEK' ) { -% @cust_payby = $cust_main->cust_payby('CHEK','DCHK'); -% } else { -% die "unknown payby $payby"; -% } -% -% my $custpaybynum = length(scalar($cgi->param('custpaybynum'))) -% ? scalar($cgi->param('custpaybynum')) -% : scalar(@cust_payby) && $cust_payby[0]->custpaybynum; -<& /elements/tr-select-cust_payby.html, - 'cust_payby' => \@cust_payby, - 'curr_value' => $custpaybynum, - 'onchange' => 'cust_payby_changed(this)', -&> - -
-

-

-> - - -<& /elements/cust_payby_new.html, - 'cust_payby' => \@cust_payby, - 'curr_value' => $custpaybynum, -&> - -
-
+% elsif ($payby eq 'CHEK' || $payby eq 'CARD') { + +% if ( $conf->exists("batch-enable") +% || grep $payby eq $_, $conf->config('batch-enable_payby') +% ) { +% if ( grep $payby eq $_, $conf->config('realtime-disable_payby') ) { + +% } else { + + > +    <% mt('Add to current batch') |h %> + +% } +% } -% } else { +% } else { - % } -

- <& /elements/tr-select-reason.html, 'field' => 'reasonnum', 'reason_class' => 'F', @@ -193,16 +155,18 @@ my $payby = $cgi->param('payby'); my $payinfo = $cgi->param('payinfo'); my $reason = $cgi->param('reason'); my $link = $cgi->param('popup') ? 'popup' : ''; +my $batch = $cgi->param('batch'); die "access denied" unless $FS::CurrentUser::CurrentUser->refund_access_right($payby); -my( $paynum, $cust_pay ) = ( '', '' ); +my( $paynum, $cust_pay, $batchnum ) = ( '', '', '' ); if ( $cgi->param('paynum') =~ /^(\d+)$/ ) { $paynum = $1; $cust_pay = qsearchs('cust_pay', { paynum=>$paynum } ) or die "unknown payment # $paynum"; $refund ||= $cust_pay->unrefunded; + $batchnum = $cust_pay->batchnum; if ( $custnum ) { die "payment # $paynum is not for specified customer # $custnum" unless $custnum == $cust_pay->custnum; diff --git a/httemplate/edit/process/cust_refund.cgi b/httemplate/edit/process/cust_refund.cgi index 8dbe4fc63..49aedf3fe 100755 --- a/httemplate/edit/process/cust_refund.cgi +++ b/httemplate/edit/process/cust_refund.cgi @@ -53,137 +53,6 @@ if ( $error ) { 'CHEK' => 'electronic check (ACH)', ); -my( $cust_payby, $payinfo, $paycvv, $month, $year, $payname ); -my $paymask = ''; -if ( (my $custpaybynum = scalar($cgi->param('custpaybynum'))) > 0 ) { - - ## - # use stored cust_payby info - ## - - $cust_payby = qsearchs('cust_payby', { custnum => $custnum, - custpaybynum => $custpaybynum, } ) - or die "unknown custpaybynum $custpaybynum"; - - # not needed for realtime_bop, but still needed for batch_card - $payinfo = $cust_payby->payinfo; - $paymask = $cust_payby->paymask; - $paycvv = $cust_payby->paycvv; # pass it if we got it, running a transaction will clear it - ( $month, $year ) = $cust_payby->paydate_mon_year; - $payname = $cust_payby->payname; - $cgi->param(-name=>"paytype", -value=>$cust_payby->paytype) unless $cgi->param("paytype"); - -} else { - - ## - # use new info - ## - - $cgi->param('year') =~ /^(\d+)$/ - or errorpage("illegal year ". $cgi->param('year')); - $year = $1; - - $cgi->param('month') =~ /^(\d+)$/ - or errorpage("illegal month ". $cgi->param('month')); - $month = $1; - - $cgi->param('payname') =~ /^([\w \,\.\-\']+)$/ - or errorpage(gettext('illegal_name'). " payname: ". $cgi->param('payname')); - $payname = $1; - - if ( $payby eq 'CHEK' ) { - - $cgi->param('payinfo1') =~ /^(\d+)$/ - or errorpage("Illegal account number ". $cgi->param('payinfo1')); - my $payinfo1 = $1; - $cgi->param('payinfo2') =~ /^(\d+)$/ - or errorpage("Illegal ABA/routing number ". $cgi->param('payinfo2')); - my $payinfo2 = $1; - if ( $conf->config('echeck-country') eq 'CA' ) { - $cgi->param('payinfo3') =~ /^(\d{5})$/ - or errorpage("Illegal branch number ". $cgi->param('payinfo2')); - $payinfo2 = "$1.$payinfo2"; - } - $payinfo = $payinfo1 . '@'. $payinfo2; - - } elsif ( $payby eq 'CARD' ) { - - $payinfo = $cgi->param('payinfo'); - - $payinfo =~ s/\D//g; - $payinfo =~ /^(\d{13,19}|\d{8,9})$/ - or errorpage(gettext('invalid_card')); - $payinfo = $1; - validate($payinfo) - or errorpage(gettext('invalid_card')); - - unless ( $cust_main->tokenized($payinfo) ) { #token - - my $cardtype = cardtype($payinfo); - - errorpage(gettext('unknown_card_type')) - if $cardtype eq "Unknown"; - - my %bop_card_types = map { $_=>1 } values %{ card_types() }; - errorpage("$cardtype not accepted") unless $bop_card_types{$cardtype}; - - } - - if ( length($cgi->param('paycvv') ) ) { - if ( cardtype($payinfo) eq 'American Express card' ) { - $cgi->param('paycvv') =~ /^(\d{4})$/ - or errorpage("CVV2 (CID) for American Express cards is four digits."); - $paycvv = $1; - } else { - $cgi->param('paycvv') =~ /^(\d{3})$/ - or errorpage("CVV2 (CVC2/CID) is three digits."); - $paycvv = $1; - } - } elsif ( $conf->exists('backoffice-require_cvv') ){ - errorpage("CVV2 is required"); - } - - } else { - die "unknown payby $payby"; - } - - # save first, for proper tokenization - if ( $cgi->param('save') ) { - - my %saveopt; - if ( $payby eq 'CARD' ) { - my $bill_location = FS::cust_location->new; - $bill_location->set( $_ => scalar($cgi->param($_)) ) - foreach @{$payby2fields{$payby}}; - $saveopt{'bill_location'} = $bill_location; - $saveopt{'paycvv'} = $paycvv; # save_cust_payby contains conf logic for when to use this - $saveopt{'paydate'} = "$year-$month-01"; - } else { - # ss/stateid/stateid_state won't be saved, but should be harmless to pass - %saveopt = map { $_ => scalar($cgi->param($_)) } @{$payby2fields{$payby}}; - } - - my $error = $cust_main->save_cust_payby( - 'saved_cust_payby' => \$cust_payby, - 'payment_payby' => $payby, - 'auto' => scalar($cgi->param('auto')), - 'weight' => scalar($cgi->param('weight')), - 'payinfo' => $payinfo, - 'payname' => $payname, - %saveopt - ); - - errorpage("error saving info, payment not processed: $error") - if $error; - - } elsif ( $payby eq 'CARD' ) { # not saving - - $paymask = FS::payinfo_Mixin->mask_payinfo('CARD',$payinfo); # for untokenized but tokenizable payinfo - - } - -} - ## # now run the refund ## @@ -193,46 +62,45 @@ if ( (my $custpaybynum = scalar($cgi->param('custpaybynum'))) > 0 ) { my $refund = "$1$2"; $cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!"; my $paynum = $1; - my $paydate; - if ($cust_payby->paydate) { $paydate = "$year-$month-01"; } - else { $paydate = "2037-12-01"; } + #my $paydate; + my $paydate = $cgi->param('exp_year'). '-'. $cgi->param('exp_month'). '-01'; + #unless ($paynum) { + # if ($cust_payby->paydate) { $paydate = "$year-$month-01"; } + # else { $paydate = "2037-12-01"; } + #} if ( $cgi->param('batch') ) { - + $paydate = "2037-12-01" unless $paydate; $error ||= $cust_main->batch_card( 'payby' => $payby, 'amount' => $refund, - 'payinfo' => $payinfo, - 'paydate' => $paydate, - 'payname' => $payname, + #'payinfo' => $payinfo, + #'paydate' => $paydate, + #'payname' => $payname, 'paycode' => 'C', map { $_ => scalar($cgi->param($_)) } @{$payby2fields{$payby}} ); errorpage($error) if $error; -#### post refund ##### my %hash = map { $_, scalar($cgi->param($_)) } fields('cust_refund'); - $paynum = $cgi->param('paynum'); - $paynum =~ /^(\d*)$/ or die "Illegal paynum!"; - if ($paynum) { - my $cust_pay = qsearchs('cust_pay',{ 'paynum' => $paynum }); - die "Could not find paynum $paynum" unless $cust_pay; - $error = $cust_pay->refund(\%hash); - } else { - my $new = new FS::cust_refund ( \%hash ); - $error = $new->insert; - } - # if not a batch refund run realtime. + + my $new = new FS::cust_refund ( { 'paynum' => $paynum, + %hash, + } ); + $error = $new->insert; + + # if not a batch refund run realtime. } else { + $options{'paydate'} = $paydate if $paydate =~ /^\d{2,4}-\d{1,2}-01$/; $error = $cust_main->realtime_refund_bop( $bop, 'amount' => $refund, 'paynum' => $paynum, 'reasonnum' => $reasonnum, %options ); } -} else { +} else { # run cash refund. my %hash = map { $_, scalar($cgi->param($_)) } fields('cust_refund'); -- cgit v1.2.1 From a439c0c5998c428e7bfd533353911ae48b4bee7b Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Mon, 16 Jul 2018 14:40:39 -0400 Subject: RT# 74435 - added check, to make sure batch format can handle refunds Conflicts: httemplate/misc/download-batch.cgi --- FS/FS/pay_batch/RBC.pm | 5 +++++ httemplate/edit/cust_refund.cgi | 4 ---- httemplate/edit/process/cust_refund.cgi | 7 ------- httemplate/misc/download-batch.cgi | 11 ++++++++++- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/FS/FS/pay_batch/RBC.pm b/FS/FS/pay_batch/RBC.pm index 05ee4e501..c4388d156 100644 --- a/FS/FS/pay_batch/RBC.pm +++ b/FS/FS/pay_batch/RBC.pm @@ -230,5 +230,10 @@ $name = 'RBC'; }, ); +## this format can handle credit transactions +sub can_handle_credits { + 1; +} + 1; diff --git a/httemplate/edit/cust_refund.cgi b/httemplate/edit/cust_refund.cgi index 55f06642b..c79c39a80 100755 --- a/httemplate/edit/cust_refund.cgi +++ b/httemplate/edit/cust_refund.cgi @@ -85,7 +85,6 @@
Refund - <% ntable("#cccccc", 2) %>
@@ -176,9 +175,6 @@ if ( $cgi->param('paynum') =~ /^(\d+)$/ ) { } die "no custnum or paynum specified!" unless $custnum; -my $cust_main = qsearchs( 'cust_main', { 'custnum'=>$custnum } ); -die "unknown custnum $custnum" unless $cust_main; - my $_date = time; my $p1 = popurl(1); diff --git a/httemplate/edit/process/cust_refund.cgi b/httemplate/edit/process/cust_refund.cgi index 49aedf3fe..44605bf42 100755 --- a/httemplate/edit/process/cust_refund.cgi +++ b/httemplate/edit/process/cust_refund.cgi @@ -21,8 +21,6 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Refund payment') || $FS::CurrentUser::CurrentUser->access_right('Post refund'); -my $conf = new FS::Conf; - $cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!"; my $custnum = $1; my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) @@ -62,12 +60,7 @@ if ( $error ) { my $refund = "$1$2"; $cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!"; my $paynum = $1; - #my $paydate; my $paydate = $cgi->param('exp_year'). '-'. $cgi->param('exp_month'). '-01'; - #unless ($paynum) { - # if ($cust_payby->paydate) { $paydate = "$year-$month-01"; } - # else { $paydate = "2037-12-01"; } - #} if ( $cgi->param('batch') ) { $paydate = "2037-12-01" unless $paydate; diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi index f3a31eb3b..7b56f2aa1 100644 --- a/httemplate/misc/download-batch.cgi +++ b/httemplate/misc/download-batch.cgi @@ -20,7 +20,16 @@ elsif ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) { $opt{'format'} = $1; } -my $pay_batch = qsearchs('pay_batch', { batchnum => $batchnum } ); +my $credit_transactions = "EXISTS (SELECT 1 FROM cust_pay_batch WHERE batchnum = $batchnum AND paycode = 'C') AS arecredits"; +my $pay_batch = qsearchs({ 'select' => "*, $credit_transactions", + 'table' => 'pay_batch', + 'hashref' => { batchnum => $batchnum }, + }); die "Batch not found: '$batchnum'" if !$pay_batch; +if ($pay_batch->{Hash}->{arecredits}) { + my $export_format = "FS::pay_batch::".$opt{'format'}; + die "This format can not handle refunds." unless $export_format->can('can_handle_credits'); +} + -- cgit v1.2.1