X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main%2FBilling.pm;h=5c10c639a36fa50c65b3e4a6353267881c341878;hb=9bbc67e3460dc0045df5262e89c662104e4edd9a;hp=29d5fa9165b70f519787e61e0490cd777e2831c5;hpb=506bee9c2fefa9a3cd4415a069b1fb16d9f4e3ab;p=freeside.git diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm index 29d5fa916..5c10c639a 100644 --- a/FS/FS/cust_main/Billing.pm +++ b/FS/FS/cust_main/Billing.pm @@ -379,6 +379,12 @@ Do not save the generated bill in the database. Useful with return_bill A list reference on which the generated bill(s) will be returned. +=item estimate + +Boolean value; indicates that this is an estimate rather than a "tax invoice". +This will be passed through to the tax engine, as online tax services +sometimes need to know it for reporting purposes. Otherwise it has no effect. + =item invoice_terms Optional terms to be printed on this invoice. Otherwise, customer-specific @@ -474,7 +480,8 @@ sub bill { foreach (@passes) { $tax_engines{$_} = FS::TaxEngine->new(cust_main => $self, invoice_time => $invoice_time, - cancel => $options{cancel} + cancel => $options{cancel}, + estimate => $options{estimate}, ); $tax_is_batch ||= $tax_engines{$_}->info->{batch}; } @@ -542,7 +549,8 @@ sub bill { $tax_engines{$pass} = FS::TaxEngine->new( cust_main => $self, invoice_time => $invoice_time, - cancel => $options{cancel} + cancel => $options{cancel}, + estimate => $options{estimate}, ); $cust_bill_pkg{$pass} = []; } @@ -810,16 +818,17 @@ sub bill { # calculate and append taxes if ( ! $tax_is_batch) { - my $arrayref_or_error = $tax_engines{$pass}->calculate_taxes($cust_bill); + local $@; + my $arrayref = eval { $tax_engines{$pass}->calculate_taxes($cust_bill) }; - unless ( ref( $arrayref_or_error ) ) { + if ( $@ ) { $dbh->rollback if $oldAutoCommit && !$options{no_commit}; - return $arrayref_or_error; + return $@; } # or should this be in TaxEngine? my $total_tax = 0; - foreach my $taxline ( @$arrayref_or_error ) { + foreach my $taxline ( @$arrayref ) { $total_tax += $taxline->setup; $taxline->set('invnum' => $cust_bill->invnum); # just to be sure push @cust_bill_pkg, $taxline; # for return_bill @@ -874,53 +883,53 @@ sub bill { sub _omit_zero_value_bundles { my @in = @_; - my @cust_bill_pkg = (); - my @cust_bill_pkg_bundle = (); - my $sum = 0; - my $discount_show_always = 0; + my @out = (); + my @bundle = (); + my $discount_show_always = $conf->exists('discount-show-always'); + my $show_this = 0; + + # this is a pack-and-deliver pattern. every time there's a cust_bill_pkg + # _without_ pkgpart_override, that's the start of the new bundle. if there's + # an existing bundle, and it contains a nonzero amount (or a zero amount + # that's displayable anyway), push all line items in the bundle. foreach my $cust_bill_pkg ( @in ) { - $discount_show_always = ($cust_bill_pkg->get('discounts') - && scalar(@{$cust_bill_pkg->get('discounts')}) - && $conf->exists('discount-show-always')); - - warn " pkgnum ". $cust_bill_pkg->pkgnum. " sum $sum, ". - "setup_show_zero ". $cust_bill_pkg->setup_show_zero. - "recur_show_zero ". $cust_bill_pkg->recur_show_zero. "\n" - if $DEBUG > 0; - - if (scalar(@cust_bill_pkg_bundle) && !$cust_bill_pkg->pkgpart_override) { - push @cust_bill_pkg, @cust_bill_pkg_bundle - if $sum > 0 - || ($sum == 0 && ( $discount_show_always - || grep {$_->recur_show_zero || $_->setup_show_zero} - @cust_bill_pkg_bundle - ) - ); - @cust_bill_pkg_bundle = (); - $sum = 0; + if (scalar(@bundle) and !$cust_bill_pkg->pkgpart_override) { + # ship out this bundle and reset it + if ( $show_this ) { + push @out, @bundle; + } + @bundle = (); + $show_this = 0; } - $sum += $cust_bill_pkg->setup + $cust_bill_pkg->recur; - push @cust_bill_pkg_bundle, $cust_bill_pkg; + # add this item to the current bundle + push @bundle, $cust_bill_pkg; + # determine if it makes the bundle displayable + if ( $cust_bill_pkg->setup > 0 + or $cust_bill_pkg->recur > 0 + or $cust_bill_pkg->setup_show_zero + or $cust_bill_pkg->recur_show_zero + or ($discount_show_always + and scalar(@{ $cust_bill_pkg->get('discounts')}) + ) + ) { + $show_this++; + } } - push @cust_bill_pkg, @cust_bill_pkg_bundle - if $sum > 0 - || ($sum == 0 && ( $discount_show_always - || grep {$_->recur_show_zero || $_->setup_show_zero} - @cust_bill_pkg_bundle - ) - ); + # last bundle + if ( $show_this) { + push @out, @bundle; + } warn " _omit_zero_value_bundles: ". scalar(@in). - '->'. scalar(@cust_bill_pkg). "\n" #. Dumper(@cust_bill_pkg). "\n" + '->'. scalar(@out). "\n" #. Dumper(@out). "\n" if $DEBUG > 2; - (@cust_bill_pkg); - + @out; } sub _make_lines { @@ -1098,6 +1107,14 @@ sub _make_lines { return "$@ running $method for $cust_pkg\n" if ( $@ ); + if ($recur eq 'NOTHING') { + # then calc_cancel (or calc_recur but that's not used) has declined to + # generate a recurring lineitem at all. treat this as zero, but also + # try not to generate a lineitem. + $recur = 0; + $lineitems--; + } + #base_cancel??? $unitrecur = $cust_pkg->base_recur( \$sdate ) || $recur; #XXX uuh, better @@ -1116,19 +1133,39 @@ sub _make_lines { # its frequency my $main_pkg_freq = $main_pkg->part_pkg->freq; my $supp_pkg_freq = $part_pkg->freq; - my $ratio = $supp_pkg_freq / $main_pkg_freq; - if ( $ratio != int($ratio) ) { + if ( $supp_pkg_freq == 0 or $main_pkg_freq == 0 ) { # the UI should prevent setting up packages like this, but just # in case - return "supplemental package period is not an integer multiple of main package period"; + return "unable to calculate supplemental package period ratio"; } - $next_bill = $sdate; - for (1..$ratio) { - $next_bill = $part_pkg->add_freq( $next_bill, $main_pkg_freq ); + my $ratio = $supp_pkg_freq / $main_pkg_freq; + if ( $ratio == int($ratio) ) { + # simple case: main package is X months, supp package is X*A months, + # advance supp package to where the main package will be in A cycles. + $next_bill = $sdate; + for (1..$ratio) { + $next_bill = $part_pkg->add_freq( $next_bill, $main_pkg_freq ); + } + } else { + # harder case: main package is X months, supp package is Y months. + # advance supp package by Y months. then if they're within half a + # month of each other, resync them. this may result in the period + # not being exactly Y months. + $next_bill = $part_pkg->add_freq( $sdate, $supp_pkg_freq ); + my $main_next_bill = $main_pkg->bill; + if ( $main_pkg->bill <= $time ) { + # then the main package has not yet been billed on this cycle; + # predict what its bill date will be. + $main_next_bill = + $part_pkg->add_freq( $main_next_bill, $main_pkg_freq ); + } + if ( abs($main_next_bill - $next_bill) < 86400*15 ) { + $next_bill = $main_next_bill; + } } } else { - # the normal case + # the normal case, not a supplemental package $next_bill = $part_pkg->add_freq($sdate, $options{freq_override} || 0); return "unparsable frequency: ". $part_pkg->freq if $next_bill == -1; @@ -1413,7 +1450,7 @@ sub _handle_taxes { return if ( $self->payby eq 'COMP' ); #dubious - if ( $conf->exists('enable_taxproducts') + if ( $conf->config('enable_taxproducts') && ( scalar($part_item->part_pkg_taxoverride) || $part_item->has_taxproduct ) @@ -2160,6 +2197,7 @@ sub due_cust_event { =item apply_payments_and_credits [ OPTION => VALUE ... ] Applies unapplied payments and credits. +Payments with the no_auto_apply flag set will not be applied. In most cases, this new method should be used in place of sequential apply_payments and apply_credits methods. @@ -2302,6 +2340,7 @@ sub apply_credits { Applies (see L) unapplied payments (see L) to outstanding invoice balances in chronological order. +Payments with the no_auto_apply flag set will not be applied. #and returns the value of any remaining unapplied payments. @@ -2331,7 +2370,7 @@ sub apply_payments { #return 0 unless - my @payments = $self->unapplied_cust_pay; + my @payments = grep { !$_->no_auto_apply } $self->unapplied_cust_pay; my @invoices = $self->open_cust_bill;