From a63b2729682586d8860290576e9307629424dbe0 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 9 May 2002 12:38:40 +0000 Subject: [PATCH] texas tax! --- FS/FS/Conf.pm | 7 + FS/FS/cust_main.pm | 160 +++++++++++++++------ FS/FS/cust_main_county.pm | 4 +- FS/FS/part_pkg.pm | 6 +- FS/t/cust_tax_exempt.t | 5 + htetc/global.asa | 2 +- htetc/handler.pl | 2 +- httemplate/browse/agent.cgi | 4 +- httemplate/browse/cust_main_county.cgi | 59 +++++--- httemplate/edit/agent.cgi | 8 +- httemplate/edit/cust_main.cgi | 4 +- httemplate/edit/cust_main_county-expand.cgi | 15 +- httemplate/edit/cust_main_county.cgi | 29 ++-- httemplate/edit/part_pkg.cgi | 26 ++++ .../edit/process/cust_main_county-expand.cgi | 4 +- httemplate/edit/process/cust_main_county.cgi | 11 +- 16 files changed, 254 insertions(+), 92 deletions(-) create mode 100644 FS/t/cust_tax_exempt.t diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 6e45ec052..126461763 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -930,6 +930,13 @@ httemplate/docs/config.html 'type' => 'checkbox', }, + { + 'key' => 'enable_taxclasses', + 'section' => 'billing', + 'description' => 'Enable per-package tax classes', + 'type' => 'checkbox', + }, + ); 1; diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 4316988ca..0faa60ca6 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -26,6 +26,7 @@ use FS::queue; use FS::part_pkg; use FS::part_bill_event; use FS::cust_bill_event; +use FS::cust_tax_exempt; use FS::Msgcat qw(gettext); @ISA = qw( FS::Record ); @@ -874,8 +875,11 @@ sub bill { # & generate invoice database. my( $total_setup, $total_recur ) = ( 0, 0 ); - my( $taxable_setup, $taxable_recur ) = ( 0, 0 ); + #my( $taxable_setup, $taxable_recur ) = ( 0, 0 ); my @cust_bill_pkg = (); + my $tax = 0;## + #my $taxable_charged = 0;## + #my $charged = 0;## foreach my $cust_pkg ( qsearch('cust_pkg', { 'custnum' => $self->custnum } ) @@ -888,7 +892,7 @@ sub bill { $cust_pkg->setfield('bill', '') unless defined($cust_pkg->bill); - my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $cust_pkg->pkgpart } ); + my $part_pkg = $cust_pkg->part_pkg; #so we don't modify cust_pkg record unnecessarily my $cust_pkg_mod_flag = 0; @@ -958,7 +962,7 @@ sub bill { # here $sdate = $cust_pkg->bill || $cust_pkg->setup || $time; - $mon += $part_pkg->getfield('freq'); + $mon += $part_pkg->freq; until ( $mon < 12 ) { $mon -= 12; $year++; } $cust_pkg->setfield('bill', timelocal($sec,$min,$hour,$mday,$mon,$year)); @@ -969,6 +973,7 @@ sub bill { warn "\$recur is undefined" unless defined($recur); warn "\$cust_pkg->bill is undefined" unless defined($cust_pkg->bill); + my $taxable_charged = 0; if ( $cust_pkg_mod_flag ) { $error=$cust_pkg->replace($old_cust_pkg); if ( $error ) { #just in case @@ -996,51 +1001,126 @@ sub bill { push @cust_bill_pkg, $cust_bill_pkg; $total_setup += $setup; $total_recur += $recur; - $taxable_setup += $setup - unless $part_pkg->dbdef_table->column('setuptax') - && $part_pkg->setuptax =~ /^Y$/i; - $taxable_recur += $recur - unless $part_pkg->dbdef_table->column('recurtax') - && $part_pkg->recurtax =~ /^Y$/i; - } - } - - } + $taxable_charged += $setup + unless $part_pkg->setuptax =~ /^Y$/i; + $taxable_charged += $recur + unless $part_pkg->recurtax =~ /^Y$/i; + + unless ( $self->tax =~ /Y/i + || $self->payby eq 'COMP' + || $taxable_charged == 0 ) { + + my $cust_main_county = + qsearchs('cust_main_county',{ + 'state' => $self->state, + 'county' => $self->county, + 'country' => $self->country, + 'taxclass' => $part_pkg->taxclass, + } ) + or qsearchs('cust_main_county',{ + 'state' => $self->state, + 'county' => $self->county, + 'country' => $self->country, + 'taxclass' => '', + } ) + or do { + $dbh->rollback if $oldAutoCommit; + return + "fatal: can't find tax rate for state/county/country/taxclass ". + join('/', map $self->$_(), qw(state county country taxclass) ). + "\n"; + }; + + if ( $cust_main_county->exempt_amount ) { + my ($mon,$year) = (localtime($sdate) )[4,5]; + $mon++; + my $freq = $part_pkg->freq || 1; + my $taxable_per_month = sprintf("%.2f", $taxable_charged / $freq ); + foreach my $which_month ( 1 .. $freq ) { + my %hash = ( + 'custnum' => $self->custnum, + 'taxnum' => $cust_main_county->taxnum, + 'year' => 1900+$year, + 'month' => $mon++, + ); + #until ( $mon < 12 ) { $mon -= 12; $year++; } + until ( $mon < 13 ) { $mon -= 12; $year++; } + my $cust_tax_exempt = + qsearchs('cust_tax_exempt', \%hash) + || new FS::cust_tax_exempt( { %hash, 'amount' => 0 } ); + my $remaining_exemption = sprintf("%.2f", + $cust_main_county->exempt_amount - $cust_tax_exempt->amount ); + if ( $remaining_exemption > 0 ) { + my $addl = $remaining_exemption > $taxable_per_month + ? $taxable_per_month + : $remaining_exemption; + $taxable_charged -= $addl; + my $new_cust_tax_exempt = new FS::cust_tax_exempt ( { + $cust_tax_exempt->hash, + 'amount' => sprintf("%.2f", $cust_tax_exempt->amount + $addl), + } ); + $error = $new_cust_tax_exempt->exemptnum + ? $new_cust_tax_exempt->replace($cust_tax_exempt) + : $new_cust_tax_exempt->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "fatal: can't update cust_tax_exempt: $error"; + } + + } # if $remaining_exemption > 0 + + } #foreach $which_month + + } #if $cust_main_county->exempt_amount + + $taxable_charged = sprintf( "%.2f", $taxable_charged); + $tax += $taxable_charged * $cust_main_county->tax / 100 + + } #unless $self->tax =~ /Y/i + # || $self->payby eq 'COMP' + # || $taxable_charged == 0 + + } #if $setup > 0 || $recur > 0 + + } #if $cust_pkg_mod_flag + + } #foreach my $cust_pkg my $charged = sprintf( "%.2f", $total_setup + $total_recur ); - my $taxable_charged = sprintf( "%.2f", $taxable_setup + $taxable_recur ); +# my $taxable_charged = sprintf( "%.2f", $taxable_setup + $taxable_recur ); - unless ( @cust_bill_pkg ) { + unless ( @cust_bill_pkg ) { #don't create invoices with no line items $dbh->commit or die $dbh->errstr if $oldAutoCommit; return ''; } - unless ( $self->tax =~ /Y/i - || $self->payby eq 'COMP' - || $taxable_charged == 0 ) { - my $cust_main_county = qsearchs('cust_main_county',{ - 'state' => $self->state, - 'county' => $self->county, - 'country' => $self->country, - } ) or die "fatal: can't find tax rate for state/county/country ". - $self->state. "/". $self->county. "/". $self->country. "\n"; - my $tax = sprintf( "%.2f", - $taxable_charged * ( $cust_main_county->getfield('tax') / 100 ) - ); - - if ( $tax > 0 ) { - $charged = sprintf( "%.2f", $charged+$tax ); - - my $cust_bill_pkg = new FS::cust_bill_pkg ({ - 'pkgnum' => 0, - 'setup' => $tax, - 'recur' => 0, - 'sdate' => '', - 'edate' => '', - }); - push @cust_bill_pkg, $cust_bill_pkg; - } +# unless ( $self->tax =~ /Y/i +# || $self->payby eq 'COMP' +# || $taxable_charged == 0 ) { +# my $cust_main_county = qsearchs('cust_main_county',{ +# 'state' => $self->state, +# 'county' => $self->county, +# 'country' => $self->country, +# } ) or die "fatal: can't find tax rate for state/county/country ". +# $self->state. "/". $self->county. "/". $self->country. "\n"; +# my $tax = sprintf( "%.2f", +# $taxable_charged * ( $cust_main_county->getfield('tax') / 100 ) +# ); + + $tax = sprintf("%.2f", $tax); + if ( $tax > 0 ) { + $charged = sprintf( "%.2f", $charged+$tax ); + + my $cust_bill_pkg = new FS::cust_bill_pkg ({ + 'pkgnum' => 0, + 'setup' => $tax, + 'recur' => 0, + 'sdate' => '', + 'edate' => '', + }); + push @cust_bill_pkg, $cust_bill_pkg; } +# } my $cust_bill = new FS::cust_bill ( { 'custnum' => $self->custnum, diff --git a/FS/FS/cust_main_county.pm b/FS/FS/cust_main_county.pm index a9a4a85bd..8e83b1a1d 100644 --- a/FS/FS/cust_main_county.pm +++ b/FS/FS/cust_main_county.pm @@ -101,7 +101,7 @@ methods. sub check { my $self = shift; - $self->amount(0) unless $self->amount; + $self->exempt_amount(0) unless $self->exempt_amount; $self->ut_numbern('taxnum') || $self->ut_textn('state') @@ -109,7 +109,7 @@ sub check { || $self->ut_text('country') || $self->ut_float('tax') || $self->ut_textn('taxclass') # ... - || $self->ut_money('amount') + || $self->ut_money('exempt_amount') ; } diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index 1f3106544..9c33e9a3b 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -59,7 +59,7 @@ inherits from FS::Record. The following fields are currently supported: =item recurtax - Recurring fee tax exempt flag, empty or `Y' -=item taxclass - Texas tax class flag, empty or "none", "access", or "hosting" +=item taxclass - Tax class flag =item plan - Price plan @@ -235,7 +235,7 @@ sub check { || $self->ut_anything('plandata') || $self->ut_enum('setuptax', [ '', 'Y' ] ) || $self->ut_enum('recurtax', [ '', 'Y' ] ) - || $self->ut_enum('taxclass', [ '', 'none', 'access', 'hosting' ] ) + || $self->ut_textn('taxclass') || $self->ut_enum('disabled', [ '', 'Y' ] ) ; } @@ -297,7 +297,7 @@ sub payby { =head1 VERSION -$Id: part_pkg.pm,v 1.13 2002-05-04 15:00:18 ivan Exp $ +$Id: part_pkg.pm,v 1.14 2002-05-09 12:38:39 ivan Exp $ =head1 BUGS diff --git a/FS/t/cust_tax_exempt.t b/FS/t/cust_tax_exempt.t new file mode 100644 index 000000000..8af13e3aa --- /dev/null +++ b/FS/t/cust_tax_exempt.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cust_tax_exempt; +$loaded=1; +print "ok 1\n"; diff --git a/htetc/global.asa b/htetc/global.asa index 62f454475..d04a5edbf 100644 --- a/htetc/global.asa +++ b/htetc/global.asa @@ -9,7 +9,7 @@ use HTML::Entities; use IO::Handle; use IO::File; use String::Approx qw(amatch); -use HTML::Widgets::SelectLayers; +use HTML::Widgets::SelectLayers 0.02; use FS::UID qw(cgisuidsetup dbh getotaker datasrc driver_name); use FS::Record qw(qsearch qsearchs fields dbdef); use FS::Conf; diff --git a/htetc/handler.pl b/htetc/handler.pl index 056efa88b..49bcbc08c 100644 --- a/htetc/handler.pl +++ b/htetc/handler.pl @@ -66,7 +66,7 @@ sub handler use IO::Handle; use IO::File; use String::Approx qw(amatch); - use HTML::Widgets::SelectLayers; + use HTML::Widgets::SelectLayers 0.02; use FS::UID qw(cgisuidsetup dbh getotaker datasrc driver_name); use FS::Record qw(qsearch qsearchs fields dbdef); use FS::Conf; diff --git a/httemplate/browse/agent.cgi b/httemplate/browse/agent.cgi index c74ef2b63..246500941 100755 --- a/httemplate/browse/agent.cgi +++ b/httemplate/browse/agent.cgi @@ -24,8 +24,8 @@ print &table(), < Agent Type - Freq. (unimp.) - Prog. (unimp.) + Freq. + Prog. END # Agent # diff --git a/httemplate/browse/cust_main_county.cgi b/httemplate/browse/cust_main_county.cgi index 5df8dfae6..991606087 100755 --- a/httemplate/browse/cust_main_county.cgi +++ b/httemplate/browse/cust_main_county.cgi @@ -1,26 +1,36 @@ <% +my $conf = new FS::Conf; +my $enable_taxclasses = $conf->exists('enable_taxclasses'); + print header("Tax Rate Listing", menubar( 'Main Menu' => $p, 'Edit tax rates' => $p. "edit/cust_main_county.cgi", )),<expand country to specify a country's tax rates by state.
Click on expand state to specify a state's tax rates by county. -

END -print &table(), <Click on expand taxclasses to specify tax classes'; +} + +print '

'. &table(). < Country State County + Taxclass Tax + Exempt
per
month END -my @regions = sort { $a->country cmp $b->country - or $a->state cmp $b->state - or $a->county cmp $b->county +my @regions = sort { $a->country cmp $b->country + or $a->state cmp $b->state + or $a->county cmp $b->county + or $a->taxclass cmp $b->taxclass } qsearch('cust_main_county',{}); my $sup=0; @@ -30,7 +40,7 @@ for ( my $i=0; $i<@regions; $i++ ) { my $hashref = $cust_main_county->hashref; print < - $hashref->{country} + $hashref->{country} END my $j; @@ -42,7 +52,8 @@ END for ( $j=1; $i+$j<@regions; $j++ ) { last if $hashref->{country} ne $regions[$i+$j]->country || $hashref->{state} ne $regions[$i+$j]->state - || $hashref->{tax} != $regions[$i+$j]->tax; + || $hashref->{tax} != $regions[$i+$j]->tax + || $hashref->{exempt_amount} != $regions[$i+$j]->exempt_amount; } my $newsup=0; @@ -60,9 +71,9 @@ END $j = 1; } - print "", $hashref->{state} - ? $hashref->{state} - : qq!(ALL) !. + print "{state} + ? ' BGCOLOR="#ffffff">'. $hashref->{state} + : qq! BGCOLOR="#cccccc">(ALL) !. qq!expand country!; @@ -73,11 +84,11 @@ END # $sup=$newsup; - print ""; + print "{county} ) { - print $hashref->{county}; + print ' BGCOLOR="#ffffff">'. $hashref->{county}; } else { - print "(ALL)"; + print ' BGCOLOR="#cccccc">(ALL)'; if ( $hashref->{state} ) { print qq!!. qq!"; - print <$hashref->{tax}% - -END + print "{taxclass} ) { + print ' BGCOLOR="#ffffff">'. $hashref->{taxclass}; + } else { + print ' BGCOLOR="#cccccc">(ALL)'; + if ( $enable_taxclasses ) { + print qq!!. + qq!expand taxclasses!; + } + + } + print ""; + + print "$hashref->{tax}%". + '$'. + sprintf("%.2f", $hashref->{exempt_amount} || 0). ''. + ''; } diff --git a/httemplate/edit/agent.cgi b/httemplate/edit/agent.cgi index 3fca34326..449456cdd 100755 --- a/httemplate/edit/agent.cgi +++ b/httemplate/edit/agent.cgi @@ -51,12 +51,12 @@ print < - Frequency (unimplemented) - + + - Program (unimplemented) - + + END diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi index 33e72d864..e92abefd7 100755 --- a/httemplate/edit/cust_main.cgi +++ b/httemplate/edit/cust_main.cgi @@ -98,7 +98,9 @@ if ( $custnum && ! $conf->exists('editreferrals') ) { print qq!!; } else { my(@referrals) = qsearch('part_referral',{}); - if ( scalar(@referrals) == 1 ) { + if ( scalar(@referrals) == 0 ) { + die "You have not created any advertising sources. You must create at least one advertising source before adding a customer. Go to ". popurl(2). "browse/part_referral.cgi and create one or more advertising sources."; + } elsif ( scalar(@referrals) == 1 ) { $refnum ||= $referrals[0]->refnum; print qq!!; } else { diff --git a/httemplate/edit/cust_main_county-expand.cgi b/httemplate/edit/cust_main_county-expand.cgi index 66e8aaf9e..9f314a457 100755 --- a/httemplate/edit/cust_main_county-expand.cgi +++ b/httemplate/edit/cust_main_county-expand.cgi @@ -1,16 +1,18 @@ <% -my($taxnum, $delim, $expansion ); +my($taxnum, $delim, $expansion, $taxclass ); +my($query) = $cgi->keywords; if ( $cgi->param('error') ) { $taxnum = $cgi->param('taxnum'); $delim = $cgi->param('delim'); $expansion = $cgi->param('expansion'); + $taxclass = $cgi->param('taxclass'); } else { - my ($query) = $cgi->keywords; - $query =~ /^(\d+)$/ - or die "Illegal taxnum!"; - $taxnum = $1; + $query =~ /^(taxclass)?(\d+)$/ + or die "Illegal taxnum (query $query)"; + $taxclass = $1 ? 'taxclass' : ''; + $taxnum = $2; $delim = 'n'; $expansion = ''; } @@ -31,11 +33,12 @@ print qq!Error: !, $cgi->param('error'), print < + Separate by END print 'line (rumor has it broken on some browsers) or', +print '>line (broken on some browsers) or', 'whitespace.'; diff --git a/httemplate/edit/cust_main_county.cgi b/httemplate/edit/cust_main_county.cgi index a11711770..7ef37a48d 100755 --- a/httemplate/edit/cust_main_county.cgi +++ b/httemplate/edit/cust_main_county.cgi @@ -14,8 +14,10 @@ print qq!
$hashref->{country} END - print "", $hashref->{state} - ? $hashref->{state} - : '(ALL)' + print "{state} + ? ' BGCOLOR="#ffffff">'.$hashref->{state} + : ' BGCOLOR="#cccccc">(ALL)' , ""; - print "", $hashref->{county} - ? $hashref->{county} - : '(ALL)' + print "{county} + ? ' BGCOLOR="#ffffff">'. $hashref->{county} + : ' BGCOLOR="#cccccc">(ALL)' + , ""; + + print "{taxclass} + ? ' BGCOLOR="#ffffff">'. $hashref->{taxclass} + : ' BGCOLOR="#cccccc">(ALL)' , ""; print qq!%!; -END + qq!" VALUE="!, $hashref->{tax}, qq!" SIZE=6 MAXLENGTH=6>%!; + print qq!\$!; + print ''; } diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index a63fc23fe..e03017db4 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -88,6 +88,23 @@ print '>'; print ''; +my $conf = new FS::Conf; +if ( $conf->exists('enable_taxclasses') ) { + print 'Tax class'; +} else { + print + ''; +} + print 'Disable new orders'; print '{disabled} eq "Y"; @@ -344,6 +361,14 @@ my %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); } tie my %options, 'Tie::IxHash', map { $_=>$plans{$_}->{'name'} } keys %plans; +my @form_select = (); +if ( $conf->exists('enable_taxclasses') ) { + push @form_select, 'taxclass'; +} else { + push @fixups, 'taxclass'; #hidden +} + + my $widget = new HTML::Widgets::SelectLayers( 'selected_layer' => $part_pkg->plan, 'options' => \%options, @@ -351,6 +376,7 @@ my $widget = new HTML::Widgets::SelectLayers( 'form_action' => 'process/part_pkg.cgi', 'form_text' => [ qw(pkg comment freq clone pkgnum pkgpart), @fixups ], 'form_checkbox' => [ qw(setuptax recurtax disabled) ], + 'form_select' => [ @form_select ], 'fixup_callback' => sub { #my $ = @_; my $html = ''; diff --git a/httemplate/edit/process/cust_main_county-expand.cgi b/httemplate/edit/process/cust_main_county-expand.cgi index 64061deed..a452711c1 100755 --- a/httemplate/edit/process/cust_main_county-expand.cgi +++ b/httemplate/edit/process/cust_main_county-expand.cgi @@ -27,7 +27,9 @@ foreach ( @expansion) { my(%hash)=$cust_main_county->hash; my($new)=new FS::cust_main_county \%hash; $new->setfield('taxnum',''); - if ( ! $cust_main_county->state ) { + if ( $cgi->param('taxclass') ) { + $new->setfield('taxclass', $_); + } elsif ( ! $cust_main_county->state ) { $new->setfield('state',$_); } else { $new->setfield('county',$_); diff --git a/httemplate/edit/process/cust_main_county.cgi b/httemplate/edit/process/cust_main_county.cgi index 0800789b5..990a23919 100755 --- a/httemplate/edit/process/cust_main_county.cgi +++ b/httemplate/edit/process/cust_main_county.cgi @@ -1,13 +1,16 @@ <% -foreach ( $cgi->param ) { +foreach ( grep { /^tax\d+$/ } $cgi->param ) { /^tax(\d+)$/ or die "Illegal form $_!"; my($taxnum)=$1; my($old)=qsearchs('cust_main_county',{'taxnum'=>$taxnum}) or die "Couldn't find taxnum $taxnum!"; - next unless $old->getfield('tax') ne $cgi->param("tax$taxnum"); - my(%hash)=$old->hash; - $hash{tax}=$cgi->param("tax$taxnum"); + my $exempt_amount = $cgi->param("exempt_amount$taxnum"); + next unless $old->tax ne $cgi->param("tax$taxnum") + || $old->exempt_amount ne $exempt_amount; + my %hash = $old->hash; + $hash{tax} = $cgi->param("tax$taxnum"); + $hash{exempt_amount} = $exempt_amount; my($new)=new FS::cust_main_county \%hash; my($error)=$new->replace($old); if ( $error ) { -- 2.11.0