package FS::cust_credit_bill_pkg;
+use base qw( FS::cust_main_Mixin FS::Record );
use strict;
-use vars qw( @ISA );
use FS::Record qw( qsearch qsearchs dbh );
-use FS::cust_main_Mixin;
-use FS::cust_credit_bill;
-use FS::cust_bill_pkg;
use FS::cust_bill_pkg_tax_location;
use FS::cust_bill_pkg_tax_rate_location;
use FS::cust_tax_exempt_pkg;
-@ISA = qw( FS::cust_main_Mixin FS::Record );
-
=head1 NAME
FS::cust_credit_bill_pkg - Object methods for cust_credit_bill_pkg records
return $error;
}
- my $payable = $self->cust_bill_pkg->payable($self->setuprecur);
- my $taxable = $self->_is_taxable ? $payable : 0;
- my $part_pkg = $self->cust_bill_pkg->part_pkg;
- my $freq = $part_pkg ? $part_pkg->freq || 1 : 1;# assume unchanged
- my $taxable_per_month = sprintf("%.2f", $taxable / $freq );
+ my $cust_bill_pkg = $self->cust_bill_pkg;
+ #'payable' is the amount charged (either setup or recur)
+ # minus any credit applications, including this one
+ my $payable = $cust_bill_pkg->payable($self->setuprecur);
+ my $part_pkg = $cust_bill_pkg->part_pkg;
+ my $freq = $cust_bill_pkg->freq;
+ unless ($freq) {
+ $freq = $part_pkg ? ($part_pkg->freq || 1) : 1;#fallback.. assumes unchanged
+ }
+ my $taxable_per_month = sprintf("%.2f", $payable / $freq );
my $credit_per_month = sprintf("%.2f", $self->amount / $freq ); #pennies?
if ($taxable_per_month >= 0) { #panic if its subzero?
- my $groupby = 'taxnum,year,month';
+ my $groupby = join(',',
+ qw(taxnum year month exempt_monthly exempt_cust
+ exempt_cust_taxname exempt_setup exempt_recur));
my $sum = 'SUM(amount)';
my @exemptions = qsearch(
{
'extra_sql' => "GROUP BY $groupby HAVING $sum > 0",
}
);
+ # each $exemption is now the sum of all monthly exemptions applied to
+ # this line item for a particular taxnum and month.
foreach my $exemption ( @exemptions ) {
- next if $taxable_per_month >= $exemption->amount;
- my $amount = $exemption->amount - $taxable_per_month;
- if ($amount > $credit_per_month) {
- "cust_bill_pkg ". $self->billpkgnum. " Reducing.\n";
- $amount = $credit_per_month;
+ my $amount = 0;
+ if ( $exemption->exempt_monthly ) {
+ # finite exemptions
+ # $taxable_per_month is AFTER inserting the credit application, so
+ # if it's still larger than the exemption, we don't need to adjust
+ next if $taxable_per_month >= $exemption->amount;
+ # the amount of 'excess' exemption already in place (above the
+ # remaining charged amount). We'll de-exempt that much, or the
+ # amount of the new credit, whichever is smaller.
+ $amount = $exemption->amount - $taxable_per_month;
+ # $amount is the amount of 'excess' exemption already existing
+ # (above the remaining taxable charge amount). We'll "de-exempt"
+ # that much, or the amount of the new credit, whichever is smaller.
+ if ($amount > $credit_per_month) {
+ "cust_bill_pkg ". $self->billpkgnum. " Reducing.\n";
+ $amount = $credit_per_month;
+ }
+ } elsif ( $exemption->exempt_setup or $exemption->exempt_recur ) {
+ # package defined exemptions: may be setup only, recur only, or both
+ my $method = 'exempt_'.$self->setuprecur;
+ if ( $exemption->$method ) {
+ # then it's exempt from the portion of the charge that this
+ # credit is being applied to
+ $amount = $self->amount;
+ }
+ } else {
+ # other types of exemptions: always equal to the amount of
+ # the charge
+ $amount = $self->amount;
}
+ next if $amount == 0;
+
+ # create a negative exemption
my $cust_tax_exempt_pkg = new FS::cust_tax_exempt_pkg {
+ $exemption->hash, # for exempt_ flags, taxnum, month/year
'billpkgnum' => $self->billpkgnum,
'creditbillpkgnum' => $self->creditbillpkgnum,
- 'amount' => 0-$amount,
- map { $_ => $exemption->$_ } split(',', $groupby)
+ 'amount' => sprintf('%.2f', 0-$amount),
};
- my $error = $cust_tax_exempt_pkg->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error inserting cust_tax_exempt_pkg: $error";
+
+ if ( $cust_tax_exempt_pkg->cust_main_county ) {
+
+ my $error = $cust_tax_exempt_pkg->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error inserting cust_tax_exempt_pkg: $error";
+ }
+
}
- }
+
+ } #foreach $exemption
}
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
- my $original_cust_bill_pkg = $self->cust_bill_pkg;
- my $cust_bill = $original_cust_bill_pkg->cust_bill;
-
- my %hash = $original_cust_bill_pkg->hash;
- delete $hash{$_} for qw( billpkgnum setup recur );
- $hash{$self->setuprecur} = $self->amount;
- my $cust_bill_pkg = new FS::cust_bill_pkg { %hash };
-
- use Data::Dumper;
- my @exemptions = qsearch( 'cust_tax_exempt_pkg',
- { creditbillpkgnum => $self->creditbillpkgnum }
- );
- my %seen = ();
- my @generated_exemptions = ();
- my @unseen_exemptions = ();
- foreach my $exemption ( @exemptions ) {
- my $error = $exemption->delete;
+ my @negative_exemptions = qsearch('cust_tax_exempt_pkg', {
+ 'creditbillpkgnum' => $self->creditbillpkgnum
+ });
+
+ # de-anti-exempt those negative exemptions
+ my $error;
+ foreach (@negative_exemptions) {
+ $error = $_->delete;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
- return "error deleting cust_tax_exempt_pkg: $error";
+ return $error;
}
-
- next if $seen{$exemption->taxnum};
- $seen{$exemption->taxnum} = 1;
- push @unseen_exemptions, $exemption;
}
- foreach my $exemption ( @unseen_exemptions ) {
- my $tax_object = $exemption->cust_main_county;
- unless ($tax_object) {
- $dbh->rollback if $oldAutoCommit;
- return "can't find exempted tax";
- }
-
- my $hashref_or_error =
- $tax_object->taxline( [ $cust_bill_pkg ],
- 'custnum' => $cust_bill->custnum,
- 'invoice_time' => $cust_bill->_date,
- );
- unless (ref($hashref_or_error)) {
- $dbh->rollback if $oldAutoCommit;
- return "error calculating taxes: $hashref_or_error";
- }
-
- push @generated_exemptions, @{ $cust_bill_pkg->_cust_tax_exempt_pkg || [] };
- }
-
- foreach my $taxnum ( keys %seen ) {
- my $sum = 0;
- $sum += $_->amount for grep {$_->taxnum == $taxnum} @exemptions;
- $sum -= $_->amount for grep {$_->taxnum == $taxnum} @generated_exemptions;
- $sum = sprintf("%.2f", $sum);
- unless ($sum eq '0.00' || $sum eq '-0.00') {
- $dbh->rollback if $oldAutoCommit;
- return "Can't unapply credit without charging tax";
- }
- }
-
- my $error = $self->SUPER::delete(@_);
+ $error = $self->SUPER::delete(@_);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
$self->SUPER::check;
}
-sub cust_credit_bill {
- my $self = shift;
- qsearchs('cust_credit_bill', { 'creditbillnum' => $self->creditbillnum } );
-}
-
-sub cust_bill_pkg {
- my $self = shift;
- qsearchs('cust_bill_pkg', { 'billpkgnum' => $self->billpkgnum } );
-}
-
sub cust_bill_pkg_tax_Xlocation {
my $self = shift;
- if ($self->billpkg_tax_locationnum) {
+ if ($self->billpkgtaxlocationnum) {
return qsearchs(
'cust_bill_pkg_tax_location',
{ 'billpkgtaxlocationnum' => $self->billpkgtaxlocationnum },
);
- } elsif ($self->billpkg_tax_rate_locationnum) {
+ } elsif ($self->billpkgtaxratelocationnum) {
return qsearchs(
'cust_bill_pkg_tax_rate_location',
{ 'billpkgtaxratelocationnum' => $self->billpkgtaxratelocationnum },
B<setuprecur> field is a kludge to compensate for cust_bill_pkg having separate
setup and recur fields. It should be removed once that's fixed.
-B<insert> method assumes that the frequency of the package associated with the
-associated line item remains unchanged during the lifetime of the system.
-It may get the tax exemption adjustments wrong if package definitions change
-frequency. The presense of delete methods in FS::cust_main_county and
-FS::tax_rate makes crediting of old "texas tax" unreliable in the presense of
-changing taxes. Explicit tax credit requests? Carry 'taxable' onto line
-items?
+B<insert> method used to assume that the frequency of the package associated
+with the associated line item remained unchanged during the lifetime of the
+system. That is still used as a fallback. It may get the tax exemption
+adjustments wrong if package definitions change frequency. The presense of
+delete methods in FS::cust_main_county and FS::tax_rate makes crediting of
+old "texas tax" unreliable in the presense of changing taxes. Explicit tax
+credit requests? Carry 'taxable' onto line items?
=head1 SEE ALSO