summaryrefslogtreecommitdiff
path: root/FS
diff options
context:
space:
mode:
authorivan <ivan>2009-01-18 23:43:40 +0000
committerivan <ivan>2009-01-18 23:43:40 +0000
commit6397a30ca9f53c90a503da8786925ec75535a699 (patch)
tree0056fe6e195f5a621d46616e75fd57ad55e8c579 /FS
parenta5b4bfc7728cf3014106806b729d2390045c71a6 (diff)
finish package location tax reporing, RT#4499
Diffstat (limited to 'FS')
-rw-r--r--FS/FS/Schema.pm45
-rw-r--r--FS/FS/cust_bill_pkg.pm14
-rw-r--r--FS/FS/cust_bill_pkg_tax_location.pm136
-rw-r--r--FS/FS/cust_main.pm193
-rw-r--r--FS/FS/cust_main_county.pm28
-rw-r--r--FS/FS/tax_rate.pm5
-rw-r--r--FS/MANIFEST2
-rw-r--r--FS/t/cust_bill_pkg_tax_location.t5
8 files changed, 324 insertions, 104 deletions
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 2c3c9670d..2cdf41c8b 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -498,19 +498,19 @@ sub tables_hashref {
'cust_bill_pkg' => {
'columns' => [
- 'billpkgnum', 'serial', '', '', '', '',
- 'pkgnum', 'int', '', '', '', '',
- 'pkgpart_override', 'int', 'NULL', '', '', '',
- 'invnum', 'int', '', '', '', '',
- 'setup', @money_type, '', '',
- 'recur', @money_type, '', '',
- 'sdate', @date_type, '', '',
- 'edate', @date_type, '', '',
- 'itemdesc', 'varchar', 'NULL', $char_d, '', '',
- 'section', 'varchar', 'NULL', $char_d, '', '',
- 'quantity', 'int', 'NULL', '', '', '',
- 'unitsetup', @money_typen, '', '',
- 'unitrecur', @money_typen, '', '',
+ 'billpkgnum', 'serial', '', '', '', '',
+ 'invnum', 'int', '', '', '', '',
+ 'pkgnum', 'int', '', '', '', '',
+ 'pkgpart_override', 'int', 'NULL', '', '', '',
+ 'setup', @money_type, '', '',
+ 'recur', @money_type, '', '',
+ 'sdate', @date_type, '', '',
+ 'edate', @date_type, '', '',
+ 'itemdesc', 'varchar', 'NULL', $char_d, '', '',
+ 'section', 'varchar', 'NULL', $char_d, '', '',
+ 'quantity', 'int', 'NULL', '', '', '',
+ 'unitsetup', @money_typen, '', '',
+ 'unitrecur', @money_typen, '', '',
],
'primary_key' => 'billpkgnum',
'unique' => [],
@@ -549,6 +549,21 @@ sub tables_hashref {
'index' => [ ['billpkgnum'], ],
},
+ 'cust_bill_pkg_tax_location' => {
+ 'columns' => [
+ 'billpkgtaxlocationnum', 'serial', '', '', '', '',
+ 'billpkgnum', 'int', '', '', '', '',
+ 'taxnum', 'int', '', '', '', '',
+ 'taxtype', 'varchar', $char_d, '', '', '',
+ 'pkgnum', 'int', '', '', '', '',
+ 'locationnum', 'int', '', '', '', '', #redundant?
+ 'amount', @money_type, '', '',
+ ],
+ 'primary_key' => 'billpkgtaxlocationnum',
+ 'unique' => [],
+ 'index' => [ [ 'billpkgnum' ], [ 'taxnum' ], [ 'pkgnum' ], [ 'locationnum' ] ],
+ },
+
'cust_credit' => {
'columns' => [
'crednum', 'serial', '', '', '', '',
@@ -742,7 +757,9 @@ sub tables_hashref {
'primary_key' => 'taxnum',
'unique' => [],
# 'unique' => [ ['taxnum'], ['state', 'county'] ],
- 'index' => [ [ 'county' ], [ 'state' ], [ 'country' ] ],
+ 'index' => [ [ 'county' ], [ 'state' ], [ 'country' ],
+ [ 'taxclass' ],
+ ],
},
'tax_rate' => {
diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm
index 4e7141b52..d0c51cfec 100644
--- a/FS/FS/cust_bill_pkg.pm
+++ b/FS/FS/cust_bill_pkg.pm
@@ -152,6 +152,20 @@ sub insert {
}
}
+ my $tax_location = $self->get('cust_bill_pkg_tax_location');
+ if ( $tax_location ) {
+ foreach my $cust_bill_pkg_tax_location ( @$tax_location ) {
+ $cust_bill_pkg_tax_location->billpkgnum($self->billpkgnum);
+ warn $cust_bill_pkg_tax_location;
+ $error = $cust_bill_pkg_tax_location->insert;
+ warn $error;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
'';
diff --git a/FS/FS/cust_bill_pkg_tax_location.pm b/FS/FS/cust_bill_pkg_tax_location.pm
new file mode 100644
index 000000000..50e86eb0b
--- /dev/null
+++ b/FS/FS/cust_bill_pkg_tax_location.pm
@@ -0,0 +1,136 @@
+package FS::cust_bill_pkg_tax_location;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_bill_pkg;
+use FS::cust_pkg;
+use FS::cust_location;
+
+=head1 NAME
+
+FS::cust_bill_pkg_tax_location - Object methods for cust_bill_pkg_tax_location records
+
+=head1 SYNOPSIS
+
+ use FS::cust_bill_pkg_tax_location;
+
+ $record = new FS::cust_bill_pkg_tax_location \%hash;
+ $record = new FS::cust_bill_pkg_tax_location { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill_pkg_tax_location object represents an record of taxation
+based on package location. FS::cust_bill_pkg_tax_location inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item billpkgtaxlocationnum
+
+billpkgtaxlocationnum
+
+=item billpkgnum
+
+billpkgnum
+
+=item taxnum
+
+taxnum
+
+=item taxtype
+
+taxtype
+
+=item pkgnum
+
+pkgnum
+
+=item locationnum
+
+locationnum
+
+=item amount
+
+amount
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record. To add the record to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'cust_bill_pkg_tax_location'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid record. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('billpkgtaxlocationnum')
+ || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
+ || $self->ut_number('taxnum') #cust_bill_pkg/tax_rate key, based on taxtype
+ || $self->ut_enum('taxtype', [ qw( FS::cust_main::county FS::tax_rate ) ] )
+ || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum' )
+ || $self->ut_foreign_key('locationnum', 'cust_location', 'locationnum' )
+ || $self->ut_money('amount')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index f9a67725d..7544b80e0 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -28,6 +28,7 @@ use FS::cust_svc;
use FS::cust_bill;
use FS::cust_bill_pkg;
use FS::cust_bill_pkg_display;
+use FS::cust_bill_pkg_tax_location;
use FS::cust_pay;
use FS::cust_pay_pending;
use FS::cust_pay_void;
@@ -2302,9 +2303,7 @@ sub bill {
###
my( $total_setup, $total_recur, $postal_charge ) = ( 0, 0, 0 );
- my %tax;
my %taxlisthash;
- my %taxname;
my @precommit_hooks = ();
my @cust_pkgs = qsearch('cust_pkg', { 'custnum' => $self->custnum } );
@@ -2386,30 +2385,54 @@ sub bill {
}
warn "having a look at the taxes we found...\n" if $DEBUG > 2;
+
+ # keys are tax names (as printed on invoices / itemdesc )
+ # values are listrefs of taxlisthash keys (internal identifiers)
+ my %taxname = ();
+
+ # keys are taxlisthash keys (internal identifiers)
+ # values are (cumulative) amounts
+ my %tax = ();
+
+ # keys are taxlisthash keys (internal identifiers)
+ # values are listrefs of cust_bill_pkg_tax_location hashrefs
+ my %tax_location = ();
+
foreach my $tax ( keys %taxlisthash ) {
my $tax_object = shift @{ $taxlisthash{$tax} };
warn "found ". $tax_object->taxname. " as $tax\n" if $DEBUG > 2;
- my $listref_or_error =
+ my $hashref_or_error =
$tax_object->taxline( $taxlisthash{$tax},
'custnum' => $self->custnum,
'invoice_time' => $invoice_time
);
- unless (ref($listref_or_error)) {
+ unless ( ref($hashref_or_error) ) {
$dbh->rollback if $oldAutoCommit;
- return $listref_or_error;
+ return $hashref_or_error;
}
unshift @{ $taxlisthash{$tax} }, $tax_object;
- warn "adding ". $listref_or_error->[1].
- " as ". $listref_or_error->[0]. "\n"
- if $DEBUG > 2;
- $tax{ $tax } += $listref_or_error->[1];
- if ( $taxname{ $listref_or_error->[0] } ) {
- push @{ $taxname{ $listref_or_error->[0] } }, $tax;
- }else{
- $taxname{ $listref_or_error->[0] } = [ $tax ];
+ my $name = $hashref_or_error->{'name'};
+ my $amount = $hashref_or_error->{'amount'};
+
+ #warn "adding $amount as $name\n";
+ $taxname{ $name } ||= [];
+ push @{ $taxname{ $name } }, $tax;
+
+ $tax{ $tax } += $amount;
+
+ $tax_location{ $tax } ||= [];
+ if ( $tax_object->get('pkgnum') || $tax_object->get('locationnum') ) {
+ push @{ $tax_location{ $tax } },
+ {
+ 'taxnum' => $tax_object->taxnum,
+ 'taxtype' => ref($tax_object),
+ 'pkgnum' => $tax_object->get('pkgnum'),
+ 'locationnum' => $tax_object->get('locationnum'),
+ 'amount' => sprintf('%.2f', $amount ),
+ };
}
-
+
}
#move the cust_tax_exempt_pkg records to the cust_bill_pkgs we will commit
@@ -2475,11 +2498,15 @@ sub bill {
foreach my $taxname ( keys %taxname ) {
my $tax = 0;
my %seen = ();
+ my @cust_bill_pkg_tax_location = ();
warn "adding $taxname\n" if $DEBUG > 1;
foreach my $taxitem ( @{ $taxname{$taxname} } ) {
- $tax += $tax{$taxitem} unless $seen{$taxitem};
- $seen{$taxitem} = 1;
+ next if $seen{$taxitem}++;
warn "adding $tax{$taxitem}\n" if $DEBUG > 1;
+ $tax += $tax{$taxitem};
+ push @cust_bill_pkg_tax_location,
+ map { new FS::cust_bill_pkg_tax_location $_ }
+ @{ $tax_location{ $taxitem } };
}
next unless $tax;
@@ -2493,6 +2520,7 @@ sub bill {
'sdate' => '',
'edate' => '',
'itemdesc' => $taxname,
+ 'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location,
};
}
@@ -2766,71 +2794,89 @@ sub _handle_taxes {
my %cust_bill_pkg = ();
my %taxes = ();
- my $prefix =
- ( $conf->exists('tax-ship_address') && length($self->ship_last) )
- ? 'ship_'
- : '';
-
my @classes;
#push @classes, $cust_bill_pkg->usage_classes if $cust_bill_pkg->type eq 'U';
push @classes, $cust_bill_pkg->usage_classes if $cust_bill_pkg->usage;
push @classes, 'setup' if $cust_bill_pkg->setup;
push @classes, 'recur' if $cust_bill_pkg->recur;
- if ( $conf->exists('enable_taxproducts')
- && (scalar($part_pkg->part_pkg_taxoverride) || $part_pkg->has_taxproduct)
- && ( $self->tax !~ /Y/i && $self->payby ne 'COMP' )
- )
- {
+ if ( $self->tax !~ /Y/i && $self->payby ne 'COMP' ) {
- foreach my $class (@classes) {
- my $err_or_ref = $self->_gather_taxes( $part_pkg, $class, $prefix );
- return $err_or_ref unless ref($err_or_ref);
- $taxes{$class} = $err_or_ref;
- }
+ if ( $conf->exists('enable_taxproducts')
+ && ( scalar($part_pkg->part_pkg_taxoverride)
+ || $part_pkg->has_taxproduct
+ )
+ )
+ {
- unless (exists $taxes{''}) {
- my $err_or_ref = $self->_gather_taxes( $part_pkg, '', $prefix );
- return $err_or_ref unless ref($err_or_ref);
- $taxes{''} = $err_or_ref;
- }
+ if ( $conf->exists('tax-pkg_address') && $cust_pkg->locationnum ) {
+ return "fatal: Can't (yet) use tax-pkg_address with taxproducts";
+ }
- } elsif ( $self->tax !~ /Y/i && $self->payby ne 'COMP' ) {
+ foreach my $class (@classes) {
+ my $err_or_ref = $self->_gather_taxes( $part_pkg, $class );
+ return $err_or_ref unless ref($err_or_ref);
+ $taxes{$class} = $err_or_ref;
+ }
- my %taxhash = map { $_ => $self->get("$prefix$_") }
- qw( state county country );
+ unless (exists $taxes{''}) {
+ my $err_or_ref = $self->_gather_taxes( $part_pkg, '' );
+ return $err_or_ref unless ref($err_or_ref);
+ $taxes{''} = $err_or_ref;
+ }
- $taxhash{'taxclass'} = $part_pkg->taxclass;
+ } else {
- my @taxes = qsearch( 'cust_main_county', \%taxhash );
+ my @loc_keys = qw( state county country );
+ my %taxhash;
+ if ( $conf->exists('tax-pkg_address') && $cust_pkg->locationnum ) {
+ my $cust_location = $cust_pkg->cust_location;
+ %taxhash = map { $_ => $cust_location->$_() } @loc_keys;
+ } else {
+ my $prefix =
+ ( $conf->exists('tax-ship_address') && length($self->ship_last) )
+ ? 'ship_'
+ : '';
+ %taxhash = map { $_ => $self->get("$prefix$_") } @loc_keys;
+ }
- unless ( @taxes ) {
- $taxhash{'taxclass'} = '';
- @taxes = qsearch( 'cust_main_county', \%taxhash );
- }
+ $taxhash{'taxclass'} = $part_pkg->taxclass;
- #one more try at a whole-country tax rate
- unless ( @taxes ) {
- $taxhash{$_} = '' foreach qw( state county );
- @taxes = qsearch( 'cust_main_county', \%taxhash );
- }
+ my @taxes = qsearch( 'cust_main_county', \%taxhash );
- $taxes{''} = [ @taxes ];
- $taxes{'setup'} = [ @taxes ];
- $taxes{'recur'} = [ @taxes ];
- $taxes{$_} = [ @taxes ] foreach (@classes);
-
- # maybe eliminate this entirely, along with all the 0% records
- unless ( @taxes ) {
- return
- "fatal: can't find tax rate for state/county/country/taxclass ".
- join('/', ( map $self->get("$prefix$_"),
- qw(state county country)
- ),
- $part_pkg->taxclass ). "\n";
- }
+ unless ( @taxes ) {
+ $taxhash{'taxclass'} = '';
+ @taxes = qsearch( 'cust_main_county', \%taxhash );
+ }
+
+ #one more try at a whole-country tax rate
+ unless ( @taxes ) {
+ $taxhash{$_} = '' foreach qw( state county );
+ @taxes = qsearch( 'cust_main_county', \%taxhash );
+ }
+
+ if ( $conf->exists('tax-pkg_address') && $cust_pkg->locationnum ) {
+ foreach (@taxes) {
+ $_->set('pkgnum', $cust_pkg->pkgnum );
+ $_->set('locationnum', $cust_pkg->locationnum );
+ }
+ }
+
+ $taxes{''} = [ @taxes ];
+ $taxes{'setup'} = [ @taxes ];
+ $taxes{'recur'} = [ @taxes ];
+ $taxes{$_} = [ @taxes ] foreach (@classes);
+
+ # maybe eliminate this entirely, along with all the 0% records
+ unless ( @taxes ) {
+ return
+ "fatal: can't find tax rate for state/county/country/taxclass ".
+ join('/', map $taxhash{$_}, qw(state county country taxclass) );
+ }
+
+ } #if $conf->exists('enable_taxproducts') ...
- } #if $conf->exists('enable_taxproducts') ...
+ }
my @display = ();
if ( $conf->exists('separate_usage') ) {
@@ -2856,7 +2902,12 @@ sub _handle_taxes {
my $tax_cust_bill_pkg = $tax_cust_bill_pkg{$key};
foreach my $tax ( @taxes ) {
- my $taxname = ref( $tax ). ' '. $tax->taxnum;
+
+ my $taxname = ref( $tax ). ' taxnum'. $tax->taxnum;
+# $taxname .= ' pkgnum'. $cust_pkg->pkgnum.
+# ' locationnum'. $cust_pkg->locationnum
+# if $conf->exists('tax-pkg_address') && $cust_pkg->locationnum;
+
if ( exists( $taxlisthash->{ $taxname } ) ) {
push @{ $taxlisthash->{ $taxname } }, $tax_cust_bill_pkg;
}else{
@@ -2872,7 +2923,6 @@ sub _gather_taxes {
my $self = shift;
my $part_pkg = shift;
my $class = shift;
- my $prefix = shift;
my @taxes = ();
my $geocode = $self->geocode('cch');
@@ -2900,12 +2950,11 @@ sub _gather_taxes {
# maybe eliminate this entirely, along with all the 0% records
unless ( @taxes ) {
return
- "fatal: can't find tax rate for zip/taxproduct/pkgpart ".
- join('/', ( map $self->get("$prefix$_"),
- qw(zip)
- ),
+ "fatal: can't find tax rate for geocode/taxproduct/pkgpart ".
+ join('/', $geocode,
$part_pkg->taxproduct_description,
- $part_pkg->pkgpart ). "\n";
+ $part_pkg->pkgpart
+ );
}
warn "Found taxes ".
diff --git a/FS/FS/cust_main_county.pm b/FS/FS/cust_main_county.pm
index edf57ab2f..bb60abb45 100644
--- a/FS/FS/cust_main_county.pm
+++ b/FS/FS/cust_main_county.pm
@@ -198,29 +198,18 @@ sub _list_sql {
map $_->[0], @{ $sth->fetchall_arrayref };
}
-=item taxline TAXABLES, [ OPTIONSHASH ]
+=item taxline TAXABLES_ARRAYREF, [ OPTION => VALUE ... ]
Returns a listref of a name and an amount of tax calculated for the list of
-packages or amounts referenced by TAXABLES. Returns a scalar error message
-on error.
+packages or amounts referenced by TAXABLES_ARRAYREF. Returns a scalar error
+message on error.
-OPTIONSHASH includes custnum and invoice_date and are hints to this method
+Options include custnum and invoice_date and are hints to this method
=cut
sub taxline {
- my $self = shift;
-
- my $taxables;
- my %opt = ();
-
- if (ref($_[0]) eq 'ARRAY') {
- $taxables = shift;
- %opt = @_;
- }else{
- $taxables = [ @_ ];
- # exemptions broken in this case
- }
+ my( $self, $taxables, %opt ) = @_;
my @exemptions = ();
push @exemptions, @{ $_->_cust_tax_exempt_pkg }
@@ -362,7 +351,12 @@ sub taxline {
}
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
- return [ $name, $amount ]
+
+ return {
+ 'name' => $name,
+ 'amount' => $amount,
+ };
+
}
=back
diff --git a/FS/FS/tax_rate.pm b/FS/FS/tax_rate.pm
index 4b906d33d..bd981e301 100644
--- a/FS/FS/tax_rate.pm
+++ b/FS/FS/tax_rate.pm
@@ -443,7 +443,10 @@ sub taxline {
warn "calculated taxes as [ $name, $amount ]\n"
if $DEBUG;
- return [$name, $amount];
+ return {
+ 'name' => $name,
+ 'amount' => $amount,
+ };
}
diff --git a/FS/MANIFEST b/FS/MANIFEST
index 21d721dcb..796eca703 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -429,3 +429,5 @@ FS/cust_pkg_detail.pm
t/cust_pkg_detail.t
FS/cust_location.pm
t/cust_location.t
+FS/cust_bill_pkg_tax_location.pm
+t/cust_bill_pkg_tax_location.t
diff --git a/FS/t/cust_bill_pkg_tax_location.t b/FS/t/cust_bill_pkg_tax_location.t
new file mode 100644
index 000000000..087b59af0
--- /dev/null
+++ b/FS/t/cust_bill_pkg_tax_location.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill_pkg_tax_location;
+$loaded=1;
+print "ok 1\n";