X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Ftax_rate.pm;h=0047f9d5fcc167a79c4299ff7926dc0cdab3f6fb;hb=0870a4c1fb02be43ea5524f58650d99c81477681;hp=a5a623d948a427e7017639e1947cdef1f41d38f9;hpb=0af38652da3b3be7da2d35b048285ef6f2194e1a;p=freeside.git diff --git a/FS/FS/tax_rate.pm b/FS/FS/tax_rate.pm index a5a623d94..0047f9d5f 100644 --- a/FS/FS/tax_rate.pm +++ b/FS/FS/tax_rate.pm @@ -1,20 +1,20 @@ package FS::tax_rate; +use base qw( FS::Record ); use strict; -use vars qw( @ISA $DEBUG $me +use vars qw( $DEBUG $me %tax_unittypes %tax_maxtypes %tax_basetypes %tax_authorities %tax_passtypes %GetInfoType $keep_cch_files ); use Date::Parse; use DateTime; use DateTime::Format::Strptime; -use Storable qw( thaw nfreeze ); use IO::File; use File::Temp; use Text::CSV_XS; +use URI::Escape; use LWP::UserAgent; use HTTP::Request; use HTTP::Response; -use MIME::Base64; use DBIx::DBSchema; use DBIx::DBSchema::Table; use DBIx::DBSchema::Column; @@ -29,10 +29,6 @@ use FS::part_pkg_taxproduct; use FS::cust_main; use FS::Misc qw( csv_from_fixed ); -use URI::Escape; - -@ISA = qw( FS::Record ); - $DEBUG = 0; $me = '[FS::tax_rate]'; $keep_cch_files = 0; @@ -82,9 +78,10 @@ a location code provided by a tax authority =item taxclassnum -a foreign key into FS::tax_class - the type of tax -referenced but FS::part_pkg_taxrate -eitem effective_date +a foreign key into FS::tax_class - the type of tax referenced by +FS::part_pkg_taxrate + +=item effective_date the time after which the tax applies @@ -215,8 +212,8 @@ sub check { $self->ut_numbern('taxnum') || $self->ut_text('geocode') || $self->ut_textn('data_vendor') - || $self->ut_textn('location') - || $self->ut_foreign_key('taxclassnum', 'tax_class', 'taxclassnum') + || $self->ut_cch_textn('location') + || $self->ut_foreign_keyn('taxclassnum', 'tax_class', 'taxclassnum') || $self->ut_snumbern('effective_date') || $self->ut_float('tax') || $self->ut_floatn('excessrate') @@ -245,6 +242,18 @@ sub check { } +#ut_text / ut_textn w/ ` added cause now that's in the data +sub ut_cch_textn { + my($self,$field)=@_; + $self->getfield($field) + =~ /^([\wô \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]\<\>\`]*)$/ + or return gettext('illegal_or_empty_text'). " $field: ". + $self->getfield($field); + $self->setfield($field,$1); + ''; + +} + =item taxclass_description Returns the human understandable value associated with the related @@ -276,16 +285,25 @@ sub unittype_name { =item maxtype_name -Returns the human understandable value associated with the maxtype column +Returns the human understandable value associated with the maxtype column. =cut +# XXX these are non-functional, and most of them are horrible to implement +# in our current model + %tax_maxtypes = ( '0' => 'receipts per invoice', '1' => 'receipts per item', '2' => 'total utility charges per utility tax year', '3' => 'total charges per utility tax year', '4' => 'receipts per access line', + '7' => 'total utility charges per calendar year', '9' => 'monthly receipts per location', + '10' => 'monthly receipts exceeds taxbase and total tax per month does not exceed maxtax', # wtf? + '11' => 'receipts/units per access line', + '14' => 'units per invoice', + '15' => 'units per month', + '18' => 'units per account', ); sub maxtype_name { @@ -361,7 +379,7 @@ sub passtype_name { $tax_passtypes{$self->passtype}; } -=item taxline TAXABLES, [ OPTIONSHASH ] +=item taxline_cch TAXABLES, [ OPTIONSHASH ] Returns a listref of a name and an amount of tax calculated for the list of packages/amounts referenced by TAXABLES. If an error occurs, a message @@ -369,15 +387,15 @@ is returned as a scalar. =cut -sub taxline { +sub taxline_cch { my $self = shift; + # this used to accept a hash of options but none of them did anything + # so it's been removed. my $taxables; - my %opt = (); if (ref($_[0]) eq 'ARRAY') { $taxables = shift; - %opt = @_; }else{ $taxables = [ @_ ]; #exemptions would be broken in this case @@ -413,17 +431,14 @@ sub taxline { } my $maxtype = $self->maxtype || 0; - if ($maxtype != 0 && $maxtype != 9) { + if ($maxtype != 0 && $maxtype != 1 + && $maxtype != 14 && $maxtype != 15 + && $maxtype != 18 # sigh + ) { return $self->_fatal_or_null( 'tax with "'. $self->maxtype_name. '" threshold' ); - } - - if ($maxtype == 9) { - return - $self->_fatal_or_null( 'tax with "'. $self->maxtype_name. '" threshold' ); - # "texas" tax - } + } # I don't know why, it's not like there are maxtypes that we DO support # we treat gross revenue as gross receipts and expect the tax data # to DTRT (i.e. tax on tax rules) @@ -476,12 +491,21 @@ sub taxline { } - # - # XXX insert exemption handling here + # XXX handle excessrate (use_excessrate) / excessfee / + # taxbase/feebase / taxmax/feemax + # and eventually exemptions # # the tax or fee is applied to taxbase or feebase and then # the excessrate or excess fee is applied to taxmax or feemax - # + + if ( ($self->taxmax > 0 and $taxable_charged > $self->taxmax) or + ($self->feemax > 0 and $taxable_units > $self->feemax) ) { + # throw an error + # (why not just cap taxable_charged/units at the taxmax/feemax? because + # it's way more complicated than that. this won't even catch every case + # where a bracket maximum should apply.) + return $self->_fatal_or_null( 'tax base > taxmax/feemax for tax'.$self->taxnum ); + } $amount += $taxable_charged * $self->tax; $amount += $taxable_units * $self->fee; @@ -499,6 +523,8 @@ sub taxline { sub _fatal_or_null { my ($self, $error) = @_; + $DB::single = 1; # not a mistake + my $conf = new FS::Conf; $error = "can't yet handle ". $error; @@ -514,10 +540,10 @@ sub _fatal_or_null { } } -=item tax_on_tax CUST_MAIN +=item tax_on_tax CUST_LOCATION Returns a list of taxes which are candidates for taxing taxes for the -given customer (see L) +given service location (see L) =cut @@ -525,13 +551,13 @@ given customer (see L) sub tax_on_tax { #akshun my $self = shift; - my $cust_main = shift; + my $cust_location = shift; warn "looking up taxes on tax ". $self->taxnum. " for customer ". - $cust_main->custnum + $cust_location->custnum if $DEBUG; - my $geocode = $cust_main->geocode($self->data_vendor); + my $geocode = $cust_location->geocode($self->data_vendor); # CCH oddness in m2m my $dbh = dbh; @@ -587,6 +613,36 @@ sub tax_rate_location { } + +=item find_or_insert + +Finds an existing tax definition matching the data_vendor, taxname, +taxclassnum, and geocode of this one, if one exists, and sets the contents of +this tax rate equal to that one (including its taxnum). If an existing +definition is not found, inserts this one. Returns an error string if +inserting a record failed. + +=cut + +sub find_or_insert { + my $self = shift; + # this doesn't uniquely identify CCH taxes (kinda goofy, I know) + die "find_or_insert is not compatible with CCH taxes\n" + if $self->data_vendor eq 'cch'; + + my @keys = (qw(data_vendor taxname taxclassnum geocode)); + my %hash = map { $_ => $self->get($_) } @keys; + my $existing = qsearchs('tax_rate', \%hash); + if ($existing) { + foreach ($self->fields) { + $self->set($_, $existing->get($_)); + } + return; + } else { + return $self->insert; + } +} + =back =head1 SUBROUTINES @@ -785,7 +841,8 @@ sub batch_import { } - for (grep { !exists($delete{$_}) } keys %insert) { + my @replace = grep { exists($delete{$_}) } keys %insert; + for (@replace) { if ( $job ) { # progress bar if ( time - $min_sec > $last ) { my $error = $job->update_statustext( @@ -799,20 +856,35 @@ sub batch_import { } } - my $tax_rate = new FS::tax_rate( $insert{$_} ); - my $error = $tax_rate->insert; + my $old = qsearchs( 'tax_rate', $delete{$_} ); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - my $hashref = $insert{$_}; - $line = join(", ", map { "$_ => ". $hashref->{$_} } keys(%$hashref) ); - return "can't insert tax_rate for $line: $error"; + if ( $old ) { + + my $new = new FS::tax_rate({ $old->hash, %{$insert{$_}}, 'manual' => '' }); + $new->taxnum($old->taxnum); + my $error = $new->replace($old); + + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + my $hashref = $insert{$_}; + $line = join(", ", map { "$_ => ". $hashref->{$_} } keys(%$hashref) ); + return "can't replace tax_rate for $line: $error"; + } + + $imported++; + + } else { + + $old = delete $delete{$_}; + warn "WARNING: can't find tax_rate to replace (inserting instead and continuing) for: ". + #join(" ", map { "$_ => ". $old->{$_} } @fields); + join(" ", map { "$_ => ". $old->{$_} } keys(%$old) ); } $imported++; } - for (grep { exists($delete{$_}) } keys %insert) { + for (grep { !exists($delete{$_}) } keys %insert) { if ( $job ) { # progress bar if ( time - $min_sec > $last ) { my $error = $job->update_statustext( @@ -826,27 +898,17 @@ sub batch_import { } } - my $old = qsearchs( 'tax_rate', $delete{$_} ); - unless ($old) { - $dbh->rollback if $oldAutoCommit; - $old = $delete{$_}; - return "can't find tax_rate to replace for: ". - #join(" ", map { "$_ => ". $old->{$_} } @fields); - join(" ", map { "$_ => ". $old->{$_} } keys(%$old) ); - } - my $new = new FS::tax_rate({ $old->hash, %{$insert{$_}}, 'manual' => '' }); - $new->taxnum($old->taxnum); - my $error = $new->replace($old); + my $tax_rate = new FS::tax_rate( $insert{$_} ); + my $error = $tax_rate->insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; my $hashref = $insert{$_}; $line = join(", ", map { "$_ => ". $hashref->{$_} } keys(%$hashref) ); - return "can't replace tax_rate for $line: $error"; + return "can't insert tax_rate for $line: $error"; } $imported++; - $imported++; } for (grep { !exists($insert{$_}) } keys %delete) { @@ -864,20 +926,22 @@ sub batch_import { } my $tax_rate = qsearchs( 'tax_rate', $delete{$_} ); - unless ($tax_rate) { + if (!$tax_rate) { $dbh->rollback if $oldAutoCommit; $tax_rate = $delete{$_}; - return "can't find tax_rate to delete for: ". - #join(" ", map { "$_ => ". $tax_rate->{$_} } @fields); - join(" ", map { "$_ => ". $tax_rate->{$_} } keys(%$tax_rate) ); - } - my $error = $tax_rate->delete; + warn "WARNING: can't find tax_rate to delete for: ". + join(" ", map { "$_ => ". $tax_rate->{$_} } keys(%$tax_rate) ). + " (ignoring)\n"; + } else { + my $error = $tax_rate->delete; # XXX we really should not do this + # (it orphans CBPTRL records) - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - my $hashref = $delete{$_}; - $line = join(", ", map { "$_ => ". $hashref->{$_} } keys(%$hashref) ); - return "can't delete tax_rate for $line: $error"; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + my $hashref = $delete{$_}; + $line = join(", ", map { "$_ => ". $hashref->{$_} } keys(%$hashref) ); + return "can't delete tax_rate for $line: $error"; + } } $imported++; @@ -898,35 +962,25 @@ Load a batch import as a queued JSRPC job =cut sub process_batch_import { - my $job = shift; + my ($job, $param) = @_; - my $oldAutoCommit = $FS::UID::AutoCommit; - local $FS::UID::AutoCommit = 0; - my $dbh = dbh; - - my $param = thaw(decode_base64(shift)); - my $args = '$job, encode_base64( nfreeze( $param ) )'; - - my $method = '_perform_batch_import'; if ( $param->{reload} ) { - $method = 'process_batch_reload'; - } - - eval "$method($args);"; - if ($@) { - $dbh->rollback or die $dbh->errstr if $oldAutoCommit; - die $@; + process_batch_reload($job, $param); + } else { + # '_perform', yuck + _perform_batch_import($job, $param); } - #success! - $dbh->commit or die $dbh->errstr if $oldAutoCommit; } sub _perform_batch_import { - my $job = shift; + my ($job, $param) = @_; - my $param = thaw(decode_base64(shift)); - my $format = $param->{'format'}; #well... this is all cch specific + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $format = $param->{'format'}; my $files = $param->{'uploaded_files'} or die "No files provided."; @@ -934,20 +988,18 @@ sub _perform_batch_import { my (%files) = map { /^(\w+):((taxdata\/\w+\.\w+\/)?[\.\w]+)$/ ? ($1,$2):() } split /,/, $files; + my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc; + my $error = ''; + if ( $format eq 'cch' || $format eq 'cch-fixed' || $format eq 'cch-update' || $format eq 'cch-fixed-update' ) { - my $oldAutoCommit = $FS::UID::AutoCommit; - local $FS::UID::AutoCommit = 0; - my $dbh = dbh; - my $error = ''; my @insert_list = (); my @delete_list = (); my @predelete_list = (); my $insertname = ''; my $deletename = ''; - my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc; my @list = ( 'GEOCODE', \&FS::tax_rate_location::batch_import, 'CODE', \&FS::tax_class::batch_import, @@ -961,7 +1013,7 @@ sub _perform_batch_import { my $file = lc($name). 'file'; unless ($files{$file}) { - $error = "No $name supplied"; + #$error = "No $name supplied"; next; } next if $name eq 'DETAIL' && $format =~ /update/; @@ -978,7 +1030,7 @@ sub _perform_batch_import { unlink $filename or warn "Can't delete $filename: $!" unless $keep_cch_files; push @insert_list, $name, $insertname, $import_sub, $format; - if ( $name eq 'GEOCODE' ) { #handle this whole ordering issue better + if ( $name eq 'GEOCODE' || $name eq 'CODE' ) { #handle this whole ordering issue better unshift @predelete_list, $name, $deletename, $import_sub, $format; } else { unshift @delete_list, $name, $deletename, $import_sub, $format; @@ -996,10 +1048,17 @@ sub _perform_batch_import { 'DETAIL', "$dir/".$files{detailfile}, \&FS::tax_rate::batch_import, $format if $format =~ /update/; + my %addl_param = (); + if ( $param->{'delete_only'} ) { + $addl_param{'delete_only'} = $param->{'delete_only'}; + @insert_list = () + } + $error ||= _perform_cch_tax_import( $job, [ @predelete_list ], [ @insert_list ], [ @delete_list ], + \%addl_param, ); @@ -1009,22 +1068,49 @@ sub _perform_batch_import { unlink $file or warn "Can't delete $file: $!"; } - if ($error) { - $dbh->rollback or die $dbh->errstr if $oldAutoCommit; - die $error; - }else{ - $dbh->commit or die $dbh->errstr if $oldAutoCommit; + } elsif ( $format =~ /^billsoft-(\w+)$/ ) { + my $mode = $1; + my $file = $dir.'/'.$files{'file'}; + open my $fh, "< $file" or $error ||= "Can't open file $file: $!"; + my @param = ( + { + filehandle => $fh, + format => 'billsoft', + }, $job); + if ( $mode eq 'pcode' ) { + $error ||= FS::cust_tax_location::batch_import(@param); + seek $fh, 0, 0; + $error ||= FS::tax_rate_location::batch_import(@param); + } elsif ( $mode eq 'taxclass' ) { + $error ||= FS::tax_class::batch_import(@param); + } elsif ( $mode eq 'taxproduct' ) { + $error ||= FS::part_pkg_taxproduct::batch_import(@param); + } else { + die "unknown import mode 'billsoft-$mode'\n"; } - }else{ + } else { die "Unknown format: $format"; } + if ($error) { + $dbh->rollback or die $dbh->errstr if $oldAutoCommit; + die $error; + } else { + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + } + } +# +# +# EVERYTHING THAT FOLLOWS IS CCH-SPECIFIC. +# +# sub _perform_cch_tax_import { - my ( $job, $predelete_list, $insert_list, $delete_list ) = @_; + my ( $job, $predelete_list, $insert_list, $delete_list, $addl_param ) = @_; + $addl_param ||= {}; my $error = ''; foreach my $list ($predelete_list, $insert_list, $delete_list) { @@ -1033,7 +1119,11 @@ sub _perform_cch_tax_import { my $fmt = "$format-update"; $fmt = $format. ( lc($name) eq 'zip' ? '-zip' : '' ); open my $fh, "< $file" or $error ||= "Can't open $name file $file: $!"; - $error ||= &{$method}({ 'filehandle' => $fh, 'format' => $fmt }, $job); + my $param = { 'filehandle' => $fh, + 'format' => $fmt, + %$addl_param, + }; + $error ||= &{$method}($param, $job); close $fh; } } @@ -1276,11 +1366,14 @@ sub _remember_tax_products { my ( $imported, $last, $min_sec ) = _progressbar_foo(); - my $extra_sql = "WHERE taxproductnum IS NOT NULL OR ". - "0 < ( SELECT count(*) from part_pkg_option WHERE ". - " part_pkg_option.pkgpart = part_pkg.pkgpart AND ". - " optionname LIKE 'usage_taxproductnum_%' AND ". - " optionvalue != '' )"; + my $extra_sql = " + WHERE taxproductnum IS NOT NULL + OR EXISTS ( SELECT 1 from part_pkg_option + WHERE part_pkg_option.pkgpart = part_pkg.pkgpart + AND optionname LIKE 'usage_taxproductnum_%' + AND optionvalue != '' + ) + "; my @items = qsearch( { table => 'part_pkg', select => 'DISTINCT pkgpart,taxproductnum', hashref => {}, @@ -1502,15 +1595,20 @@ sub _copy_from_temp { =item process_download_and_reload Download and process a tax update as a queued JSRPC job after wiping the -existing wipable tax data. +existing wipeable tax data. =cut sub process_download_and_reload { - _process_reload('process_download_and_update', @_); + _process_reload(\&process_download_and_update, @_); } - +# +# +# END OF CCH STUFF +# +# + =item process_batch_reload Load and process a tax update from the provided files as a queued JSRPC job @@ -1519,15 +1617,12 @@ after wiping the existing wipable tax data. =cut sub process_batch_reload { - _process_reload('_perform_batch_import', @_); + _process_reload(\&_perform_batch_import, @_); } - sub _process_reload { - my ( $method, $job ) = ( shift, shift ); - - my $param = thaw(decode_base64($_[0])); - my $format = $param->{'format'}; #well... this is all cch specific + my ( $continuation, $job, $param ) = @_; + my $format = $param->{'format'}; my ( $imported, $last, $min_sec ) = _progressbar_foo(); @@ -1541,47 +1636,79 @@ sub _process_reload { my $dbh = dbh; my $error = ''; - my $sql = - "SELECT count(*) FROM part_pkg_taxoverride JOIN tax_class ". - "USING (taxclassnum) WHERE data_vendor = '$format'"; - my $sth = $dbh->prepare($sql) or die $dbh->errstr; - $sth->execute - or die "Unexpected error executing statement $sql: ". $sth->errstr; - die "Don't (yet) know how to handle part_pkg_taxoverride records." - if $sth->fetchrow_arrayref->[0]; - - # really should get a table EXCLUSIVE lock here - - #remember disabled taxes - my %disabled_tax_rate = (); - $error ||= _remember_disabled_taxes( $job, $format, \%disabled_tax_rate ); - - #remember tax products - my %taxproduct = (); - $error ||= _remember_tax_products( $job, $format, \%taxproduct ); - - #create temp tables - $error ||= _create_temporary_tables( $job, $format ); - - #import new data - unless ($error) { - my $args = '$job, @_'; - eval "$method($args);"; - $error = $@ if $@; - } + if ( $format =~ /^cch/ ) { + # no, THIS part is CCH specific + + my $sql = + "SELECT count(*) FROM part_pkg_taxoverride JOIN tax_class ". + "USING (taxclassnum) WHERE data_vendor = '$format'"; + my $sth = $dbh->prepare($sql) or die $dbh->errstr; + $sth->execute + or die "Unexpected error executing statement $sql: ". $sth->errstr; + die "Don't (yet) know how to handle part_pkg_taxoverride records." + if $sth->fetchrow_arrayref->[0]; - #restore taxproducts - $error ||= _restore_remembered_tax_products( $job, $format, \%taxproduct ); + # really should get a table EXCLUSIVE lock here - #disable tax_rates - $error ||= - _restore_remembered_disabled_taxes( $job, $format, \%disabled_tax_rate ); + #remember disabled taxes + my %disabled_tax_rate = (); + $error ||= _remember_disabled_taxes( $job, $format, \%disabled_tax_rate ); - #wipe out the old data - $error ||= _remove_old_tax_data( $job, $format ); + #remember tax products + my %taxproduct = (); + $error ||= _remember_tax_products( $job, $format, \%taxproduct ); - #untemporize - $error ||= _copy_from_temp( $job, $format ); + #create temp tables + $error ||= _create_temporary_tables( $job, $format ); + + #import new data + unless ($error) { + eval { &{$continuation}( $job, $param ) }; + $error = $@ if $@; + } + + #restore taxproducts + $error ||= _restore_remembered_tax_products( $job, $format, \%taxproduct ); + + #disable tax_rates + $error ||= + _restore_remembered_disabled_taxes( $job, $format, \%disabled_tax_rate ); + + #wipe out the old data + $error ||= _remove_old_tax_data( $job, $format ); + + #untemporize + $error ||= _copy_from_temp( $job, $format ); + + } elsif ( $format =~ /^billsoft-(\w+)/ ) { + + my $mode = $1; + my @sql; + if ( $mode eq 'pcode' ) { + push @sql, + "DELETE FROM cust_tax_location WHERE data_vendor = 'billsoft'", + "UPDATE tax_rate_location SET disabled = 'Y' WHERE data_vendor = 'billsoft'"; + } elsif ( $mode eq 'taxclass' ) { + push @sql, + "DELETE FROM tax_class WHERE data_vendor = 'billsoft'"; + } elsif ( $mode eq 'taxproduct' ) { + push @sql, + "DELETE FROM part_pkg_taxproduct WHERE data_vendor = 'billsoft'"; + } + + foreach (@sql) { + if (!$dbh->do($_)) { + $error = $dbh->errstr; + last; + } + } + + unless ( $error ) { + local $@; + eval { &{ $continuation }($job, $param) }; + $error = $@; + } + } # if ($format ...) if ($error) { $dbh->rollback or die $dbh->errstr if $oldAutoCommit; @@ -1602,7 +1729,7 @@ Download and process a tax update as a queued JSRPC job sub process_download_and_update { my $job = shift; - my $param = thaw(decode_base64(shift)); + my $param = shift; my $format = $param->{'format'}; #well... this is all cch specific my ( $imported, $last, $min_sec ) = _progressbar_foo(); @@ -1705,7 +1832,7 @@ sub process_download_and_update { $param->{uploaded_files} = join( ',', @list ); $param->{format} .= '-update' if $update; $error ||= - _perform_batch_import( $job, encode_base64( nfreeze( $param ) ) ); + _perform_batch_import( $job, $param ); rename "$dir.new", "$dir" or die "cch tax update processed, but can't rename $dir.new: $!\n"; @@ -1798,11 +1925,17 @@ sub browse_queries { =item queue_liability_report PARAMS Launches a tax liability report. + +PARAMS needs to be a base64-encoded Storable hash containing: +- beginning: the start date, as a I (not a timestamp). +- end: the end date of the report, likewise. +- agentnum: the agent to limit the report to, if any. + =cut sub queue_liability_report { my $job = shift; - my $param = thaw(decode_base64(shift)); + my $param = shift; my $cgi = new CGI; $cgi->param('beginning', $param->{beginning}); @@ -1821,8 +1954,12 @@ sub queue_liability_report { =item generate_liability_report PARAMS -Generates a tax liability report. Provide a hash including desired -agentnum, beginning, and ending +Generates a tax liability report. PARAMS must include: + +- beginning, as a timestamp +- ending, as a timestamp +- p: the Freeside root URL, for generating links +- agentnum (optional) =cut @@ -1884,11 +2021,16 @@ sub generate_liability_report { my %taxes = (); my %basetaxes = (); my $calculated = 0; + + # get all distinct tuples of (tax name, state, county, city, locationtaxid) + # for taxes that have been charged + # (state, county, city are from tax_rate_location, not from customer data) my @tax_and_location = qsearch({ table => 'cust_bill_pkg', select => $select, hashref => { pkgpart => 0 }, addl_from => $addl_from, extra_sql => $where, + debug => 1, }); $count = scalar(@tax_and_location); foreach my $t ( @tax_and_location ) { @@ -1912,15 +2054,14 @@ sub generate_liability_report { $taxes{$label}->{'url_param'} = join(';', map { "$_=". uri_escape($t->$_) } @params); - my $payby_itemdesc_loc = - " payby != 'COMP' ". - "AND ( itemdesc = ? OR ? = '' AND itemdesc IS NULL ) ". + my $itemdesc_loc = + " ( itemdesc = ? OR ? = '' AND itemdesc IS NULL ) ". "AND ". FS::tax_rate_location->location_sql( map { $_ => $t->$_ } @taxparams ); my $taxwhere = - "FROM cust_bill_pkg $addl_from $where AND $payby_itemdesc_loc"; + "FROM cust_bill_pkg $addl_from $where AND $itemdesc_loc"; my $sql = "SELECT SUM(amount) $taxwhere AND cust_bill_pkg.pkgnum = 0"; @@ -1931,7 +2072,7 @@ sub generate_liability_report { my $creditfrom = "JOIN cust_credit_bill_pkg USING (billpkgnum,billpkgtaxratelocationnum)"; my $creditwhere = - "FROM cust_bill_pkg $addl_from $creditfrom $where AND $payby_itemdesc_loc"; + "FROM cust_bill_pkg $addl_from $creditfrom $where AND $itemdesc_loc"; $sql = "SELECT SUM(cust_credit_bill_pkg.amount) ". " $creditwhere AND cust_bill_pkg.pkgnum = 0"; @@ -1995,7 +2136,8 @@ sub generate_liability_report { my $dateagentlink = "begin=$args{beginning};end=$args{ending}"; $dateagentlink .= ';agentnum='. $args{agentnum} if length($agentname); - my $baselink = $args{p}. "search/cust_bill_pkg.cgi?$dateagentlink"; + my $baselink = $args{p}. "search/cust_bill_pkg.cgi?vendortax=1;" . + $dateagentlink; my $creditlink = $args{p}. "search/cust_credit_bill_pkg.html?$dateagentlink"; print $report <, L, L, schema.html from the base -documentation. +L, L, L =cut