From 68399771603d9a2d084a9eaca480f17016801fe6 Mon Sep 17 00:00:00 2001 From: tetragon Date: Sun, 8 Oct 2006 20:47:38 +0000 Subject: First round of tax code replacement, adds cumulative tax support git-svn-id: https://ledger-smb.svn.sourceforge.net/svnroot/ledger-smb/trunk@195 4979c152-3d1c-0410-bac9-87ea11338e46 --- LedgerSMB/AM.pm | 34 ++++++++-- LedgerSMB/Form.pm | 2 +- LedgerSMB/IR.pm | 42 +++++------- LedgerSMB/IS.pm | 80 +++++++++-------------- LedgerSMB/OE.pm | 159 ++++++++++++++++++---------------------------- LedgerSMB/Tax.pm | 100 +++++++++++++++++++++++++++++ LedgerSMB/Taxes/Simple.pm | 64 +++++++++++++++++++ 7 files changed, 301 insertions(+), 180 deletions(-) create mode 100755 LedgerSMB/Tax.pm create mode 100755 LedgerSMB/Taxes/Simple.pm (limited to 'LedgerSMB') diff --git a/LedgerSMB/AM.pm b/LedgerSMB/AM.pm index c1576aed..82b58534 100755 --- a/LedgerSMB/AM.pm +++ b/LedgerSMB/AM.pm @@ -35,7 +35,7 @@ #====================================================================== package AM; - +use LedgerSMB::Tax; sub get_account { @@ -1479,15 +1479,18 @@ sub defaultaccounts { sub taxes { my ($self, $myconfig, $form) = @_; + my $taxaccounts = ''; # connect to database my $dbh = $form->{dbh}; my $query = qq| - SELECT c.id, c.accno, c.description, - t.rate * 100 AS rate, t.taxnumber, t.validto + SELECT c.id, c.accno, c.description, + t.rate * 100 AS rate, t.taxnumber, t.validto, + t.pass, m.taxmodulename FROM chart c JOIN tax t ON (c.id = t.chart_id) + JOIN taxmodule m ON (t.taxmodule_id = m.taxmodule_id) ORDER BY 3, 6|; my $sth = $dbh->prepare($query); @@ -1495,6 +1498,21 @@ sub taxes { while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { push @{ $form->{taxrates} }, $ref; + $taxaccounts .= " " . $ref{accno}; + } + + $sth->finish; + + $query = qq| + SELECT taxmodule_id, taxmodulename FROM taxmodule + ORDER BY 2|; + + $sth = $dbh->prepare($query); + $sth->execute || $form->dberror($query); + + while (my $ref = $sth->fetchrow_hashref(NAME_lc)) { + $form->{"taxmodule_".$ref->{taxmodule_id}} = + $ref->{taxmodulename}; } $sth->finish; @@ -1516,16 +1534,20 @@ sub save_taxes { $query = qq| - INSERT INTO tax (chart_id, rate, taxnumber, validto) - VALUES (?, ?, ?, ?)|; + INSERT INTO tax (chart_id, rate, taxnumber, validto, + pass, taxmodule_id) + VALUES (?, ?, ?, ?, ?, ?)|; my $sth = $dbh->prepare($query); foreach my $item (split / /, $form->{taxaccounts}) { my ($chart_id, $i) = split /_/, $item; my $rate = $form->parse_amount( $myconfig, $form->{"taxrate_$i"}) / 100; + my $validto = $form->{"validto_$i"}; + $validto = undef if not $validto; my @queryargs = ($chart_id, $rate, $form->{"taxnumber_$i"}, - $form->{"validto_$i"}); + $validto, $form->{"pass_$i"}, + $form->{"taxmodule_id_$i"}); $sth->execute(@queryargs) || $form->dberror($query); } diff --git a/LedgerSMB/Form.pm b/LedgerSMB/Form.pm index cd30803b..e5fa4f61 100755 --- a/LedgerSMB/Form.pm +++ b/LedgerSMB/Form.pm @@ -1579,7 +1579,7 @@ sub check_exchangerate { my $sth = $self->{dbh}->prepare($query); $sth->execute($currenct, $transdate); - my ($exchangerate) = $sth->fetchrow_array($query); + my ($exchangerate) = $sth->fetchrow_array; $sth->finish; $self->{dbh}->commit; diff --git a/LedgerSMB/IR.pm b/LedgerSMB/IR.pm index 92e2c964..9f2da830 100755 --- a/LedgerSMB/IR.pm +++ b/LedgerSMB/IR.pm @@ -32,6 +32,7 @@ #====================================================================== package IR; +use LedgerSMB::Tax; use LedgerSMB::PriceMatrix; sub post_invoice { @@ -172,33 +173,24 @@ sub post_invoice { my $linetotal = $form->round_amount($amount, 2); $fxdiff += $amount - $linetotal; - @taxaccounts = split / /, $form->{"taxaccounts_$i"}; + @taxaccounts = Tax::init_taxes($form, $form->{"taxaccounts_$i"}); - $ml = 1; - $tax = 0; - $fxtax = 0; + $tax = Math::BigFloat->bzero(); + $fxtax = Math::BigFloat->bzero(); - for (0 .. 1) { - $taxrate = 0; - - # add tax rates - for (@taxaccounts) { - $taxrate += $form->{"${_}_rate"} if ($form->{"${_}_rate"} * $ml) > 0; - } - - if ($form->{taxincluded}) { - $tax += $amount = $linetotal * ($taxrate / (1 + ($taxrate * $ml))); - $form->{"sellprice_$i"} -= $amount / $form->{"qty_$i"}; - } else { - $tax += $amount = $linetotal * $taxrate; - $fxtax += $fxlinetotal * $taxrate; - } - - for (@taxaccounts) { - $form->{acc_trans}{$form->{id}}{$_}{amount} += $amount * $form->{"${_}_rate"} / $taxrate if ($form->{"${_}_rate"} * $ml) > 0; - } - - $ml = -1; + if ($form->{taxincluded}) { + $tax += $amount = Tax::calculate_taxes(\@taxaccounts, $form, + $linetotal, 1); + $form->{"sellprice_$i"} -= $amount / $form{"qty_$i"}; + } else { + $tax += $amount = Tax::calculate_taxes(\@taxaccounts, $form, + $linetotal, 0); + $fxtax += Tax::calculate_taxes(\@taxaccounts, $form, + $fxlinetotal, 0); + } + + for (@taxaccounts) { + $form->{acc_trans}{$form->{id}}{$_->account}{amount} += $_->value; } $grossamount = $form->round_amount($linetotal, 2); diff --git a/LedgerSMB/IS.pm b/LedgerSMB/IS.pm index eda3a984..0b38b3a8 100755 --- a/LedgerSMB/IS.pm +++ b/LedgerSMB/IS.pm @@ -32,6 +32,7 @@ #====================================================================== package IS; +use LedgerSMB::Tax; use LedgerSMB::PriceMatrix; sub invoice_details { @@ -263,44 +264,30 @@ sub invoice_details { $form->{"linetotal_$i"} = $form->format_amount($myconfig, $linetotal, 2); push(@{ $form->{linetotal} }, $form->{"linetotal_$i"}); - @taxaccounts = split / /, $form->{"taxaccounts_$i"}; + @taxaccounts = Tax::init_taxes($form, $form->{"taxaccounts_$i"}); my $ml = 1; my @taxrates = (); $tax = 0; - for (0 .. 1) { - $taxrate = 0; - - for (@taxaccounts) { $taxrate += $form->{"${_}_rate"} if ($form->{"${_}_rate"} * $ml) > 0 } - - $taxrate *= $ml; - $taxamount = $linetotal * $taxrate / (1 + $taxrate); - $taxbase = ($linetotal - $taxamount); - - foreach $item (@taxaccounts) { - if (($form->{"${item}_rate"} * $ml) > 0) { - - push @taxrates, $form->{"${item}_rate"} * 100; - - if ($form->{taxincluded}) { - $taxaccounts{$item} += $linetotal * $form->{"${item}_rate"} / (1 + $taxrate); - $taxbase{$item} += $taxbase; - } else { - $taxbase{$item} += $linetotal; - $taxaccounts{$item} += $linetotal * $form->{"${item}_rate"}; - } - } - } + if ($form->{taxincluded}) { + $taxamount = Tax::calculate_taxes(\@taxaccounts, $form, $linetotal, 1); + $taxbase = ($linetotal - $taxamount); + $tax += Tax::extract_taxes(\@taxaccounts, $form, $linetotal); + } else { + $taxamount = Tax::calculate_taxes(\@taxaccounts, $form, $linetotal, 0); + $tax += Tax::apply_taxes(\@taxaccounts, $form, $linetotal); + } + foreach $item (@taxaccounts) { + push @taxrates, 100 * $item->rate; + $taxaccounts{$item->account} += $item->value; if ($form->{taxincluded}) { - $tax += $linetotal * ($taxrate / (1 + ($taxrate * $ml))); + $taxbase{$item->account} += $taxbase; } else { - $tax += $linetotal * $taxrate; + $taxbase{$item->account} += $linetotal; } - - $ml *= -1; } push(@{ $form->{lineitems} }, { amount => $linetotal, tax => $form->round_amount($tax, 2) }); @@ -699,31 +686,20 @@ sub post_invoice { my $linetotal = $form->round_amount($amount, 2); $fxdiff += $amount - $linetotal; - @taxaccounts = split / /, $form->{"taxaccounts_$i"}; + @taxaccounts = Tax::init_taxes($form, $form->{"taxaccounts_$i"}); $ml = 1; $tax = 0; $fxtax = 0; - for (0 .. 1) { - $taxrate = 0; - - # add tax rates - for (@taxaccounts) { $taxrate += $form->{"${_}_rate"} if ($form->{"${_}_rate"} * $ml) > 0 } - - if ($form->{taxincluded}) { - $tax += $amount = $linetotal * ($taxrate / (1 + ($taxrate * $ml))); - $form->{"sellprice_$i"} -= $amount / $form->{"qty_$i"}; - $fxtax += $fxlinetotal * ($taxrate / (1 + ($taxrate * $ml))); - } else { - $tax += $amount = $linetotal * $taxrate; - $fxtax += $fxlinetotal * $taxrate; - } - - for (@taxaccounts) { - $form->{acc_trans}{$form->{id}}{$_}{amount} += $amount * $form->{"${_}_rate"} / $taxrate if ($form->{"${_}_rate"} * $ml) > 0; - } - - $ml = -1; + if ($form->{taxincluded}) { + $tax += $amount = Tax::calculate_taxes(\@taxaccounts, $form, + $linetotal, 1); + $form->{"sellprice_$i"} -= $amount / $form->{"qty_$i"}; + $fxtax += Tax::calculate_taxes(\@taxaccounts, $form, $linetotal, 1); + } else { + $tax += $amount = Tax::calculate_taxes(\@taxaccounts, $form, + $linetotal, 0); + $fxtax += Tax::calculate_taxes(\@taxaccounts, $form, $linetotal, 0); } $grossamount = $form->round_amount($linetotal, 2); @@ -832,7 +808,11 @@ sub post_invoice { $invnetamount = $amount; $amount = 0; - for (split / /, $form->{taxaccounts}) { $amount += $form->{acc_trans}{$form->{id}}{$_}{amount} = $form->round_amount($form->{acc_trans}{$form->{id}}{$_}{amount}, 2) } + for (split / /, $form->{taxaccounts}) { + $amount += + $form->{acc_trans}{$form->{id}}{$_}{amount} = + $form->round_amount($form->{acc_trans}{$form->{id}}{$_}{amount}, 2); + } $invamount = $invnetamount + $amount; $diff = 0; diff --git a/LedgerSMB/OE.pm b/LedgerSMB/OE.pm index 9ebbad19..0d9ab99d 100755 --- a/LedgerSMB/OE.pm +++ b/LedgerSMB/OE.pm @@ -33,7 +33,7 @@ #====================================================================== package OE; - +use LedgerSMB::Tax; sub transactions { my ($self, $myconfig, $form) = @_; @@ -419,67 +419,52 @@ sub save { $form->{"sellprice_$i"} * $form->{"qty_$i"}, 2 ); - @taxaccounts = split / /, $form->{"taxaccounts_$i"}; - $taxrate = 0; - $taxdiff = 0; - - for (@taxaccounts) { $taxrate += $form->{"${_}_rate"} } - + @taxaccounts = Tax::init_taxes($form, + $form->{"taxaccounts_$i"}); if ($form->{taxincluded}) { - $taxamount = $linetotal * $taxrate - / (1 + $taxrate); - $taxbase = $linetotal - $taxamount; - # we are not keeping a natural price, - # do not round - $form->{"sellprice_$i"} = - $form->{"sellprice_$i"} - * (1 / (1 + $taxrate)); + $taxamount = Tax::calculate_taxes(\@taxaccounts, + $form, $linetotal, 1); + $form->{"sellprice_$i"} = Tax::extract_taxes(\@taxaccounts, + $form, $form->{"sellprice_$i"}); + $taxbase = Tax::extract_taxes(\@taxaccounts, + $form, $linetotal); } else { - $taxamount = $linetotal * $taxrate; + $taxamount = Tax::apply_taxes(\@taxaccounts, + $form, $linetotal); $taxbase = $linetotal; } - - if (@taxaccounts && $form->round_amount($taxamount, 2) - == 0) { - if ($form->{taxincluded}) { + + if (@taxaccounts && $form->round_amount($taxamount, 2) + == 0) { + if ($form->{taxincluded}) { foreach $item (@taxaccounts) { $taxamount = $form->round_amount( - - $linetotal - * $form->{"${item}_rate"} - / (1 + abs( - $form->{"${item}_rate"} - )), 2 - ); - - $taxaccounts{$item} += + $item->value, 2); + $taxaccounts{$item->account} += $taxamount; - $taxdiff += $taxamount; - - $taxbase{$item} += $taxbase; + $taxdiff += $taxamount; + $taxbase{$item->account} += + $taxbase; } - $taxaccounts{$taxaccounts[0]} + $taxaccounts{$taxaccounts[0]->account} += $taxdiff; - } else { + } else { foreach $item (@taxaccounts) { - $taxaccounts{$item} += - $linetotal * - $form->{"${item}_rate"}; - $taxbase{$item} += $taxbase; + $taxaccounts{$item->account} += + $item->value; + $taxbase{$item->account} += + $taxbase; } } } else { foreach $item (@taxaccounts) { - $taxaccounts{$item} += - $taxamount * - $form->{"${item}_rate"} / - $taxrate; - $taxbase{$item} += $taxbase; + $taxaccounts{$item->account} += + $item->value; + $taxbase{$item->account} += $taxbase; } } - $netamount += $form->{"sellprice_$i"} * $form->{"qty_$i"}; @@ -939,7 +924,7 @@ sub retrieve { $sth->finish; # get recurring transaction - $form->get_recurring($dbh); + $form->get_recurring; @queries = $form->run_custom_queries('oe', 'SELECT'); $form->{dbh}->commit; @@ -1328,66 +1313,39 @@ sub order_details { $myconfig, $linetotal, 2); push(@{ $form->{linetotal} }, $form->{"linetotal_$i"}); - @taxaccounts = split / /, $form->{"taxaccounts_$i"}; + @taxaccounts = Tax::init_taxes($form, + $form->{"taxaccounts_$i"}); my $ml = 1; my @taxrates = (); $tax = 0; - for (0 .. 1) { - $taxrate = 0; - - for (@taxaccounts) { - $taxrate += $form->{"${_}_rate"} - if ($form->{"${_}_rate"} * $ml) - > 0; - } - - $taxrate *= $ml; - $taxamount = $linetotal * $taxrate - / (1 + $taxrate); - $taxbase = ($linetotal - $taxamount); - - foreach $item (@taxaccounts) { - if (($form->{"${item}_rate"} * $ml) - > 0) { - - push @taxrates, - $form->{"${item}_rate"} - * 100; - - if ($form->{taxincluded}) { - - $taxaccounts{$item} - += $linetotal - * $form->{"${item}_rate"} - / - (1 + $taxrate); - - $taxbase{$item} - += $taxbase; - } else { - $taxbase{$item} - += $linetotal; - - $taxaccounts{$item} - += $linetotal - * $form->{"${item}_rate"}; - - } - } - } - + $taxamount = Tax::calculate_taxes(\@taxaccounts, + $form, $linetotal, 1); + $taxbase = Tax::extract_taxes(\@taxaccounts, + $form, $linetotal); + foreach $item (@taxaccounts) { + push @taxrates, Math::BigFloat->new(100) * + $item->rate; if ($form->{taxincluded}) { - $tax += $linetotal - * ($taxrate - / (1 + ($taxrate * $ml))); + $taxaccounts{$item->account} += + $item->value; + $taxbase{$item->account} += $taxbase; } else { - $tax += $linetotal * $taxrate; + Tax::apply_taxes(\@taxaccounts, $form, + $linetotal); + $taxbase{$item->account} += $linetotal; + $taxaccounts{$item->account} += + $item->value; } - - $ml *= -1; + } + if ($form->{taxincluded}) { + $tax += Tax::calculate_taxes(\@taxaccounts, + $form, $linetotal, 1); + } else { + $tax += Tax::calculate_taxes(\@taxaccounts, + $form, $linetotal, 0); } push(@{ $form->{lineitems} }, @@ -2422,19 +2380,24 @@ sub generate_orders { $sth->execute($parts_id) || $form->dberror($query); my $rate = 0; + my $taxes = ''; while ($ref = $sth->fetchrow_hashref(NAME_lc)) { $description = $ref->{description}; $unit = $ref->{unit}; $rate += $tax{$ref->{accno}}; + $taxes .= "$ref->{accno} "; } - $sth->finish; + $sth->finish; + chop $taxes; + my @taxaccounts = Tax::init_taxes($form, $taxes); $netamount += $linetotal; if ($taxincluded) { $amount += $linetotal; } else { $amount += $form->round_amount( - $linetotal * (1 + $rate), 2); + Tax::apply_taxes(\@taxaccounts, $form, + $linetotal), 2); } diff --git a/LedgerSMB/Tax.pm b/LedgerSMB/Tax.pm new file mode 100755 index 00000000..4c7bd121 --- /dev/null +++ b/LedgerSMB/Tax.pm @@ -0,0 +1,100 @@ +#===================================================================== +# +# Tax support module for LedgerSMB +# LedgerSMB::Tax +# Default simple tax application +# +# LedgerSMB +# Small Medium Business Accounting software +# http://www.ledgersmb.org/ +# +# +# Copyright (C) 2006 +# This work contains copyrighted information from a number of sources all used +# with permission. It is released under the GNU General Public License +# Version 2 or, at your option, any later version. See COPYRIGHT file for +# details. +# +# +#====================================================================== +# This package contains tax related functions: +# +# apply_taxes - applies taxes to the given subtotal +# extract_taxes - extracts taxes from the given total +# initialize_taxes - loads taxes from the database +# calculate_taxes - calculates taxes +# +#==================================================================== +package Tax; + +use Math::BigFloat; + +sub init_taxes { + my ($form, $taxaccounts) = @_; + my $dbh = $form->{dbh}; + @taxes = (); + my @accounts = split / /, $taxaccounts; + my $query = qq|SELECT t.taxnumber, c.description, + t.rate, t.chart_id, t.pass, m.taxmodulename + FROM tax t INNER JOIN chart c ON (t.chart_id = c.id) + INNER JOIN taxmodule m ON (t.taxmodule_id = m.taxmodule_id) + WHERE c.accno = ?|; + my $sth = $dbh->prepare($query); + foreach $taxaccount (@accounts) { + $sth->execute(int($taxaccount)) || $form->dberror($query); + my $ref = $sth->fetchrow_hashref; + + my $module = $ref->{'taxmodulename'}; + require "LedgerSMB/Taxes/${module}.pm"; + $module =~ s/\//::/g; + my $tax = (eval 'Taxes::'.$module)->new(); + + $tax->pass($ref->{'pass'}); + $tax->account($taxaccount); + $tax->rate(Math::BigFloat->new($ref->{'rate'})); + $tax->taxnumber($ref->{'taxnumber'}); + $tax->chart($ref->{'chart'}); + $tax->description($ref->{'description'}); + $tax->value(Math::BigFloat->bzero()); + + push @taxes, $tax; + $sth->finish; + } + return @taxes; +} + +sub calculate_taxes { + my ($taxes, $form, $subtotal, $extract) = @_; + my $total = Math::BigFloat->bzero(); + my %passes; + foreach my $tax (@taxes) { + push @{$passes{$tax->pass}}, $tax; + } + my @passkeys = sort keys %passes; + @passkeys = reverse @passkeys if $extract; + foreach my $pass (@passkeys) { + my $passrate = Math::BigFloat->bzero(); + my $passtotal = Math::BigFloat->bzero(); + foreach my $tax (@{$passes{$pass}}) { + $passrate += $tax->rate; + } + foreach my $tax (@{$passes{$pass}}) { + $passtotal += $tax->apply_tax($form, $subtotal + $total) if not $extract; + $passtotal += $tax->extract_tax($form, $subtotal - $total, $passrate) if $extract; + } + $total += $passtotal; + } + return $total; +} + +sub apply_taxes { + my ($taxes, $form, $subtotal) = @_; + return $subtotal + calculate_taxes($taxes, $form, $subtotal, 0); +} + +sub extract_taxes { + my ($taxes, $form, $subtotal) = @_; + return $subtotal - calculate_taxes($taxes, $form, $subtotal, 1); +} + +1; diff --git a/LedgerSMB/Taxes/Simple.pm b/LedgerSMB/Taxes/Simple.pm new file mode 100755 index 00000000..57777be4 --- /dev/null +++ b/LedgerSMB/Taxes/Simple.pm @@ -0,0 +1,64 @@ +#===================================================================== +# +# Simple Tax support module for LedgerSMB +# Taxes::Simple +# Default simple tax application +# +# LedgerSMB +# Small Medium Business Accounting software +# http://www.ledgersmb.org/ +# +# +# Copyright (C) 2006 +# This work contains copyrighted information from a number of sources all used +# with permission. It is released under the GNU General Public License +# Version 2 or, at your option, any later version. See COPYRIGHT file for +# details. +# +# +#====================================================================== +# This package contains tax related functions: +# +# calculate_tax - calculates tax on subtotal +# apply_tax - sets $value to the tax value for the subtotal +# extract_tax - sets $value to the tax value on a tax-included subtotal +# +#==================================================================== +package Taxes::Simple; + +use Class::Struct; +use Math::BigFloat; + +struct Taxes::Simple => { + taxnumber => '$', + description => '$', + rate => 'Math::BigFloat', + chart => '$', + account => '$', + value => 'Math::BigFloat', + pass => '$' +}; + +sub calculate_tax { + my ($self, $form, $subtotal, $extract, $passrate) = @_; + my $rate = $self->rate; + my $tax = $subtotal * $rate / (Math::BigFloat->bone() + $passrate); + $tax = $subtotal * $rate if not $extract; + return $tax; +} + +sub apply_tax { + my ($self, $form, $subtotal) = @_; + my $tax = $self->calculate_tax($form, $subtotal, 0); + $self->value($tax); + return $tax; +} + +sub extract_tax { + my ($self, $form, $subtotal, $passrate) = @_; + my $tax = $self->calculate_tax($form, $subtotal, 1, $passrate); + $self->value($tax); + return $tax; +} + +1; -- cgit v1.2.3