From 7118e89bc7d860ed82aa12b3e78d9f2d31afe597 Mon Sep 17 00:00:00 2001 From: Brian Medley Date: Sun, 22 Apr 2012 18:47:18 -0500 Subject: remove superflous comma, #15655 --- httemplate/edit/tower.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httemplate/edit/tower.html b/httemplate/edit/tower.html index 82513082c..03b488e86 100644 --- a/httemplate/edit/tower.html +++ b/httemplate/edit/tower.html @@ -20,8 +20,8 @@ 'sectornum' => 'Sector', 'disabled' => 'Disabled', 'default_ip_addr' => 'Tower IP address', - 'latitude', => 'Latitude', - 'longitude', => 'Longitude', + 'latitude' => 'Latitude', + 'longitude' => 'Longitude', }, &> <%init> -- cgit v1.2.1 From 43e6b2ea06434abead940939c9b04e00e3a72f32 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Mon, 23 Apr 2012 20:27:17 -0700 Subject: manual control of quick payment application, #15861 --- FS/FS/cust_pay.pm | 42 ++- httemplate/elements/customer-table.html | 36 ++- httemplate/misc/batch-cust_pay.html | 354 ++++++++++++++++++++++++-- httemplate/misc/process/batch-cust_pay.cgi | 115 +++++---- httemplate/misc/xmlhttp-cust_bill-search.html | 18 ++ 5 files changed, 479 insertions(+), 86 deletions(-) create mode 100644 httemplate/misc/xmlhttp-cust_bill-search.html diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm index ef30809b0..e15bf0154 100644 --- a/FS/FS/cust_pay.pm +++ b/FS/FS/cust_pay.pm @@ -760,6 +760,12 @@ objects. Returns a list, each element representing the status of inserting the corresponding payment - empty. If there is an error inserting any payment, the entire transaction is rolled back, i.e. all payments are inserted or none are. +FS::cust_pay objects may have the pseudo-field 'apply_to', containing a +reference to an array of (uninserted) FS::cust_bill_pay objects. If so, +those objects will be inserted with the paynum of the payment, and for +each one, an error message or an empty string will be inserted into the +list of errors. + For example: my @errors = FS::cust_pay->batch_insert(@cust_pay); @@ -786,19 +792,35 @@ sub batch_insert { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - my $errors = 0; + my $num_errors = 0; - my @errors = map { - my $error = $_->insert( 'manual' => 1 ); - if ( $error ) { - $errors++; - } else { - $_->cust_main->apply_payments; + my @errors; + foreach my $cust_pay (@_) { + my $error = $cust_pay->insert( 'manual' => 1 ); + push @errors, $error; + $num_errors++ if $error; + + if ( ref($cust_pay->get('apply_to')) eq 'ARRAY' ) { + + foreach my $cust_bill_pay ( @{ $cust_pay->apply_to } ) { + if ( $error ) { # insert placeholders if cust_pay wasn't inserted + push @errors, ''; + } + else { + $cust_bill_pay->set('paynum', $cust_pay->paynum); + my $apply_error = $cust_bill_pay->insert; + push @errors, $apply_error || ''; + $num_errors++ if $apply_error; + } + } + + } elsif ( !$error ) { #normal case: apply payments as usual + $cust_pay->cust_main->apply_payments; } - $error; - } @_; - if ( $errors ) { + } + + if ( $num_errors ) { $dbh->rollback if $oldAutoCommit; } else { $dbh->commit or die $dbh->errstr if $oldAutoCommit; diff --git a/httemplate/elements/customer-table.html b/httemplate/elements/customer-table.html index a517ece2a..b49bf0290 100644 --- a/httemplate/elements/customer-table.html +++ b/httemplate/elements/customer-table.html @@ -356,7 +356,7 @@ Example: % my $row = 0; % for ( $row = 0; exists($param->{"custnum$row"}); $row++ ) { - + [$col]; % my $font = $color ? qq() : ''; % my $onchange = ''; -% if ( $opt{footer}->[$col] eq '_TOTAL' ) { +% if ( $opt{onchange}->[$col] ) { +% $onchange = 'onchange="'.$opt{onchange}->[$col].'"'; +% } +% elsif ( $opt{footer}->[$col] eq '_TOTAL' ) { % $total[$col] += $value; % $onchange = $opt{prefix}. "calc_total$col();"; % $onchange = qq(onchange="$onchange" onkeyup="$onchange"); % } -% if (! $types->[$col] || $types->[$col] eq 'text') { - [$col] || 'text'; +% if ($type eq 'text' or $type eq 'checkbox') { + > % } elsif ($types->[$col] eq 'immutable') { @@ -485,7 +490,7 @@ Example: % } - + Total <% $row ? $row-1 : 0 %> <% PL($opt{name_singular} || 'customer', ( $row ? $row-1 : 0 ) ) %> @@ -559,7 +564,8 @@ Example: var table = document.getElementById('<% $opt{prefix} %>OneTrueTable'); var tablebody = table.getElementsByTagName('tbody').item(0); - var row = table.insertRow(rownum+1); + var row = table.insertRow(table.rows.length - 1); + row.setAttribute('id', 'row'+rownum); var invnum_cell = document.createElement('TD'); @@ -676,7 +682,7 @@ Example: % } else { % $value = $param->{"$field$row"}; % } - var my_text = document.createTextNode('<% $value %>'); + var my_text = document.createTextNode(<% $value |js_string %>); my_cell.appendChild(my_text); % } @@ -686,10 +692,17 @@ Example: my_input.setAttribute('id', '<% $name %>'+<% $opt{prefix} %>rownum); my_input.style.textAlign = '<% $align{ $opt{align}->[$col] || 'l' } %>'; my_input.setAttribute('size', <% $sizes->[$col] || 10 %>); -% if ($types->[$col] eq 'immutable') { + my_input.setAttribute('rownum', <% $opt{prefix} %>rownum); +% if ( $types->[$col] eq 'immutable' ) { my_input.setAttribute('type', 'hidden'); % } -% if ( $opt{footer}->[$col] eq '_TOTAL' ) { +% elsif ( $types->[$col] eq 'checkbox' ) { + my_input.setAttribute('type', 'checkbox'); +% } +% if ( $opt{onchange}->[$col] ) { + my_input.onchange = <% $opt{onchange}->[$col] %>; +% } +% elsif ( $opt{footer}->[$col] eq '_TOTAL' ) { my_input.onchange = <% $opt{prefix} %>calc_total<%$col%>; my_input.onkeyup = <% $opt{prefix} %>calc_total<%$col%>; % } @@ -713,6 +726,11 @@ Example: + ' <% PL($opt{name_singular} || 'customer') %>'; } +% if ( $opt{add_row_callback} ) { + <% $opt{add_row_callback} %>(<% $opt{prefix} %>rownum, + '<% $opt{prefix} %>'); +% } + <% $opt{prefix} %>rownum++; } diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html index 2e798652d..45459f14d 100644 --- a/httemplate/misc/batch-cust_pay.html +++ b/httemplate/misc/batch-cust_pay.html @@ -1,6 +1,9 @@ -<% include('/elements/header.html', 'Quick payment entry') %> +<& /elements/header.html, { + title => 'Quick payment entry', + etc => 'onload="preload()"' +} &> -<% include('/elements/error.html') %> +<& /elements/error.html &> <% include('/elements/xmlhttp.html', @@ -57,21 +331,26 @@ function select_discount_term(row, prefix) {
+<& /elements/xmlhttp.html, + url => $p.'misc/xmlhttp-cust_bill-search.html', + subs => ['custnum_search_open'] +&> -<% include( "/elements/customer-table.html", - name_singular => 'payment', - header => \@header, - fields => \@fields, - type => \@types, - align => \@align, - size => \@sizes, - color => \@colors, - param => \%param, - footer => \@footer, - footer_align => \@footer_align, - custnum_update_callback => $custnum_update_callback, - ) -%> +<& /elements/customer-table.html, + name_singular => 'payment', + header => \@header, + fields => \@fields, + type => \@types, + align => \@align, + size => \@sizes, + color => \@colors, + param => \%param, + footer => \@footer, + footer_align => \@footer_align, + onchange => \@onchange, + custnum_update_callback => 'custnum_update_callback', + add_row_callback => 'add_row_callback', +&>
@@ -105,7 +384,8 @@ my @colors = ( '', '' ); my %param = (); my @footer = ( '_TOTAL', '' ); my @footer_align = ( 'r', 'r' ); -my $custnum_update_callback = ''; +my @onchange = ( '', '' );; +my $use_discounts = ''; if ( FS::Record->scalar_sql('SELECT COUNT(*) FROM part_pkg_discount') ) { #push @header, 'Discount'; @@ -117,9 +397,20 @@ if ( FS::Record->scalar_sql('SELECT COUNT(*) FROM part_pkg_discount') ) { push @colors, ''; push @footer, ''; push @footer_align, ''; - $custnum_update_callback = 'select_discount_term'; + push @onchange, ''; + $use_discounts = 'Y'; } +push @header, 'Allocate'; +push @fields, 'enable_app'; +push @types, 'checkbox'; +push @align, 'c'; +push @sizes, '0'; +push @colors, ''; +push @footer, ''; +push @footer_align, ''; +push @onchange, 'toggle_application_row'; + #push @header, 'Error'; push @header, ''; push @fields, 'error'; @@ -129,7 +420,34 @@ push @sizes, '0'; push @colors, '#ff0000'; push @footer, ''; push @footer_align, ''; +push @onchange, ''; $m->comp('/elements/handle_uri_query'); +# set up for preloading +my @rows; +my @row_errors; +if ( $cgi->param('error') ) { + my $param = $cgi->Vars; + my $enum = 0; #errors numbered separately + for( my $row = 0; exists($param->{"custnum$row"}); $row++ ) { + $rows[$row] = []; + $row_errors[$row] = $param->{"error$enum"}; + $enum++; + for( my $app = 0; exists($param->{"invnum$row.$app"}); $app++ ) { + next if !$param->{"invnum$row.$app"}; + my %this_app = map { $_ => ($param->{$_.$row.'.'.$app} || '') } + qw( invnum amount ); + $this_app{'error'} = $param->{"error$enum"} || ''; + $param->{"error$enum"} = ''; # don't pass this error through + $rows[$row][$app] = \%this_app; + $enum++; + } + } + for( my $row = 0; $row < @row_errors; $row++ ) { + $param->{"error$row"} = $row_errors[$row]; + } +} +#warn Dumper {rows => \@rows, row_errors => \@row_errors }; + diff --git a/httemplate/misc/process/batch-cust_pay.cgi b/httemplate/misc/process/batch-cust_pay.cgi index a6b90ea74..3b06f3ab7 100644 --- a/httemplate/misc/process/batch-cust_pay.cgi +++ b/httemplate/misc/process/batch-cust_pay.cgi @@ -1,51 +1,69 @@ -% die "access denied" -% unless $FS::CurrentUser::CurrentUser->access_right('Post payment batch'); -% -% my $param = $cgi->Vars; -% -% #my $paybatch = $param->{'paybatch'}; -% my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time); -% -% my @cust_pay = (); -% #my $row = 0; -% #while ( exists($param->{"custnum$row"}) ) { -% for ( my $row = 0; exists($param->{"custnum$row"}); $row++ ) { -% my $custnum = $param->{"custnum$row"}; -% my $cust_main; -% if ( $custnum =~ /^(\d+)$/ and $1 <= 2147483647 ) { -% $cust_main = qsearchs({ -% 'table' => 'cust_main', -% 'hashref' => { 'custnum' => $1 }, -% 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, -% }); -% } -% if ( length($custnum) and !$cust_main ) { # not found, try agent_custid -% $cust_main = qsearchs({ -% 'table' => 'cust_main', -% 'hashref' => { 'agent_custid' => $custnum }, -% 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, -% }); -% } -% $custnum = $cust_main->custnum if $cust_main; -% # if !$cust_main, then this will throw an error on batch_insert -% -% push @cust_pay, new FS::cust_pay { -% 'custnum' => $custnum, -% 'paid' => $param->{"paid$row"}, -% 'payby' => 'BILL', -% 'payinfo' => $param->{"payinfo$row"}, -% 'discount_term' => $param->{"discount_term$row"}, -% 'paybatch' => $paybatch, -% } -% if $param->{"custnum$row"} -% || $param->{"paid$row"} -% || $param->{"payinfo$row"}; -% #$row++; -% } -% -% my @errors = FS::cust_pay->batch_insert(@cust_pay); -% my $num_errors = scalar(grep $_, @errors); -% +<%init> +my $DEBUG = 1; +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Post payment batch'); + +my $param = $cgi->Vars; +warn Dumper($param) if $DEBUG; + +#my $paybatch = $param->{'paybatch'}; +my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time); + +my @cust_pay = (); +#my $row = 0; +#while ( exists($param->{"custnum$row"}) ) { +for ( my $row = 0; exists($param->{"custnum$row"}); $row++ ) { + my $custnum = $param->{"custnum$row"}; + my $cust_main; + if ( $custnum =~ /^(\d+)$/ and $1 <= 2147483647 ) { + $cust_main = qsearchs({ + 'table' => 'cust_main', + 'hashref' => { 'custnum' => $1 }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, + }); + } + if ( length($custnum) and !$cust_main ) { # not found, try agent_custid + $cust_main = qsearchs({ + 'table' => 'cust_main', + 'hashref' => { 'agent_custid' => $custnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, + }); + } + $custnum = $cust_main->custnum if $cust_main; + # if !$cust_main, then this will throw an error on batch_insert + + my $cust_pay = new FS::cust_pay { + 'custnum' => $custnum, + 'paid' => $param->{"paid$row"}, + 'payby' => 'BILL', + 'payinfo' => $param->{"payinfo$row"}, + 'discount_term' => $param->{"discount_term$row"}, + 'paybatch' => $paybatch, + } + if $param->{"custnum$row"} + || $param->{"paid$row"} + || $param->{"payinfo$row"}; + next if !$cust_pay; + #$row++; + + # payment applications, if any + my @cust_bill_pay = (); + for ( my $app = 0; exists($param->{"invnum$row.$app"}); $app++ ) { + next if !$param->{"invnum$row.$app"}; + push @cust_bill_pay, new FS::cust_bill_pay { + 'invnum' => $param->{"invnum$row.$app"}, + 'amount' => $param->{"amount$row.$app"} + }; + } + $cust_pay->set('apply_to', \@cust_bill_pay) if scalar(@cust_bill_pay) > 0; + + push @cust_pay, $cust_pay; + +} + +my @errors = FS::cust_pay->batch_insert(@cust_pay); +my $num_errors = scalar(grep $_, @errors); + % if ( $num_errors ) { % % $cgi->param('error', "$num_errors error". ($num_errors>1 ? 's' : ''). @@ -65,4 +83,3 @@ % <% $cgi->redirect(popurl(3). "search/cust_pay.html?magic=paybatch;paybatch=$paybatch") %> % } - diff --git a/httemplate/misc/xmlhttp-cust_bill-search.html b/httemplate/misc/xmlhttp-cust_bill-search.html new file mode 100644 index 000000000..46f15d1ab --- /dev/null +++ b/httemplate/misc/xmlhttp-cust_bill-search.html @@ -0,0 +1,18 @@ +<% encode_json(\@return) %> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; +die 'access denied' unless $curuser->access_right('View invoices'); +my @return; +if ( $cgi->param('sub') eq 'custnum_search_open' ) { + my $custnum = $cgi->param('arg'); + #warn "searching invoices for $custnum\n"; + my $cust_main = FS::cust_main->by_key($custnum); + @return = map { + +{ $_->hash, + 'owed' => $_->owed } + } $cust_main->open_cust_bill + if $curuser->agentnums_href->{ $cust_main->agentnum }; +} + + -- cgit v1.2.1 From be368a6ee2dca0ddd4aa53c0b912e09498bef24a Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Mon, 23 Apr 2012 21:05:13 -0700 Subject: fix map canvas --- httemplate/view/directions.html | 1 - 1 file changed, 1 deletion(-) diff --git a/httemplate/view/directions.html b/httemplate/view/directions.html index 599d049c2..f14a11a07 100644 --- a/httemplate/view/directions.html +++ b/httemplate/view/directions.html @@ -22,7 +22,6 @@ body { height: 100%; margin: 0px; padding: 0px } #map_canvas { height: 100%; - margin-right: 320px; } #directions_panel { -- cgit v1.2.1 From a502263ac279815f164ba2abfffec919c6250dcb Mon Sep 17 00:00:00 2001 From: Brian Medley Date: Tue, 24 Apr 2012 07:40:00 -0500 Subject: add in access_right checks for sub menues, #17056 --- httemplate/elements/menu.html | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index b1cbebf34..0f36500b0 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -221,7 +221,8 @@ foreach my $svcdb ( FS::part_svc->svc_tables() ) { } - $report_services{$name} = [ \%report_svc, $longname ]; + $report_services{$name} = [ \%report_svc, $longname ] if + $curuser->access_right("Services: $name"); } @@ -253,14 +254,15 @@ tie my %report_inventory, 'Tie::IxHash', 'Inventory activity' => [ $fsurl.'search/report_h_inventory_item.html', '' ], ; -tie my %report_rating, 'Tie::IxHash', - 'RADIUS sessions' => [ $fsurl.'search/sqlradius.html', '' ], - 'Call Detail Records (CDRs)' => [ $fsurl.'search/report_cdr.html', '' ], - 'Unrateable CDRs' => [ $fsurl.'search/cdr.html?freesidestatus=failed'. - ';cdrbatchnum=_ALL_' ], - 'Time worked' => [ $fsurl.'search/report_rt_transaction.html', '' ], - 'Time worked summary' => [ $fsurl.'search/report_rt_ticket.html', '' ], -; +my @report_rating = (); +push(@report_rating, 'RADIUS sessions' => [ $fsurl.'search/sqlradius.html', '' ]) if $curuser->access_right("Usage: RADIUS sessions"); +push(@report_rating, 'Call Detail Records (CDRs)' => [ $fsurl.'search/report_cdr.html', '' ]) if $curuser->access_right("Usage: Call Detail Records (CDRs)"); +push(@report_rating, 'Unrateable CDRs' => [ $fsurl.'search/cdr.html?freesidestatus=failed'. + ';cdrbatchnum=_ALL_' ]) if $curuser->access_right("Usage: Unrateable CDRs"); +push(@report_rating, 'Time worked' => [ $fsurl.'search/report_rt_transaction.html', '' ]) if $curuser->access_right("Usage: Time worked"); +push(@report_rating, 'Time worked summary' => [ $fsurl.'search/report_rt_ticket.html', '' ]) if $curuser->access_right("Usage: Time worked summary"); + +tie my %report_rating, 'Tie::IxHash', @report_rating; tie my %report_ticketing_statistics, 'Tie::IxHash', 'Tickets per day per Queue' => [ $fsurl.'rt/RTx/Statistics/CallsQueueDay', 'View the number of tickets created, resolved or deleted in a specific Queue, over the requested period of days' ], -- cgit v1.2.1 From 998dcad2d8f4b8e22b9a2f042f006b9167b8cdf3 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Tue, 24 Apr 2012 10:06:05 -0700 Subject: event option, #16382 --- FS/FS/part_event/Action/cust_bill_spool_csv.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FS/FS/part_event/Action/cust_bill_spool_csv.pm b/FS/FS/part_event/Action/cust_bill_spool_csv.pm index 11ecbc555..1504a4fa9 100644 --- a/FS/FS/part_event/Action/cust_bill_spool_csv.pm +++ b/FS/FS/part_event/Action/cust_bill_spool_csv.pm @@ -15,9 +15,10 @@ sub option_fields { ( 'spoolformat' => { label => 'Format', type => 'select', - options => ['default', 'billco'], + options => ['default', 'billco', 'oneline'], option_labels => { 'default' => 'Default', 'billco' => 'Billco', + 'oneline' => 'One line', }, }, 'spoolbalanceover' => { label => -- cgit v1.2.1 From 5fd66da492a1b206fa4e944eb0caa2e2642d454a Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Wed, 25 Apr 2012 11:26:36 -0700 Subject: consider districts in tax liability report, #17051 --- FS/FS/cust_main_county.pm | 2 +- FS/FS/cust_pkg.pm | 23 ++++++++----- httemplate/search/cust_bill_pkg.cgi | 6 ++-- httemplate/search/report_tax.cgi | 69 ++++++++++++++++++------------------- 4 files changed, 52 insertions(+), 48 deletions(-) diff --git a/FS/FS/cust_main_county.pm b/FS/FS/cust_main_county.pm index e937b205c..6316f239a 100644 --- a/FS/FS/cust_main_county.pm +++ b/FS/FS/cust_main_county.pm @@ -176,7 +176,7 @@ with different tax classes. sub sql_taxclass_sameregion { my $self = shift; - my $same_query = 'SELECT taxclass FROM cust_main_county '. + my $same_query = 'SELECT DISTINCT taxclass FROM cust_main_county '. ' WHERE taxnum != ? AND country = ?'; my @same_param = ( 'taxnum', 'country' ); foreach my $opt_field (qw( state county )) { diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index bee1b82fb..612ffa0f6 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -3445,7 +3445,13 @@ sub location_sql { # '?' placeholders in _location_sql_where my $x = $ornull ? 3 : 2; - my @bill_param = ( ('city')x3, ('county')x$x, ('state')x$x, 'country' ); + my @bill_param = ( + ('district')x3, + ('city')x3, + ('county')x$x, + ('state')x$x, + 'country' + ); my $main_where; my @main_param; @@ -3504,16 +3510,17 @@ sub _location_sql_where { $ornull = $ornull ? ' OR ? IS NULL ' : ''; - my $or_empty_city = " OR ( ? = '' AND $table.${prefix}city IS NULL ) "; - my $or_empty_county = " OR ( ? = '' AND $table.${prefix}county IS NULL ) "; - my $or_empty_state = " OR ( ? = '' AND $table.${prefix}state IS NULL ) "; + my $or_empty_city = " OR ( ? = '' AND $table.${prefix}city IS NULL )"; + my $or_empty_county = " OR ( ? = '' AND $table.${prefix}county IS NULL )"; + my $or_empty_state = " OR ( ? = '' AND $table.${prefix}state IS NULL )"; # ( $table.${prefix}city = ? $or_empty_city $ornull ) " - ( $table.${prefix}city = ? OR ? = '' OR CAST(? AS text) IS NULL ) - AND ( $table.${prefix}county = ? $or_empty_county $ornull ) - AND ( $table.${prefix}state = ? $or_empty_state $ornull ) - AND $table.${prefix}country = ? + ( $table.${prefix}district = ? OR ? = '' OR CAST(? AS text) IS NULL ) + AND ( $table.${prefix}city = ? OR ? = '' OR CAST(? AS text) IS NULL ) + AND ( $table.${prefix}county = ? $or_empty_county $ornull ) + AND ( $table.${prefix}state = ? $or_empty_state $ornull ) + AND $table.${prefix}country = ? "; } diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi index 94860d3f2..b08024cb0 100644 --- a/httemplate/search/cust_bill_pkg.cgi +++ b/httemplate/search/cust_bill_pkg.cgi @@ -218,7 +218,7 @@ if ( $cgi->param('taxclass') } -my @loc_param = qw( city county state country ); +my @loc_param = qw( district city county state country ); if ( $cgi->param('out') ) { @@ -266,7 +266,7 @@ if ( $cgi->param('out') ) { my %ph = ( 'county' => dbh->quote($_), map { $_ => dbh->quote( $cgi->param($_) ) } - qw( city state country ) + qw( district city state country ) ); my ( $loc_sql, @param ) = FS::cust_pkg->location_sql; @@ -330,7 +330,7 @@ if ( $cgi->param('out') ) { push @where, FS::tax_rate_location->location_sql( map { $_ => (scalar($cgi->param($_)) || '') } - qw( city county state locationtaxid ) + qw( district city county state locationtaxid ) ); } elsif ( $cgi->param('unearned_now') =~ /^(\d+)$/ ) { diff --git a/httemplate/search/report_tax.cgi b/httemplate/search/report_tax.cgi index 0cd652d83..bfbc5fe5a 100755 --- a/httemplate/search/report_tax.cgi +++ b/httemplate/search/report_tax.cgi @@ -275,7 +275,10 @@ sub gotcust { my $table = shift; my $prefix = @_ ? shift : ''; " - ( $table.${prefix}city = cust_main_county.city + ( $table.${prefix}district = cust_main_county.district + OR cust_main_county.district = '' + OR cust_main_county.district IS NULL ) + AND ( $table.${prefix}city = cust_main_county.city OR cust_main_county.city = '' OR cust_main_county.city IS NULL ) AND ( $table.${prefix}county = cust_main_county.county @@ -332,6 +335,7 @@ if ( $conf->exists('tax-pkg_address') ) { } my $out = 'Out of taxable region(s)'; +# these are actually tax labels, not regions my %regions = (); foreach my $r ( qsearch({ 'table' => 'cust_main_county', @@ -341,6 +345,7 @@ foreach my $r ( qsearch({ 'table' => 'cust_main_county', { #warn $r->county. ' '. $r->state. ' '. $r->country. "\n"; + # set up a %regions entry for this region's tax label my $label = getlabel($r); $regions{$label}->{'label'} = $label; @@ -366,6 +371,7 @@ foreach my $r ( qsearch({ 'table' => 'cust_main_county', } else { + # SQL for "taxclass doesn't match any other tax in the region" my $same_sql = $r->sql_taxclass_sameregion; $mywhere .= " AND $same_sql" if $same_sql; @@ -375,42 +381,24 @@ foreach my $r ( qsearch({ 'table' => 'cust_main_county', } + # FROM cust_bill_pkg JOIN (whatever is needed to determine tax location) + # WHERE (matches tax location and agentnum and taxclass) + # takes parameters in @base_param, plus taxclass if there is one my $fromwhere = "$from_join_cust_pkg $mywhere"; # AND payby != 'COMP' "; -# my $label = getlabel($r); -# $regions{$label}->{'label'} = $label; - my $nottax = 'pkgnum != 0'; - ## calculate total for this region + ## calculate total of sales (non-tax line items) for this region my $t_sql = "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) $fromwhere AND $nottax"; my $t = scalar_sql($r, \@param, $t_sql); $regions{$label}->{'total'} += $t; - #if ( $label eq $out ) # && $t ) { - # warn "adding $t for ". - # join('/', map $r->$_, qw( taxclass county state country ) ). "\n"; - # #warn $t_sql if $r->state eq 'FL'; - #} + #$regions{$label}->{subtotals}->{$r->taxnum} = $t; #useful debug ## calculate customer-exemption for this region -## my $taxable = $t; - -# my($taxable, $x_cust) = (0, 0); -# foreach my $e ( grep { $r->get($_.'tax') !~ /^Y/i } -# qw( cust_bill_pkg.setup cust_bill_pkg.recur ) ) { -# $taxable += scalar_sql($r, \@param, -# "SELECT SUM($e) $fromwhere AND $nottax AND ( tax != 'Y' OR tax IS NULL )" -# ); -# -# $x_cust += scalar_sql($r, \@param, -# "SELECT SUM($e) $fromwhere AND $nottax AND tax = 'Y'" -# ); -# } - #false laziness -ish w/report_tax.cgi my $cust_exempt; if ( $r->taxname ) { @@ -486,10 +474,12 @@ foreach my $r ( qsearch({ 'table' => 'cust_main_county', } else { $regions{$label}->{'rate'} = $r->tax.'%'; } - } +#warn Dumper(\%regions); +# $regions{$label} now contains 'total', 'exempt_cust', 'exempt_pkg', +# 'exempt_monthly', summed over each set of regions with the same label. -my $distinct = "country, state, county, city, +my $distinct = "country, state, county, city, district, CASE WHEN taxname IS NULL THEN '' ELSE taxname END AS taxname"; my $taxclass_distinct = #a little bit unsure of this part... test? @@ -528,11 +518,19 @@ $creditfromwhere .= ")"; $taxfromwhere .= " $taxwhere "; #AND payby != 'COMP' "; $creditfromwhere .= " $taxwhere AND billpkgtaxratelocationnum IS NULL"; #AND payby != 'COMP' "; -my @taxparam = @base_param; +#should i be a cust_main_county method or something +# yes. yes, you should. +# $taxfromwhere: Most of a query to find cust_bill_pkg records linked to a +# customer matching a given state/county/city/district (and within the date +# range for the report). +# @base_param: A list of the fields from cust_main_county to use as parameters. + +# $_taxamount_sub: Takes a cust_main_county and returns the sum of taxes billed +# within the report period for all customers located in that county. If +# the cust_main_county has a taxname, limits to taxes with that name; otherwise +# includes all line items with pkgnum = 0 and description either 'Tax' or empty. -#should i be a cust_main_county method or something -#need to pass in $taxfromwhere & @taxparam??? my $_taxamount_sub = sub { my $r = shift; @@ -545,9 +543,11 @@ my $_taxamount_sub = sub { my $sql = "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) ". " $taxfromwhere AND cust_bill_pkg.pkgnum = 0 $named_tax"; - scalar_sql($r, \@taxparam, $sql ); + scalar_sql($r, [ @base_param ], $sql ); }; +# $_creditamount_sub: As above, but returns the sum of credits applied + my $_creditamount_sub = sub { my $r = shift; @@ -560,7 +560,7 @@ my $_creditamount_sub = sub { my $sql = "SELECT SUM(cust_credit_bill_pkg.amount) ". " $creditfromwhere AND cust_bill_pkg.pkgnum = 0 $named_tax"; - scalar_sql($r, \@taxparam, $sql ); + scalar_sql($r, [ @base_param ], $sql ); }; #tax-report_groups filtering @@ -734,7 +734,8 @@ sub getlabel { my $label; if ( $r->tax == 0 - && ! scalar( qsearch('cust_main_county', { 'city' => $r->city, + && ! scalar( qsearch('cust_main_county', { 'district'=> $r->district, + 'city' => $r->city, 'county' => $r->county, 'state' => $r->state, 'country' => $r->country, @@ -747,10 +748,6 @@ sub getlabel { #kludge to avoid "will not stay shared" warning my $out = 'Out of taxable region(s)'; $label = $out; -# } elsif ( $r->taxname && count_taxname($r->taxname) == 1 ) { -# $label = $r->taxname; -## $regions{$label}->{'taxname'} = $label; -## push @{$regions{$label}->{$_}}, $r->$_() foreach qw( county state country ); } else { $label = $r->country; $label = $r->state.", $label" if $r->state; -- cgit v1.2.1 From ca75ecc26c0e37dd951621de436bd74f581aa88f Mon Sep 17 00:00:00 2001 From: Mike Robinson Date: Wed, 25 Apr 2012 16:17:50 -0500 Subject: E-mail for un-suspension. (#16144) --- FS/FS/Conf.pm | 7 +++++++ FS/FS/Conf_compat17.pm | 7 +++++++ FS/FS/cust_pkg.pm | 27 +++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 81443632c..31999bc7f 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -3951,6 +3951,13 @@ and customer address. Include units.', 'type' => 'text', }, + { + 'key' => 'unsuspend_email_admin', + 'section' => '', + 'description' => 'Destination admin email address to enable unsuspension notices', + 'type' => 'text', + }, + { 'key' => 'email_report-subject', 'section' => '', diff --git a/FS/FS/Conf_compat17.pm b/FS/FS/Conf_compat17.pm index 6685935d3..2e4bb055f 100644 --- a/FS/FS/Conf_compat17.pm +++ b/FS/FS/Conf_compat17.pm @@ -2457,6 +2457,13 @@ httemplate/docs/config.html 'type' => 'text', }, + { + 'key' => 'unsuspend_email_admin', + 'section' => '', + 'description' => 'Destination admin email address to enable unsuspension notices', + 'type' => 'text', + }, + { 'key' => 'email_report-subject', 'section' => '', diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 612ffa0f6..4359de9a4 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -1239,6 +1239,8 @@ sub unsuspend { } #if $date + my @labels = (); + foreach my $cust_svc ( qsearch('cust_svc',{'pkgnum'=> $self->pkgnum } ) ) { @@ -1258,6 +1260,8 @@ sub unsuspend { $dbh->rollback if $oldAutoCommit; return $error; } + my( $label, $value ) = $cust_svc->label; + push @labels, "$label: $value"; } } @@ -1288,6 +1292,29 @@ sub unsuspend { return $error; } + if ( $conf->config('unsuspend_email_admin') ) { + + my $error = send_email( + 'from' => $conf->config('invoice_from', $self->cust_main->agentnum), + #invoice_from ??? well as good as any + 'to' => $conf->config('unsuspend_email_admin'), + 'subject' => 'FREESIDE NOTIFICATION: Customer package unsuspended', 'body' => [ + "This is an automatic message from your Freeside installation\n", + "informing you that the following customer package has been unsuspended:\n", + "\n", + 'Customer: #'. $self->custnum. ' '. $self->cust_main->name. "\n", + 'Package : #'. $self->pkgnum. " (". $self->part_pkg->pkg_comment. ")\n", + ( map { "Service : $_\n" } @labels ), + ], + ); + + if ( $error ) { + warn "WARNING: can't send unsuspension admin email (unsuspending anyway): ". + "$error\n"; + } + + } + $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; #no errors -- cgit v1.2.1 From 62939905ff73736ea366791726e3372f59f274a9 Mon Sep 17 00:00:00 2001 From: Mike Robinson Date: Wed, 25 Apr 2012 16:59:55 -0500 Subject: Don't default 'payname' to first/last name if paying by check. (#15819) In this case, 'payname' is the name of the financial institution and there is no default. --- FS/FS/ClientAPI/MyAccount.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 7bc3011d2..e9394e4df 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -729,7 +729,7 @@ sub payment_info { $return{payinfo2} = $payinfo2; $return{paytype} = $cust_main->paytype; $return{paystate} = $cust_main->paystate; - + $return{payname} = $cust_main->payname; # override 'first/last name' default from above, if any. Is instution-name here. (#15819) } if ( $conf->config('prepayment_discounts-credit_type') ) { -- cgit v1.2.1 From 51ea2fe73308427bfbcc9e9fb717d573c82880bc Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Wed, 25 Apr 2012 16:58:15 -0700 Subject: one more event option, #16382 --- FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm b/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm index bf472683f..71bbaa89b 100644 --- a/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm +++ b/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm @@ -15,9 +15,10 @@ sub option_fields { ( 'ftpformat' => { label => 'Format', type =>'select', - options => ['default', 'billco'], + options => ['default', 'billco', 'oneline'], option_labels => { 'default' => 'Default', 'billco' => 'Billco', + 'oneline' => 'One line', }, }, 'ftpserver' => 'FTP server', -- cgit v1.2.1 From 30d17233a3e064bb84417d16fd03feb52dc11dd0 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Wed, 25 Apr 2012 17:14:57 -0700 Subject: change duration field for "cia" CDRs, #17507 --- FS/FS/cdr/cia.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/FS/FS/cdr/cia.pm b/FS/FS/cdr/cia.pm index 070f3fb0d..ca44c0fdf 100644 --- a/FS/FS/cdr/cia.pm +++ b/FS/FS/cdr/cia.pm @@ -20,11 +20,12 @@ use FS::cdr qw(_cdr_date_parser_maker); skip(2), # Conference Start Time, Conference End Time _cdr_date_parser_maker('startdate'), # Connect Time _cdr_date_parser_maker('enddate'), # Disconnect Time + skip(1), # Duration sub { my($cdr, $data, $conf, $param) = @_; $cdr->duration($data); $cdr->billsec( $data); - }, # Duration - skip(2), # Roundup Duration, User Name + }, # Roundup Duration + skip(1), # User Name 'dst', # DNIS 'src', # ANI skip(2), # Call Type, Toll Free, -- cgit v1.2.1 From 580dd345b7616365903b9fbedc162f77e5c8b91f Mon Sep 17 00:00:00 2001 From: Mike Robinson Date: Thu, 26 Apr 2012 12:21:24 -0500 Subject: pkg_dundate_age condition (#17394) --- FS/FS/part_event/Condition/pkg_dundate_age.pm | 43 +++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 FS/FS/part_event/Condition/pkg_dundate_age.pm diff --git a/FS/FS/part_event/Condition/pkg_dundate_age.pm b/FS/FS/part_event/Condition/pkg_dundate_age.pm new file mode 100644 index 000000000..2ea2a2041 --- /dev/null +++ b/FS/FS/part_event/Condition/pkg_dundate_age.pm @@ -0,0 +1,43 @@ +package FS::part_event::Condition::pkg_dundate_age; +use base qw( FS::part_event::Condition ); + +use strict; + +sub description { + "Skip until specified #days before package suspension delay date"; +} + + +sub option_fields { + ( + 'age' => { 'label' => 'Time before suspension delay date', + 'type' => 'freq', + }, + ); +} + +sub eventtable_hashref { + { 'cust_main' => 0, + 'cust_bill' => 0, + 'cust_pkg' => 1, + }; +} + +sub condition { + my($self, $cust_pkg, %opt) = @_; + + my $age = $self->option_age_from('age', $opt{'time'} ); + + $cust_pkg->dundate <= $age; +} + +sub condition_sql { + my( $class, $table, %opt ) = @_; + return 'true' unless $table eq 'cust_pkg'; + + my $age = $class->condition_sql_option_age_from('age', $opt{'time'}); + + "COALESCE($table.dundate,0) <= ". $age; +} + +1; -- cgit v1.2.1 From 527b92c4f125282e267863cb102664da57f94ebd Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Thu, 26 Apr 2012 11:41:05 -0700 Subject: allocate quick payment only when customer has multiple open invoices, #15861 --- httemplate/elements/customer-table.html | 10 +++++++++- httemplate/misc/batch-cust_pay.html | 5 ++++- httemplate/misc/xmlhttp-cust_main-search.cgi | 4 ++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/httemplate/elements/customer-table.html b/httemplate/elements/customer-table.html index b49bf0290..fc1af69c6 100644 --- a/httemplate/elements/customer-table.html +++ b/httemplate/elements/customer-table.html @@ -41,6 +41,8 @@ Example: diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html index 45459f14d..887b92489 100644 --- a/httemplate/misc/batch-cust_pay.html +++ b/httemplate/misc/batch-cust_pay.html @@ -23,7 +23,10 @@ function add_row_callback(rownum, prefix) { function custnum_update_callback(rownum, prefix) { var custnum = document.getElementById('custnum'+rownum).value; - document.getElementById('enable_app'+rownum).disabled = (custnum == 0); + document.getElementById('enable_app'+rownum).disabled = ( + custnum == 0 || + num_open_invoices[rownum] < 2 + ); % if ( $use_discounts ) { select_discount_term(rownum, prefix); % } diff --git a/httemplate/misc/xmlhttp-cust_main-search.cgi b/httemplate/misc/xmlhttp-cust_main-search.cgi index 436501e8b..86983e462 100644 --- a/httemplate/misc/xmlhttp-cust_main-search.cgi +++ b/httemplate/misc/xmlhttp-cust_main-search.cgi @@ -12,7 +12,7 @@ % my @cust_main = smart_search( 'search' => $string, % 'no_fuzzy_on_exact' => 1, #pref? % ); -% my $return = [ map [ $_->custnum, $_->name, $_->balance, $_->ucfirst_status, $_->statuscolor ], @cust_main ]; +% my $return = [ map [ $_->custnum, $_->name, $_->balance, $_->ucfirst_status, $_->statuscolor, scalar($_->open_cust_bill) ], @cust_main ]; % <% objToJson($return) %> % } elsif ( $sub eq 'invnum_search' ) { @@ -57,7 +57,7 @@ sub findbycustnum{ 'hashref' => $hashref, 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, }); - return [ $c->custnum, $c->name, $c->balance, $c->ucfirst_status, $c->statuscolor ] + return [ $c->custnum, $c->name, $c->balance, $c->ucfirst_status, $c->statuscolor, scalar($c->open_cust_bill) ] if $c; []; } -- cgit v1.2.1 From 3775fafeb19d7dab5f7104e03685ec8dc0d3a8f7 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Thu, 26 Apr 2012 12:21:53 -0700 Subject: debug --- httemplate/misc/process/batch-cust_pay.cgi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httemplate/misc/process/batch-cust_pay.cgi b/httemplate/misc/process/batch-cust_pay.cgi index 3b06f3ab7..1105af943 100644 --- a/httemplate/misc/process/batch-cust_pay.cgi +++ b/httemplate/misc/process/batch-cust_pay.cgi @@ -1,5 +1,5 @@ <%init> -my $DEBUG = 1; +my $DEBUG = 0; die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Post payment batch'); -- cgit v1.2.1 From 88269174c839d6953f30e2f849bc60327dd10f07 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Thu, 26 Apr 2012 13:25:50 -0700 Subject: better duplicate checking for ip address, #17515 --- FS/FS/Schema.pm | 2 +- FS/FS/svc_broadband.pm | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 3894f65f8..d42b946f2 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -2556,7 +2556,7 @@ sub tables_hashref { 'plan_id', 'varchar', 'NULL', $char_d, '', '', ], 'primary_key' => 'svcnum', - 'unique' => [ [ 'mac_addr' ] ], + 'unique' => [ [ 'ip_addr' ], [ 'mac_addr' ] ], 'index' => [], }, diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index 64cc3770e..82102697d 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -543,9 +543,9 @@ sub _check_ip_addr { sub _check_duplicate { my $self = shift; - - $self->lock_table; - + # Not a reliable check because the table isn't locked, but + # that's why we have a unique index. This is just to give a + # friendlier error message. my @dup; @dup = $self->find_duplicates('global', 'ip_addr'); if ( @dup ) { -- cgit v1.2.1 From 6ff1c755b054201c38b0a2a7b6161325af5c0bcf Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Thu, 26 Apr 2012 17:09:53 -0700 Subject: site id in cust_location labels, #16815 --- FS/FS/Conf.pm | 18 ++++++++++++++++++ FS/FS/cust_location.pm | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 31999bc7f..82e5ea89c 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -3051,6 +3051,24 @@ and customer address. Include units.', 'select_hash' => [ FS::ConfDefaults->cust_fields_avail() ], }, + { + 'key' => 'cust_location-label_prefix', + 'section' => 'UI', + 'description' => 'Optional "site ID" to show in the location label', + 'type' => 'select', + 'select_hash' => [ '' => '', + 'CoStAg' => 'CoStAgXXXXX (country, state, agent name, locationnum)', + ], + }, + + { + 'key' => 'cust_location-agent_code', + 'section' => 'UI', + 'description' => 'Optional agent string for cust_location-label_prefix', + 'type' => 'text', + 'per_agent' => 1, + }, + { 'key' => 'cust_pkg-display_times', 'section' => 'UI', diff --git a/FS/FS/cust_location.pm b/FS/FS/cust_location.pm index a5250ec05..a99fa17d8 100644 --- a/FS/FS/cust_location.pm +++ b/FS/FS/cust_location.pm @@ -408,6 +408,42 @@ sub dealternize { ''; } +=item location_label + +Returns the label of the location object, with an optional site ID +string (based on the cust_location-label_prefix config option). + +=cut + +sub location_label { + my $self = shift; + my %opt = @_; + my $conf = new FS::Conf; + my $prefix = ''; + my $format = $conf->config('cust_location-label_prefix') || ''; + if ( $format eq 'CoStAg' ) { + my $cust_or_prospect; + if ( $self->custnum ) { + $cust_or_prospect = FS::cust_main->by_key($self->custnum); + } + elsif ( $self->prospectnum ) { + $cust_or_prospect = FS::prospect_main->by_key($self->prospectnum); + } + my $agent = $conf->config('cust_location-agent_code', + $cust_or_prospect->agentnum) + || $cust_or_prospect->agent->agent; + # else this location is invalid + $prefix = uc( join('', + $self->country, + ($self->state =~ /^(..)/), + ($agent =~ /^(..)/), + sprintf('%05d', $self->locationnum) + ) ); + } + $prefix .= ($opt{join_string} || ': ') if $prefix; + $prefix . $self->SUPER::location_label(%opt); +} + =back =head1 BUGS -- cgit v1.2.1