Basisversion des Kalkulators für Beträge, Preise und Steuern
[kivitendo-erp.git] / SL / DB / Helper / PriceTaxCalculator.pm
1 package SL::DB::Helper::PriceTaxCalculator;
2
3 use strict;
4
5 use parent qw(Exporter);
6 our @EXPORT = qw(calculate_prices_and_taxes);
7
8 use Carp;
9 use List::Util qw(sum);
10 use SL::DB::PriceFactor;
11 use SL::DB::Unit;
12
13 sub calculate_prices_and_taxes {
14   my ($self) = @_;
15
16   my $is_sales            = $self->can('customer') && $self->customer;
17
18   my %units_by_name       = map { ( $_->name => $_ ) } @{ SL::DB::Manager::Unit->get_all        };
19   my %price_factors_by_id = map { ( $_->id   => $_ ) } @{ SL::DB::Manager::PriceFactor->get_all };
20   my %taxes_by_chart_id   = ();
21
22   $self->netamount(  0);
23   $self->marge_total(0);
24   my $lastcost_total = 0;
25
26   my $idx = 0;
27
28   foreach my $item ($self->items) {
29     $idx++;
30
31     my $part_unit  = $units_by_name{ $item->part->unit };
32     my $item_unit  = $units_by_name{ $item->unit       };
33
34     croak("Undefined unit " . $item->part->unit) if !$part_unit;
35     croak("Undefined unit " . $item->unit)       if !$item_unit;
36
37     $item->base_qty($item_unit->convert_to($item->qty, $part_unit));
38
39     my $num_dec   = num_decimal_places($item->sellprice);
40     my $discount  = round($item->sellprice * ($item->discount || 0), $num_dec);
41     my $sellprice = round($item->sellprice - $discount,              $num_dec);
42
43     $item->price_factor(      ! $item->price_factor_obj   ? 1 : ($item->price_factor_obj->factor   || 1));
44     $item->marge_price_factor(! $item->part->price_factor ? 1 : ($item->part->price_factor->factor || 1));
45     my $linetotal = round($sellprice * $item->qty / $item->price_factor, 2);
46
47     if (!$linetotal) {
48       $item->marge_total(  0);
49       $item->marge_percent(0);
50
51     } else {
52       my $lastcost = ! ($item->lastcost * 1) ? ($item->part->lastcost || 0) : $item->lastcost;
53
54       $item->marge_total(  $linetotal - $lastcost / $item->marge_price_factor);
55       $item->marge_percent($item->marge_total * 100 / $linetotal);
56
57       $self->marge_total(  $self->marge_total + $item->marge_total);
58       $lastcost_total += $lastcost;
59     }
60
61     my $taxkey     = $item->part->get_taxkey(date => $self->transdate, is_sales => $is_sales, taxzone => $self->taxzone_id);
62     my $tax_rate   = $taxkey->tax->rate;
63     my $tax_amount = undef;
64
65     if ($self->taxincluded) {
66       $tax_amount = $linetotal * $tax_rate / ($tax_rate + 1);
67       $sellprice  = $sellprice             / ($tax_rate + 1);
68
69     } else {
70       $tax_amount = $linetotal * $tax_rate;
71     }
72
73     $taxes_by_chart_id{ $taxkey->chart_id } ||= 0;
74     $taxes_by_chart_id{ $taxkey->chart_id }  += $tax_amount;
75
76     $self->netamount($self->netamount + $sellprice * $item->qty / $item->price_factor);
77
78     $::lxdebug->message(0, "CALCULATE! ${idx} i.qty " . $item->qty . " i.sellprice " . $item->sellprice . " sellprice $sellprice taxamount $tax_amount " .
79                         "i.linetotal $linetotal netamount " . $self->netamount . " marge_total " . $item->marge_total . " marge_percent " . $item->marge_percent);
80   }
81
82   my $tax_sum = sum map { round($_, 2) } values %taxes_by_chart_id;
83
84   $self->amount(       round($self->netamount + $tax_sum, 2));
85   $self->netamount(    round($self->netamount,            2));
86   $self->marge_percent($self->netamount ? ($self->netamount - $lastcost_total) * 100 / $self->netamount : 0);
87
88   return $self unless wantarray;
89   return ( self  => $self,
90            taxes => \%taxes_by_chart_id,
91          );
92 }
93
94 sub round {
95   return $::form->round_amount(@_);
96 }
97
98 sub num_decimal_places {
99   return length( (split(/\./, '' . shift, 2))[1] || '' );
100 }
101
102 1;
103 __END__
104
105 =pod
106
107 =encoding utf8
108
109 =head1 NAME
110
111 SL::DB::Helper::PriceTaxCalculator - Mixin for calculating the prices,
112 amounts and taxes of orders, quotations, invoices
113
114 =head1 FUNCTIONS
115
116 =over 4
117
118 =item C<calculate_prices_and_taxes %params>
119
120 Calculates the prices, amounts and taxes for an order, a quotation or
121 an invoice. The function assumes that the mixing package has a certain
122 layout and provides certain functions:
123
124 =over 2
125
126 =item C<transdate>
127
128 The record's date.
129
130 =item C<customer> or C<vendor>
131
132 Determines if the record is a sales or purchase record.
133
134 =item C<items>
135
136 Accessor returning all line items for this record. The line items
137 themselves must again have a certain layout. Instances of
138 L<SL::DB::OrderItem> and L<SL::DB::InvoiceItem> are supported.
139
140 =back
141
142 The following values are calculated and set for C<$self>: C<amount>,
143 C<netamount>, C<marge_percent>, C<marge_total>.
144
145 The following values are calculated and set for each line item:
146 C<base_qty>, C<price_factor>, C<marge_price_factor>, C<marge_total>,
147 C<marge_percent>.
148
149 The objects are not saved.
150
151 Returns C<$self> in scalar context.
152
153 In array context a hash with the following keys is returned:
154
155 =over 2
156
157 =item C<self>
158
159 The object itself.
160
161 =item C<taxes>
162
163 A hash reference with the calculated taxes. The keys are chart IDs,
164 the values the calculated taxes.
165
166 =back
167
168 =back
169
170 =head1 BUGS
171
172 Nothing here yet.
173
174 =head1 AUTHOR
175
176 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
177
178 =cut