X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_pkg.pm;h=c1039b59f70992cb2dc3bec57451dbf63c06409e;hb=399013698f9ba6ff2c9861a839f424da9609825e;hp=467b54d718a92f1a5ae4ba58b7f6409df71025a3;hpb=6d2db824c90c29789852fb1a7645400c90032091;p=freeside.git diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 467b54d71..c1039b59f 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -1792,6 +1792,8 @@ sub credit_remaining { # the cancellation date (can happen with advance billing). in that # case, use the entire recurring charge: my $amount = $cust_bill_pkg->recur - $cust_bill_pkg->usage; + my $max_credit = $amount + - $cust_bill_pkg->credited('', '', setuprecur => 'recur') || 0; # but if the cancellation happens during the interval, prorate it: # (XXX obey prorate_round_day here?) @@ -1800,14 +1802,23 @@ sub credit_remaining { ($edate - $time) / ($edate - $cust_bill_pkg->sdate); } + # if there are existing credits, don't let the sum of credits exceed + # the recurring charge + $amount = $max_credit if $amount > $max_credit; + $amount = sprintf('%.2f', $amount); - push @billpkgnums, $cust_bill_pkg->billpkgnum; - push @amounts, $amount; - push @setuprecurs, 'recur'; + # if no time has been used and/or there are existing line item + # credits, we may end up not needing to credit anything. + if ( $amount > 0 ) { - warn "Crediting for $amount on package ".$remain_pkg->pkgnum."\n" - if $DEBUG; + push @billpkgnums, $cust_bill_pkg->billpkgnum; + push @amounts, $amount; + push @setuprecurs, 'recur'; + + warn "Crediting for $amount on package ".$remain_pkg->pkgnum."\n" + if $DEBUG; + } } @@ -2962,7 +2973,7 @@ sub modify_charge { $pkg_opt_modified = 1; } } - $pkg_opt_modified = 1 if (scalar(@old_additional) - 1) != $i; + $pkg_opt_modified = 1 if scalar(@old_additional) != $i; $pkg_opt{'additional_count'} = $i if $i > 0; my $old_classnum; @@ -3115,8 +3126,6 @@ sub modify_charge { ''; } - - use Storable 'thaw'; use MIME::Base64; use Data::Dumper; @@ -5031,6 +5040,17 @@ sub cancel_sql { "cust_pkg.cancel IS NOT NULL AND cust_pkg.cancel != 0"; } +=item ncancelled_recurring_sql + +Returns an SQL expression identifying un-cancelled, recurring packages. + +=cut + +sub ncancelled_recurring_sql { + $_[0]->recurring_sql(). + " AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) "; +} + =item status_sql Returns an SQL expression to give the package status as a string. @@ -5513,29 +5533,38 @@ sub search { foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel )) { - next unless exists($params->{$field}); + if ( $params->{$field.'_null'} ) { - my($beginning, $ending) = @{$params->{$field}}; + push @where, "cust_pkg.$field IS NULL"; + # this should surely be obsoleted by now: OR cust_pkg.$field == 0 - next if $beginning == 0 && $ending == 4294967295; + } else { - push @where, - "cust_pkg.$field IS NOT NULL", - "cust_pkg.$field >= $beginning", - "cust_pkg.$field <= $ending"; - - $orderby ||= "ORDER BY cust_pkg.$field"; - - if ( $field eq 'setup' ) { - $exclude_change_from = 1; - } elsif ( $field eq 'cancel' ) { - $exclude_change_to = 1; - } elsif ( $field eq 'change_date' ) { - # if we are given setup and change_date ranges, and the setup date - # falls in _both_ ranges, then include the package whether it was - # a change or not - $exclude_change_from = 0; + next unless exists($params->{$field}); + + my($beginning, $ending) = @{$params->{$field}}; + + next if $beginning == 0 && $ending == 4294967295; + + push @where, + "cust_pkg.$field IS NOT NULL", + "cust_pkg.$field >= $beginning", + "cust_pkg.$field <= $ending"; + + $orderby ||= "ORDER BY cust_pkg.$field"; + + if ( $field eq 'setup' ) { + $exclude_change_from = 1; + } elsif ( $field eq 'cancel' ) { + $exclude_change_to = 1; + } elsif ( $field eq 'change_date' ) { + # if we are given setup and change_date ranges, and the setup date + # falls in _both_ ranges, then include the package whether it was + # a change or not + $exclude_change_from = 0; + } } + } if ($exclude_change_from) { @@ -5548,6 +5577,7 @@ sub search { WHERE cust_pkg.pkgnum = changed_to_pkg.change_pkgnum )"; } + } $orderby ||= 'ORDER BY bill'; @@ -6187,6 +6217,32 @@ sub _upgrade_data { # class method my $error = $part_pkg_link->remove_linked; die $error if $error; } + + # RT#73607: canceling a package with billing addons sometimes changes its + # pkgpart. + # Find records where the last replace_new record for the package before it + # was canceled has a different pkgpart from the package itself. + my @cust_pkg = qsearch({ + 'table' => 'cust_pkg', + 'select' => 'cust_pkg.*, h_cust_pkg.pkgpart AS h_pkgpart', + 'addl_from' => ' JOIN ( + SELECT pkgnum, MAX(historynum) AS historynum FROM h_cust_pkg + WHERE cancel IS NULL + AND history_action = \'replace_new\' + GROUP BY pkgnum + ) AS last_history USING (pkgnum) + JOIN h_cust_pkg USING (historynum)', + 'extra_sql' => ' WHERE cust_pkg.cancel is not null + AND cust_pkg.pkgpart != h_cust_pkg.pkgpart' + }); + foreach my $cust_pkg ( @cust_pkg ) { + my $pkgnum = $cust_pkg->pkgnum; + warn "fixing pkgpart on canceled pkg#$pkgnum\n"; + $cust_pkg->set('pkgpart', $cust_pkg->h_pkgpart); + my $error = $cust_pkg->replace; + die $error if $error; + } + } # will autoload in v4+