From: ivan Date: Thu, 18 Mar 2010 07:59:52 +0000 (+0000) Subject: disable auto-billing of specific customer packages, RT#6378 X-Git-Tag: root_of_svc_elec_features~392 X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=e078ca418dcf3c7b92efcd371ce761df3314c369 disable auto-billing of specific customer packages, RT#6378 --- diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 2cfe4b4be..ecb3a749d 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -1259,6 +1259,7 @@ sub tables_hashref { 'change_pkgpart', 'int', 'NULL', '', '', '', 'change_locationnum', 'int', 'NULL', '', '', '', 'manual_flag', 'char', 'NULL', 1, '', '', + 'no_auto', 'char', 'NULL', 1, '', '', 'quantity', 'int', 'NULL', '', '', '', ], 'primary_key' => 'pkgnum', @@ -1435,7 +1436,7 @@ sub tables_hashref { 'pay_weight', 'real', 'NULL', '', '', '', 'credit_weight', 'real', 'NULL', '', '', '', 'agentnum', 'int', 'NULL', '', '', '', - + 'no_auto', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'pkgpart', 'unique' => [], diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 28a7257cd..b2cad50ad 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -356,11 +356,24 @@ this invoice. sub cust_pkg { my $self = shift; - my @cust_pkg = map { $_->cust_pkg } $self->cust_bill_pkg; + my @cust_pkg = map { $_->pkgnum > 0 ? $_->cust_pkg : () } + $self->cust_bill_pkg; my %saw = (); grep { ! $saw{$_->pkgnum}++ } @cust_pkg; } +=item no_auto + +Returns true if any of the packages (or their definitions) corresponding to the +line items for this invoice have the no_auto flag set. + +=cut + +sub no_auto { + my $self = shift; + grep { $_->no_auto || $_->part_pkg->no_auto } $self->cust_pkg; +} + =item open_cust_bill_pkg Returns the open line items for this invoice. @@ -1899,6 +1912,14 @@ sub realtime_bop { $cust_main->realtime_bop($method, $amount, 'description' => $description, 'invnum' => $self->invnum, +#this didn't do what we want, it just calls apply_payments_and_credits +# 'apply' => 1, + 'apply_to_invoice' => 1, + #what we want: + #this changes application behavior: auto payments + #triggered against a specific invoice are now applied + #to that invoice instead of oldest open. + #seem okay to me... ); } diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 6c2bcf377..2574ca7df 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -2696,15 +2696,21 @@ sub bill { return $error; } - my @cust_bill_pkg = (); + #keep auto-charge and non-auto-charge line items separate + my @passes = ( '', 'no_auto' ); + + my %cust_bill_pkg = map { $_ => [] } @passes; ### # find the packages which are due for billing, find out how much they are # & generate invoice database. ### - my( $total_setup, $total_recur, $postal_charge ) = ( 0, 0, 0 ); - my %taxlisthash; + my %total_setup = map { my $z = 0; $_ => \$z; } @passes; + my %total_recur = map { my $z = 0; $_ => \$z; } @passes; + + my %taxlisthash = map { $_ => {} } @passes; + my @precommit_hooks = (); $options{'pkg_list'} ||= [ $self->ncancelled_pkgs ]; #param checks? @@ -2727,14 +2733,16 @@ sub bill { $cust_pkg->set($_, $hash{$_}) foreach qw ( setup last_bill bill ); + my $pass = ($cust_pkg->no_auto || $part_pkg->no_auto) ? 'no_auto' : ''; + my $error = $self->_make_lines( 'part_pkg' => $part_pkg, 'cust_pkg' => $cust_pkg, 'precommit_hooks' => \@precommit_hooks, - 'line_items' => \@cust_bill_pkg, - 'setup' => \$total_setup, - 'recur' => \$total_recur, - 'tax_matrix' => \%taxlisthash, + 'line_items' => $cust_bill_pkg{$pass}, + 'setup' => $total_setup{$pass}, + 'recur' => $total_recur{$pass}, + 'tax_matrix' => $taxlisthash{$pass}, 'time' => $time, 'real_pkgpart' => $real_pkgpart, 'options' => \%options, @@ -2748,130 +2756,138 @@ sub bill { } #foreach my $cust_pkg - unless ( @cust_bill_pkg ) { #don't create an invoice w/o line items - #but do commit any package date cycling that happened - $dbh->commit or die $dbh->errstr if $oldAutoCommit; - return ''; - } + #if the customer isn't on an automatic payby, everything can go on a single + #invoice anyway? + #if ( $cust_main->payby !~ /^(CARD|CHEK)$/ ) { + #merge everything into one list + #} - if ( scalar( grep { $_->recur && $_->recur > 0 } @cust_bill_pkg) || - !$conf->exists('postal_invoice-recurring_only') - ) - { + foreach my $pass (@passes) { # keys %cust_bill_pkg ) { - my $postal_pkg = $self->charge_postal_fee(); - if ( $postal_pkg && !ref( $postal_pkg ) ) { + my @cust_bill_pkg = @{ $cust_bill_pkg{$pass} }; - $dbh->rollback if $oldAutoCommit; - return "can't charge postal invoice fee for customer ". - $self->custnum. ": $postal_pkg"; - - } elsif ( $postal_pkg ) { - - my $real_pkgpart = $postal_pkg->pkgpart; - foreach my $part_pkg ( $postal_pkg->part_pkg->self_and_bill_linked ) { - my %postal_options = %options; - delete $postal_options{cancel}; - my $error = - $self->_make_lines( 'part_pkg' => $part_pkg, - 'cust_pkg' => $postal_pkg, - 'precommit_hooks' => \@precommit_hooks, - 'line_items' => \@cust_bill_pkg, - 'setup' => \$total_setup, - 'recur' => \$total_recur, - 'tax_matrix' => \%taxlisthash, - 'time' => $time, - 'real_pkgpart' => $real_pkgpart, - 'options' => \%postal_options, - ); - if ($error) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - } + next unless @cust_bill_pkg; #don't create an invoice w/o line items - } + if ( scalar( grep { $_->recur && $_->recur > 0 } @cust_bill_pkg) || + !$conf->exists('postal_invoice-recurring_only') + ) + { - } + my $postal_pkg = $self->charge_postal_fee(); + if ( $postal_pkg && !ref( $postal_pkg ) ) { - my $listref_or_error = - $self->calculate_taxes( \@cust_bill_pkg, \%taxlisthash, $invoice_time); + $dbh->rollback if $oldAutoCommit; + return "can't charge postal invoice fee for customer ". + $self->custnum. ": $postal_pkg"; + + } elsif ( $postal_pkg ) { + + my $real_pkgpart = $postal_pkg->pkgpart; + foreach my $part_pkg ( $postal_pkg->part_pkg->self_and_bill_linked ) { + my %postal_options = %options; + delete $postal_options{cancel}; + my $error = + $self->_make_lines( 'part_pkg' => $part_pkg, + 'cust_pkg' => $postal_pkg, + 'precommit_hooks' => \@precommit_hooks, + 'line_items' => \@cust_bill_pkg, + 'setup' => $total_setup{$pass}, + 'recur' => $total_recur{$pass}, + 'tax_matrix' => $taxlisthash{$pass}, + 'time' => $time, + 'real_pkgpart' => $real_pkgpart, + 'options' => \%postal_options, + ); + if ($error) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } - unless ( ref( $listref_or_error ) ) { - $dbh->rollback if $oldAutoCommit; - return $listref_or_error; - } + } - foreach my $taxline ( @$listref_or_error ) { - $total_setup = sprintf('%.2f', $total_setup+$taxline->setup ); - push @cust_bill_pkg, $taxline; - } + } - #add tax adjustments - warn "adding tax adjustments...\n" if $DEBUG > 2; - foreach my $cust_tax_adjustment ( - qsearch('cust_tax_adjustment', { 'custnum' => $self->custnum, - 'billpkgnum' => '', - } - ) - ) { + my $listref_or_error = + $self->calculate_taxes( \@cust_bill_pkg, $taxlisthash{$pass}, $invoice_time); - my $tax = sprintf('%.2f', $cust_tax_adjustment->amount ); - - my $itemdesc = $cust_tax_adjustment->taxname; - $itemdesc = '' if $itemdesc eq 'Tax'; - - push @cust_bill_pkg, new FS::cust_bill_pkg { - 'pkgnum' => 0, - 'setup' => $tax, - 'recur' => 0, - 'sdate' => '', - 'edate' => '', - 'itemdesc' => $itemdesc, - 'itemcomment' => $cust_tax_adjustment->comment, - 'cust_tax_adjustment' => $cust_tax_adjustment, - #'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location, - }; + unless ( ref( $listref_or_error ) ) { + $dbh->rollback if $oldAutoCommit; + return $listref_or_error; + } - } + foreach my $taxline ( @$listref_or_error ) { + ${ $total_setup{$pass} } = + sprintf('%.2f', ${ $total_setup{$pass} } + $taxline->setup ); + push @cust_bill_pkg, $taxline; + } - my $charged = sprintf('%.2f', $total_setup + $total_recur ); + #add tax adjustments + warn "adding tax adjustments...\n" if $DEBUG > 2; + foreach my $cust_tax_adjustment ( + qsearch('cust_tax_adjustment', { 'custnum' => $self->custnum, + 'billpkgnum' => '', + } + ) + ) { - my @cust_bill = $self->cust_bill; - my $balance = $self->balance; - my $previous_balance = scalar(@cust_bill) - ? ( $cust_bill[$#cust_bill]->billing_balance || 0 ) - : 0; + my $tax = sprintf('%.2f', $cust_tax_adjustment->amount ); + + my $itemdesc = $cust_tax_adjustment->taxname; + $itemdesc = '' if $itemdesc eq 'Tax'; + + push @cust_bill_pkg, new FS::cust_bill_pkg { + 'pkgnum' => 0, + 'setup' => $tax, + 'recur' => 0, + 'sdate' => '', + 'edate' => '', + 'itemdesc' => $itemdesc, + 'itemcomment' => $cust_tax_adjustment->comment, + 'cust_tax_adjustment' => $cust_tax_adjustment, + #'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location, + }; - $previous_balance += $cust_bill[$#cust_bill]->charged - if scalar(@cust_bill); - #my $balance_adjustments = - # sprintf('%.2f', $balance - $prior_prior_balance - $prior_charged); + } - #create the new invoice - my $cust_bill = new FS::cust_bill ( { - 'custnum' => $self->custnum, - '_date' => ( $invoice_time ), - 'charged' => $charged, - 'billing_balance' => $balance, - 'previous_balance' => $previous_balance, - 'invoice_terms' => $options{'invoice_terms'}, - } ); - $error = $cust_bill->insert; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "can't create invoice for customer #". $self->custnum. ": $error"; - } + my $charged = sprintf('%.2f', ${ $total_setup{$pass} } + ${ $total_recur{$pass} } ); - foreach my $cust_bill_pkg ( @cust_bill_pkg ) { - $cust_bill_pkg->invnum($cust_bill->invnum); - my $error = $cust_bill_pkg->insert; + my @cust_bill = $self->cust_bill; + my $balance = $self->balance; + my $previous_balance = scalar(@cust_bill) + ? ( $cust_bill[$#cust_bill]->billing_balance || 0 ) + : 0; + + $previous_balance += $cust_bill[$#cust_bill]->charged + if scalar(@cust_bill); + #my $balance_adjustments = + # sprintf('%.2f', $balance - $prior_prior_balance - $prior_charged); + + #create the new invoice + my $cust_bill = new FS::cust_bill ( { + 'custnum' => $self->custnum, + '_date' => ( $invoice_time ), + 'charged' => $charged, + 'billing_balance' => $balance, + 'previous_balance' => $previous_balance, + 'invoice_terms' => $options{'invoice_terms'}, + } ); + $error = $cust_bill->insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "can't create invoice line item: $error"; + return "can't create invoice for customer #". $self->custnum. ": $error"; } - } - + + foreach my $cust_bill_pkg ( @cust_bill_pkg ) { + $cust_bill_pkg->invnum($cust_bill->invnum); + my $error = $cust_bill_pkg->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't create invoice line item: $error"; + } + } + + } #foreach my $pass ( keys %cust_bill_pkg ) foreach my $hook ( @precommit_hooks ) { eval { @@ -7549,12 +7565,14 @@ sub charge { my ( $pkg, $comment, $additional ); my ( $setuptax, $taxclass ); #internal taxes my ( $taxproduct, $override ); #vendor (CCH) taxes + my $no_auto = ''; my $cust_pkg_ref = ''; my ( $bill_now, $invoice_terms ) = ( 0, '' ); if ( ref( $_[0] ) ) { $amount = $_[0]->{amount}; $quantity = exists($_[0]->{quantity}) ? $_[0]->{quantity} : 1; $start_date = exists($_[0]->{start_date}) ? $_[0]->{start_date} : ''; + $no_auto = exists($_[0]->{no_auto}) ? $_[0]->{no_auto} : ''; $pkg = exists($_[0]->{pkg}) ? $_[0]->{pkg} : 'One-time charge'; $comment = exists($_[0]->{comment}) ? $_[0]->{comment} : '$'. sprintf("%.2f",$amount); @@ -7632,6 +7650,7 @@ sub charge { 'pkgpart' => $pkgpart, 'quantity' => $quantity, 'start_date' => $start_date, + 'no_auto' => $no_auto, } ); $error = $cust_pkg->insert; diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 6e9cae017..0094135ec 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -525,6 +525,7 @@ sub check { || $self->ut_numbern('cancel') || $self->ut_numbern('adjourn') || $self->ut_numbern('expire') + || $self->ut_enum('no_auto', [ '', 'Y' ]) ; return $error if $error; diff --git a/FS/FS/part_event/Condition/cust_bill_has_noauto.pm b/FS/FS/part_event/Condition/cust_bill_has_noauto.pm new file mode 100644 index 000000000..6cb94c03b --- /dev/null +++ b/FS/FS/part_event/Condition/cust_bill_has_noauto.pm @@ -0,0 +1,33 @@ +package FS::part_event::Condition::cust_bill_has_noauto; + +use strict; +use FS::cust_bill; + +use base qw( FS::part_event::Condition ); + +sub description { + 'Invoice ineligible for automatic collection'; +} + +sub eventtable_hashref { + { 'cust_main' => 0, + 'cust_bill' => 1, + 'cust_pkg' => 0, + }; +} + +sub condition { + #my($self, $cust_bill, %opt) = @_; + my($self, $cust_bill) = @_; + + $cust_bill->no_auto; +} + +#sub condition_sql { +# my( $class, $table ) = @_; +# +# my $sql = qq| |; +# return $sql; +#} + +1; diff --git a/FS/FS/part_event/Condition/cust_bill_hasnt_noauto.pm b/FS/FS/part_event/Condition/cust_bill_hasnt_noauto.pm new file mode 100644 index 000000000..78a6d51d4 --- /dev/null +++ b/FS/FS/part_event/Condition/cust_bill_hasnt_noauto.pm @@ -0,0 +1,33 @@ +package FS::part_event::Condition::cust_bill_hasnt_noauto; + +use strict; +use FS::cust_bill; + +use base qw( FS::part_event::Condition ); + +sub description { + 'Invoice eligible for automatic collection'; +} + +sub eventtable_hashref { + { 'cust_main' => 0, + 'cust_bill' => 1, + 'cust_pkg' => 0, + }; +} + +sub condition { + #my($self, $cust_bill, %opt) = @_; + my($self, $cust_bill) = @_; + + ! $cust_bill->no_auto; +} + +#sub condition_sql { +# my( $class, $table ) = @_; +# +# my $sql = qq| |; +# return $sql; +#} + +1; diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index 61bfc4dc0..08c9b87f9 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -462,6 +462,7 @@ sub check { || $self->ut_textn('taxclass') || $self->ut_enum('disabled', [ '', 'Y' ] ) || $self->ut_enum('custom', [ '', 'Y' ] ) + || $self->ut_enum('no_auto', [ '', 'Y' ]) #|| $self->ut_moneyn('setup_cost') #|| $self->ut_moneyn('recur_cost') || $self->ut_floatn('setup_cost') @@ -908,9 +909,11 @@ sub options { map { $_->optionname => $_->optionvalue } $self->part_pkg_option; } -=item option OPTIONNAME +=item option OPTIONNAME [ QUIET ] -Returns the option value for the given name, or the empty string. +Returns the option value for the given name, or the empty string. If a true +value is passed as the second argument, warnings about missing the option +will be suppressed. =cut diff --git a/httemplate/edit/process/quick-charge.cgi b/httemplate/edit/process/quick-charge.cgi index 827530e50..27921b8c3 100644 --- a/httemplate/edit/process/quick-charge.cgi +++ b/httemplate/edit/process/quick-charge.cgi @@ -61,6 +61,7 @@ unless ( $error ) { ? str2time($cgi->param('start_date')) : '' ), + 'no_auto' => scalar($cgi->param('no_auto')), 'pkg' => scalar($cgi->param('pkg')), 'setuptax' => scalar($cgi->param('setuptax')), 'taxclass' => scalar($cgi->param('taxclass')), diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi index a0958922e..202d0a3cf 100644 --- a/httemplate/edit/process/quick-cust_pkg.cgi +++ b/httemplate/edit/process/quick-cust_pkg.cgi @@ -57,6 +57,7 @@ my $cust_pkg = new FS::cust_pkg { ? str2time($cgi->param('start_date')) : '' ), + 'no_auto' => scalar($cgi->param('no_auto')), 'refnum' => $refnum, 'locationnum' => $locationnum, 'discountnum' => $discountnum, diff --git a/httemplate/edit/quick-charge.html b/httemplate/edit/quick-charge.html index 64ad3a289..a472915a1 100644 --- a/httemplate/edit/quick-charge.html +++ b/httemplate/edit/quick-charge.html @@ -153,6 +153,13 @@ function bill_now_changed (what) { }); +% if ( $cust_main->payby =~ /^(CARD|CHEK)$/ ) { +% my $what = lc(FS::payby->shortname($cust_main->payby)); + + Disable automatic <% $what %> charge + + +% } Tax exempt diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html index 666099a02..33b2bb390 100644 --- a/httemplate/misc/order_pkg.html +++ b/httemplate/misc/order_pkg.html @@ -66,6 +66,14 @@ }); +% if ( $cust_main->payby =~ /^(CARD|CHEK)$/ ) { +% my $what = lc(FS::payby->shortname($cust_main->payby)); + + Disable automatic <% $what %> charge + + +% } + % if ( $curuser->access_right('Discount customer package') ) { <% include('/elements/tr-select-discount.html', 'element_etc' => 'DISABLED', @@ -119,7 +127,7 @@ my $cust_main = qsearchs({ my $pkgpart = scalar($cgi->param('pkgpart')); -my $format = "%m/%d/%Y %T %z (%Z)"; #false laziness w/REAL_cust_pkg.cgi? +my $format = $date_format. ' %T %z (%Z)'; #false laziness w/REAL_cust_pkg.cgi? my $start_date = $cust_main->next_bill_date; $start_date = $start_date ? time2str($format, $start_date) : ''; diff --git a/httemplate/view/cust_main/one_time_charge_link.html b/httemplate/view/cust_main/one_time_charge_link.html index 4ce8a28a3..b3defa294 100644 --- a/httemplate/view/cust_main/one_time_charge_link.html +++ b/httemplate/view/cust_main/one_time_charge_link.html @@ -80,7 +80,7 @@ function taxoverridequickchargemagic() { 'actionlabel' => 'One-time charge', 'color' => '#333399', 'width' => 763, - 'height' => 408, + 'height' => 460, #more for more room for lines of add'l description? }) %> diff --git a/httemplate/view/cust_main/order_pkg_link.html b/httemplate/view/cust_main/order_pkg_link.html index 0c53f2a59..30c86a757 100644 --- a/httemplate/view/cust_main/order_pkg_link.html +++ b/httemplate/view/cust_main/order_pkg_link.html @@ -6,7 +6,7 @@ 'cust_main' => $cust_main, 'closetext' => 'Close', 'width' => 763, - 'height' => 476, + 'height' => 538, ) %> <%init> diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html index 7b1b53add..a6868434b 100644 --- a/httemplate/view/cust_main/packages/status.html +++ b/httemplate/view/cust_main/packages/status.html @@ -42,6 +42,8 @@ ) %> + <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> % unless ( $cust_pkg->get('setup') ) { @@ -76,7 +78,9 @@ <% pkg_status_row_colspan( $cust_pkg, 'Not yet billed (one-time charge)', '', 'colspan'=>$colspan, %opt ) %> - <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + + <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> <% pkg_status_row_if( $cust_pkg, @@ -100,6 +104,8 @@ <% pkg_status_row_colspan($cust_pkg, "Not yet billed ($billed_or_prepaid ". myfreq($part_pkg). ')', '', 'colspan'=>$colspan, %opt ) %> + <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> <% pkg_status_row_if($cust_pkg, 'Start billing', 'start_date', %opt) %> @@ -114,6 +120,8 @@ <% pkg_status_row($cust_pkg, 'Billed', 'setup', %opt) %> + <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> % } else { @@ -138,6 +146,8 @@ %> % } + <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> <% pkg_status_row($cust_pkg, 'Setup', 'setup', %opt) %> @@ -290,6 +300,23 @@ sub pkg_status_row_changed { $html; } +sub pkg_status_row_noauto { + my( $cust_pkg, %opt ) = @_; + my $part_pkg = $opt{'part_pkg'}; + return '' unless $cust_pkg->no_auto || $part_pkg->no_auto; + + #inefficient, should be passed in + my $cust_main = $cust_pkg->cust_main; + + return '' unless $cust_main->payby =~ /^(CARD|CHEK)$/; + my $what = lc(FS::payby->shortname($cust_main->payby)); + + pkg_status_row_colspan( $cust_pkg, "No automatic $what charge", '', + 'colspan' => $opt{'colspan'}, + #%opt, + ); +} + sub pkg_status_row_discount { my( $cust_pkg, %opt ) = @_;