From 337323718878fdfe98801193b0dbecffdc8dffc8 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 5 Nov 2010 22:58:50 +0000 Subject: [PATCH] split discount bs out into its own file before cust_main/Billing.pm becomes as bad as the old monolithic cust_main.pm, somehow RT#10462 --- FS/FS/cust_main.pm | 1 + FS/FS/cust_main/Billing.pm | 173 +----------------------------- FS/FS/cust_main/Billing_Discount.pm | 207 ++++++++++++++++++++++++++++++++++++ FS/MANIFEST | 1 + 4 files changed, 213 insertions(+), 169 deletions(-) create mode 100644 FS/FS/cust_main/Billing_Discount.pm diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 796996554..03e8cc647 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -5,6 +5,7 @@ use strict; #FS::cust_main:_Marketgear when they're ready to move to 2.1 use base qw( FS::cust_main::Packages FS::cust_main::Billing FS::cust_main::Billing_Realtime + FS::cust_main::Billing_Discount FS::otaker_Mixin FS::payinfo_Mixin FS::cust_main_Mixin FS::geocode_Mixin FS::Record diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm index e00478836..536a93860 100644 --- a/FS/FS/cust_main/Billing.pm +++ b/FS/FS/cust_main/Billing.pm @@ -12,7 +12,6 @@ use FS::cust_bill_pkg; use FS::cust_bill_pkg_display; use FS::cust_bill_pay; use FS::cust_credit_bill; -use FS::cust_pkg; use FS::cust_tax_adjustment; use FS::tax_rate; use FS::tax_rate_location; @@ -20,6 +19,7 @@ use FS::cust_bill_pkg_tax_location; use FS::cust_bill_pkg_tax_rate_location; use FS::part_event; use FS::part_event_condition; +use FS::pkg_category; # 1 is mostly method/subroutine entry and options # 2 traces progress of some operations @@ -897,6 +897,7 @@ sub _make_lines { # There may be some part_pkg for which this is wrong. Only those # which can_discount are supported. + # (the UI should prevent adding discounts to these at the moment) $recur = eval { $cust_pkg->$method( \$sdate, \@details, \%param ) }; return "$@ running $method for $cust_pkg\n" @@ -1626,174 +1627,6 @@ Explicitly pass the objects to be tested (typically used with eventtable). Set to true to return the objects, but not actually insert them into the database. -=item discount_terms - -Returns a list of lengths for term discounts - -=cut - -sub _discount_pkgs_and_bill { -my $self = shift; - - my @cust_bill = $self->cust_bill; - my $cust_bill = pop @cust_bill; - return () unless $cust_bill && $cust_bill->owed; - - my @where = (); - push @where, "cust_bill_pkg.invnum = ". $cust_bill->invnum; - push @where, "cust_bill_pkg.pkgpart_override IS NULL"; - push @where, "part_pkg.freq = '1'"; - push @where, "(cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0)"; - push @where, "(cust_pkg.susp IS NULL OR cust_pkg.susp = 0)"; - push @where, "0<(SELECT count(*) FROM part_pkg_discount - WHERE part_pkg.pkgpart = part_pkg_discount.pkgpart)"; - push @where, - "0=(SELECT count(*) FROM cust_bill_pkg_discount - WHERE cust_bill_pkg.billpkgnum = cust_bill_pkg_discount.billpkgnum)"; - - my $extra_sql = 'WHERE '. join(' AND ', @where); - - my @cust_pkg = - qsearch({ - 'table' => 'cust_pkg', - 'select' => "DISTINCT cust_pkg.*", - 'addl_from' => 'JOIN cust_bill_pkg USING(pkgnum) '. - 'JOIN part_pkg USING(pkgpart)', - 'hashref' => {}, - 'extra_sql' => $extra_sql, - }); - - ($cust_bill, @cust_pkg); -} - -sub _discountable_pkgs_at_term { - my ($term, @pkgs) = @_; - my $part_pkg = new FS::part_pkg { freq => $term - 1 }; - grep { ( !$_->adjourn || $_->adjourn > $part_pkg->add_freq($_->bill) ) && - ( !$_->expire || $_->expire > $part_pkg->add_freq($_->bill) ) - } - @pkgs; -} - -=item discount_terms - -Returns a list of lengths for term discounts - -=cut - -sub discount_terms { -my $self = shift; - - my %terms = (); - - my @discount_pkgs = $self->_discount_pkgs_and_bill; - shift @discount_pkgs; #discard bill; - - map { $terms{$_->months} = 1 } - grep { $_->months && $_->months > 1 } - map { $_->discount } - map { $_->part_pkg->part_pkg_discount } - @discount_pkgs; - - return sort { $a <=> $b } keys %terms; - -} - -=back - -=item discount_term_values MONTHS - -Returns a list with credit, dollar amount saved, and total bill acheived -by prepaying the most recent invoice for MONTHS. - -=cut - -sub discount_term_values { - my $self = shift; - my $term = shift; - - local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; - - warn "$me discount_term_values called with $term\n" if $DEBUG; - - my %result = (); - - my @packages = $self->_discount_pkgs_and_bill; - my $cust_bill = shift(@packages); - @packages = _discountable_pkgs_at_term( $term, @packages ); - return () unless scalar(@packages); - - $_->bill($_->last_bill) foreach @packages; - my @final = map { new FS::cust_pkg { $_->hash } } @packages; - - my %options = ( - 'recurring_only' => 1, - 'no_usage_reset' => 1, - 'no_commit' => 1, - ); - - my %params = ( - 'return_bill' => [], - 'pkg_list' => \@packages, - 'time' => $cust_bill->_date, - ); - - my $error = $self->bill(%options, %params); - die $error if $error; # XXX think about this a bit more - - my $credit = 0; - $credit += $_->charged foreach @{$params{return_bill}}; - $credit = sprintf('%.2f', $credit); - warn "$me discount_term_values $term credit: $credit\n" if $DEBUG; - - %params = ( - 'return_bill' => [], - 'pkg_list' => \@packages, - 'time' => $packages[0]->part_pkg->add_freq($cust_bill->_date) - ); - - $error = $self->bill(%options, %params); - die $error if $error; # XXX think about this a bit more - - my $next = 0; - $next += $_->charged foreach @{$params{return_bill}}; - warn "$me discount_term_values $term next: $next\n" if $DEBUG; - - %params = ( - 'return_bill' => [], - 'pkg_list' => \@final, - 'time' => $cust_bill->_date, - 'freq_override' => $term, - ); - - $error = $self->bill(%options, %params); - die $error if $error; # XXX think about this a bit more - - my $final = $self->balance - $credit; - $final += $_->charged foreach @{$params{return_bill}}; - $final = sprintf('%.2f', $final); - warn "$me discount_term_values $term final: $final\n" if $DEBUG; - - my $savings = sprintf('%.2f', $self->balance + ($term - 1) * $next - $final); - - ( $credit, $savings, $final ); - -} - -sub discount_terms_hash { - my $self = shift; - - my %result = (); - my @terms = $self->discount_terms; - foreach my $term (@terms) { - my @result = $self->discount_term_values($term); - $result{$term} = [ @result ] if scalar(@result); - } - - return %result; - -} - =back =cut @@ -2245,6 +2078,8 @@ sub apply_payments { return $total_unapplied_payments; } +=back + =head1 BUGS =head1 SEE ALSO diff --git a/FS/FS/cust_main/Billing_Discount.pm b/FS/FS/cust_main/Billing_Discount.pm new file mode 100644 index 000000000..9dda389f6 --- /dev/null +++ b/FS/FS/cust_main/Billing_Discount.pm @@ -0,0 +1,207 @@ +package FS::cust_main::Billing_Discount; + +use strict; +use vars qw( $DEBUG $me ); +use FS::Record qw( qsearch ); #qsearchs ); +use FS::cust_pkg; + +# 1 is mostly method/subroutine entry and options +# 2 traces progress of some operations +# 3 is even more information including possibly sensitive data +$DEBUG = 0; +$me = '[FS::cust_main::Billing_Discount]'; + +=head1 NAME + +FS::cust_main::Billing_Discount - Billing discount mixin for cust_main + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +These methods are available on FS::cust_main objects. + +=head1 METHODS + +=over 4 + +=item _discount_pkg_and_bill + +=cut + +sub _discount_pkgs_and_bill { + my $self = shift; + + my @cust_bill = $self->cust_bill; + my $cust_bill = pop @cust_bill; + return () unless $cust_bill && $cust_bill->owed; + + my @where = (); + push @where, "cust_bill_pkg.invnum = ". $cust_bill->invnum; + push @where, "cust_bill_pkg.pkgpart_override IS NULL"; + push @where, "part_pkg.freq = '1'"; + push @where, "(cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0)"; + push @where, "(cust_pkg.susp IS NULL OR cust_pkg.susp = 0)"; + push @where, "0<(SELECT count(*) FROM part_pkg_discount + WHERE part_pkg.pkgpart = part_pkg_discount.pkgpart)"; + push @where, + "0=(SELECT count(*) FROM cust_bill_pkg_discount + WHERE cust_bill_pkg.billpkgnum = cust_bill_pkg_discount.billpkgnum)"; + + my $extra_sql = 'WHERE '. join(' AND ', @where); + + my @cust_pkg = + qsearch({ + 'table' => 'cust_pkg', + 'select' => "DISTINCT cust_pkg.*", + 'addl_from' => 'JOIN cust_bill_pkg USING(pkgnum) '. + 'JOIN part_pkg USING(pkgpart)', + 'hashref' => {}, + 'extra_sql' => $extra_sql, + }); + + ($cust_bill, @cust_pkg); +} + +=item _discountable_pkgs_at_term + +=cut + +#this isn't even a method +sub _discountable_pkgs_at_term { + my ($term, @pkgs) = @_; + my $part_pkg = new FS::part_pkg { freq => $term - 1 }; + grep { ( !$_->adjourn || $_->adjourn > $part_pkg->add_freq($_->bill) ) && + ( !$_->expire || $_->expire > $part_pkg->add_freq($_->bill) ) + } + @pkgs; +} + +=item discount_terms + +Returns a list of lengths for term discounts + +=cut + +sub discount_terms { + my $self = shift; + + my %terms = (); + + my @discount_pkgs = $self->_discount_pkgs_and_bill; + shift @discount_pkgs; #discard bill; + + map { $terms{$_->months} = 1 } + grep { $_->months && $_->months > 1 } + map { $_->discount } + map { $_->part_pkg->part_pkg_discount } + @discount_pkgs; + + return sort { $a <=> $b } keys %terms; + +} + +=item discount_term_values MONTHS + +Returns a list with credit, dollar amount saved, and total bill acheived +by prepaying the most recent invoice for MONTHS. + +=cut + +sub discount_term_values { + my $self = shift; + my $term = shift; + + local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; + + warn "$me discount_term_values called with $term\n" if $DEBUG; + + my %result = (); + + my @packages = $self->_discount_pkgs_and_bill; + my $cust_bill = shift(@packages); + @packages = _discountable_pkgs_at_term( $term, @packages ); + return () unless scalar(@packages); + + $_->bill($_->last_bill) foreach @packages; + my @final = map { new FS::cust_pkg { $_->hash } } @packages; + + my %options = ( + 'recurring_only' => 1, + 'no_usage_reset' => 1, + 'no_commit' => 1, + ); + + my %params = ( + 'return_bill' => [], + 'pkg_list' => \@packages, + 'time' => $cust_bill->_date, + ); + + my $error = $self->bill(%options, %params); + die $error if $error; # XXX think about this a bit more + + my $credit = 0; + $credit += $_->charged foreach @{$params{return_bill}}; + $credit = sprintf('%.2f', $credit); + warn "$me discount_term_values $term credit: $credit\n" if $DEBUG; + + %params = ( + 'return_bill' => [], + 'pkg_list' => \@packages, + 'time' => $packages[0]->part_pkg->add_freq($cust_bill->_date) + ); + + $error = $self->bill(%options, %params); + die $error if $error; # XXX think about this a bit more + + my $next = 0; + $next += $_->charged foreach @{$params{return_bill}}; + warn "$me discount_term_values $term next: $next\n" if $DEBUG; + + %params = ( + 'return_bill' => [], + 'pkg_list' => \@final, + 'time' => $cust_bill->_date, + 'freq_override' => $term, + ); + + $error = $self->bill(%options, %params); + die $error if $error; # XXX think about this a bit more + + my $final = $self->balance - $credit; + $final += $_->charged foreach @{$params{return_bill}}; + $final = sprintf('%.2f', $final); + warn "$me discount_term_values $term final: $final\n" if $DEBUG; + + my $savings = sprintf('%.2f', $self->balance + ($term - 1) * $next - $final); + + ( $credit, $savings, $final ); + +} + +sub discount_terms_hash { + my $self = shift; + + my %result = (); + my @terms = $self->discount_terms; + foreach my $term (@terms) { + my @result = $self->discount_term_values($term); + $result{$term} = [ @result ] if scalar(@result); + } + + return %result; + +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L, L + +=cut + +1; diff --git a/FS/MANIFEST b/FS/MANIFEST index 56f7af0c6..b4bce287d 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -69,6 +69,7 @@ FS/cust_credit.pm FS/cust_credit_bill.pm FS/cust_main.pm FS/cust_main/Billing.pm +FS/cust_main/Billing_Discount.pm FS/cust_main/Billing_Realtime.pm FS/cust_main/Import.pm FS/cust_main/Packages.pm -- 2.11.0