Merge branch 'master' of github.com:kivitendo/kivitendo-erp
[kivitendo-erp.git] / SL / IS.pm
1 #=====================================================================
2 # LX-Office ERP
3 # Copyright (C) 2004
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
6 #
7 #=====================================================================
8 # SQL-Ledger Accounting
9 # Copyright (C) 1998-2002
10 #
11 #  Author: Dieter Simader
12 #   Email: dsimader@sql-ledger.org
13 #     Web: http://www.sql-ledger.org
14 #
15 #  Contributors:
16 #
17 # This program is free software; you can redistribute it and/or modify
18 # it under the terms of the GNU General Public License as published by
19 # the Free Software Foundation; either version 2 of the License, or
20 # (at your option) any later version.
21 #
22 # This program is distributed in the hope that it will be useful,
23 # but WITHOUT ANY WARRANTY; without even the implied warranty of
24 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25 # GNU General Public License for more details.
26 # You should have received a copy of the GNU General Public License
27 # along with this program; if not, write to the Free Software
28 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 #======================================================================
30 #
31 # Inventory invoicing module
32 #
33 #======================================================================
34
35 package IS;
36
37 use List::Util qw(max);
38
39 use SL::AM;
40 use SL::ARAP;
41 use SL::CVar;
42 use SL::Common;
43 use SL::DATEV qw(:CONSTANTS);
44 use SL::DBUtils;
45 use SL::DO;
46 use SL::GenericTranslations;
47 use SL::MoreCommon;
48 use SL::IC;
49 use SL::IO;
50 use SL::TransNumber;
51 use SL::DB::Default;
52 use SL::DB::Tax;
53 use Data::Dumper;
54
55 use strict;
56
57 sub invoice_details {
58   $main::lxdebug->enter_sub();
59
60   my ($self, $myconfig, $form, $locale) = @_;
61
62   $form->{duedate} ||= $form->{invdate};
63
64   # connect to database
65   my $dbh = $form->get_standard_dbh;
66   my $sth;
67
68   my $query = qq|SELECT date | . conv_dateq($form->{duedate}) . qq| - date | . conv_dateq($form->{invdate}) . qq| AS terms|;
69   ($form->{terms}) = selectrow_query($form, $dbh, $query);
70
71   my (@project_ids, %projectnumbers, %projectdescriptions);
72   $form->{TEMPLATE_ARRAYS} = {};
73
74   push(@project_ids, $form->{"globalproject_id"}) if ($form->{"globalproject_id"});
75
76   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
77   my %price_factors;
78
79   foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
80     $price_factors{$pfac->{id}}  = $pfac;
81     $pfac->{factor}             *= 1;
82     $pfac->{formatted_factor}    = $form->format_amount($myconfig, $pfac->{factor});
83   }
84
85   # sort items by partsgroup
86   for my $i (1 .. $form->{rowcount}) {
87 #    $partsgroup = "";
88 #    if ($form->{"partsgroup_$i"} && $form->{groupitems}) {
89 #      $partsgroup = $form->{"partsgroup_$i"};
90 #    }
91 #    push @partsgroup, [$i, $partsgroup];
92     push(@project_ids, $form->{"project_id_$i"}) if ($form->{"project_id_$i"});
93   }
94
95   if (@project_ids) {
96     $query = "SELECT id, projectnumber, description FROM project WHERE id IN (" .
97       join(", ", map({ "?" } @project_ids)) . ")";
98     $sth = $dbh->prepare($query);
99     $sth->execute(@project_ids) ||
100       $form->dberror($query . " (" . join(", ", @project_ids) . ")");
101     while (my $ref = $sth->fetchrow_hashref()) {
102       $projectnumbers{$ref->{id}} = $ref->{projectnumber};
103       $projectdescriptions{$ref->{id}} = $ref->{description};
104     }
105     $sth->finish();
106   }
107
108   $form->{"globalprojectnumber"} =
109     $projectnumbers{$form->{"globalproject_id"}};
110   $form->{"globalprojectdescription"} =
111     $projectdescriptions{$form->{"globalproject_id"}};
112
113   my $tax = 0;
114   my $item;
115   my $i;
116   my @partsgroup = ();
117   my $partsgroup;
118
119   # sort items by partsgroup
120   for $i (1 .. $form->{rowcount}) {
121     $partsgroup = "";
122     if ($form->{"partsgroup_$i"} && $form->{groupitems}) {
123       $partsgroup = $form->{"partsgroup_$i"};
124     }
125     push @partsgroup, [$i, $partsgroup];
126   }
127
128   my $sameitem = "";
129   my @taxaccounts;
130   my %taxaccounts;
131   my %taxbase;
132   my $taxrate;
133   my $taxamount;
134   my $taxbase;
135   my $taxdiff;
136   my $nodiscount;
137   my $yesdiscount;
138   my $nodiscount_subtotal = 0;
139   my $discount_subtotal = 0;
140   my $position = 0;
141   my $subtotal_header = 0;
142   my $subposition = 0;
143
144   $form->{discount} = [];
145
146   IC->prepare_parts_for_printing(myconfig => $myconfig, form => $form);
147
148   my $ic_cvar_configs = CVar->get_configs(module => 'IC');
149
150   my @arrays =
151     qw(runningnumber number description longdescription qty ship unit bin
152        deliverydate_oe ordnumber_oe transdate_oe validuntil
153        partnotes serialnumber reqdate sellprice listprice netprice
154        discount p_discount discount_sub nodiscount_sub
155        linetotal  nodiscount_linetotal tax_rate projectnumber projectdescription
156        price_factor price_factor_name partsgroup weight lineweight);
157
158   push @arrays, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs };
159
160   my @tax_arrays = qw(taxbase tax taxdescription taxrate taxnumber);
161
162   my @payment_arrays = qw(payment paymentaccount paymentdate paymentsource paymentmemo);
163
164   map { $form->{TEMPLATE_ARRAYS}->{$_} = [] } (@arrays, @tax_arrays, @payment_arrays);
165
166   my $totalweight = 0;
167   foreach $item (sort { $a->[1] cmp $b->[1] } @partsgroup) {
168     $i = $item->[0];
169
170     if ($item->[1] ne $sameitem) {
171       push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, qq|$item->[1]|);
172       $sameitem = $item->[1];
173
174       map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } @arrays));
175     }
176
177     $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"});
178
179     if ($form->{"id_$i"} != 0) {
180
181       # add number, description and qty to $form->{number},
182       if ($form->{"subtotal_$i"} && !$subtotal_header) {
183         $subtotal_header = $i;
184         $position = int($position);
185         $subposition = 0;
186         $position++;
187       } elsif ($subtotal_header) {
188         $subposition += 1;
189         $position = int($position);
190         $position = $position.".".$subposition;
191       } else {
192         $position = int($position);
193         $position++;
194       }
195
196       my $price_factor = $price_factors{$form->{"price_factor_id_$i"}} || { 'factor' => 1 };
197
198       push @{ $form->{TEMPLATE_ARRAYS}->{runningnumber} },     $position;
199       push @{ $form->{TEMPLATE_ARRAYS}->{number} },            $form->{"partnumber_$i"};
200       push @{ $form->{TEMPLATE_ARRAYS}->{serialnumber} },      $form->{"serialnumber_$i"};
201       push @{ $form->{TEMPLATE_ARRAYS}->{bin} },               $form->{"bin_$i"};
202       push @{ $form->{TEMPLATE_ARRAYS}->{partnotes} },         $form->{"partnotes_$i"};
203       push @{ $form->{TEMPLATE_ARRAYS}->{description} },       $form->{"description_$i"};
204       push @{ $form->{TEMPLATE_ARRAYS}->{longdescription} },   $form->{"longdescription_$i"};
205       push @{ $form->{TEMPLATE_ARRAYS}->{qty} },               $form->format_amount($myconfig, $form->{"qty_$i"});
206       push @{ $form->{TEMPLATE_ARRAYS}->{qty_nofmt} },         $form->{"qty_$i"};
207       push @{ $form->{TEMPLATE_ARRAYS}->{unit} },              $form->{"unit_$i"};
208       push @{ $form->{TEMPLATE_ARRAYS}->{deliverydate_oe} },   $form->{"reqdate_$i"};
209       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice} },         $form->{"sellprice_$i"};
210       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice_nofmt} },   $form->parse_amount($myconfig, $form->{"sellprice_$i"});
211       push @{ $form->{TEMPLATE_ARRAYS}->{ordnumber_oe} },      $form->{"ordnumber_$i"};
212       push @{ $form->{TEMPLATE_ARRAYS}->{transdate_oe} },      $form->{"transdate_$i"};
213       push @{ $form->{TEMPLATE_ARRAYS}->{invnumber} },         $form->{"invnumber"};
214       push @{ $form->{TEMPLATE_ARRAYS}->{invdate} },           $form->{"invdate"};
215       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor} },      $price_factor->{formatted_factor};
216       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor_name} }, $price_factor->{description};
217       push @{ $form->{TEMPLATE_ARRAYS}->{partsgroup} },        $form->{"partsgroup_$i"};
218       push @{ $form->{TEMPLATE_ARRAYS}->{reqdate} },           $form->{"reqdate_$i"};
219       push(@{ $form->{TEMPLATE_ARRAYS}->{listprice} },         $form->{"listprice_$i"});
220
221       my $sellprice     = $form->parse_amount($myconfig, $form->{"sellprice_$i"});
222       my ($dec)         = ($sellprice =~ /\.(\d+)/);
223       my $decimalplaces = max 2, length($dec);
224
225       my $parsed_discount            = $form->parse_amount($myconfig, $form->{"discount_$i"});
226
227       my $linetotal_exact            = $form->{"qty_$i"} * $sellprice * (100 - $parsed_discount) / 100 / $price_factor->{factor};
228       my $linetotal                  = $form->round_amount($linetotal_exact, 2);
229
230       my $nodiscount_exact_linetotal = $form->{"qty_$i"} * $sellprice                                  / $price_factor->{factor};
231       my $nodiscount_linetotal       = $form->round_amount($nodiscount_exact_linetotal,2);
232
233       my $discount                   = $nodiscount_linetotal - $linetotal; # is always rounded because $nodiscount_linetotal and $linetotal are rounded
234
235       my $discount_round_error       = $discount + ($linetotal_exact - $nodiscount_exact_linetotal); # not used
236
237       $form->{"netprice_$i"}   = $form->round_amount($form->{"qty_$i"} ? ($linetotal / $form->{"qty_$i"}) : 0, 2);
238
239       push @{ $form->{TEMPLATE_ARRAYS}->{netprice} },       ($form->{"netprice_$i"} != 0) ? $form->format_amount($myconfig, $form->{"netprice_$i"}, $decimalplaces) : '';
240       push @{ $form->{TEMPLATE_ARRAYS}->{netprice_nofmt} }, ($form->{"netprice_$i"} != 0) ? $form->{"netprice_$i"} : '';
241
242       $linetotal = ($linetotal != 0) ? $linetotal : '';
243
244       push @{ $form->{TEMPLATE_ARRAYS}->{discount} },       ($discount != 0) ? $form->format_amount($myconfig, $discount * -1, 2) : '';
245       push @{ $form->{TEMPLATE_ARRAYS}->{discount_nofmt} }, ($discount != 0) ? $discount * -1 : '';
246       push @{ $form->{TEMPLATE_ARRAYS}->{p_discount} },     $form->{"discount_$i"};
247
248       $form->{total}            += $linetotal;
249       $form->{nodiscount_total} += $nodiscount_linetotal;
250       $form->{discount_total}   += $discount;
251
252       if ($subtotal_header) {
253         $discount_subtotal   += $linetotal;
254         $nodiscount_subtotal += $nodiscount_linetotal;
255       }
256
257       if ($form->{"subtotal_$i"} && $subtotal_header && ($subtotal_header != $i)) {
258         push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub} },         $form->format_amount($myconfig, $discount_subtotal,   2);
259         push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub_nofmt} },   $discount_subtotal;
260         push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub} },       $form->format_amount($myconfig, $nodiscount_subtotal, 2);
261         push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub_nofmt} }, $nodiscount_subtotal;
262
263         $discount_subtotal   = 0;
264         $nodiscount_subtotal = 0;
265         $subtotal_header     = 0;
266
267       } else {
268         push @{ $form->{TEMPLATE_ARRAYS}->{$_} }, "" for qw(discount_sub nodiscount_sub discount_sub_nofmt nodiscount_sub_nofmt);
269       }
270
271       if (!$form->{"discount_$i"}) {
272         $nodiscount += $linetotal;
273       }
274
275       push @{ $form->{TEMPLATE_ARRAYS}->{linetotal} },                  $form->format_amount($myconfig, $linetotal, 2);
276       push @{ $form->{TEMPLATE_ARRAYS}->{linetotal_nofmt} },            $linetotal_exact;
277       push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal} },       $form->format_amount($myconfig, $nodiscount_linetotal, 2);
278       push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal_nofmt} }, $nodiscount_linetotal;
279
280       push(@{ $form->{TEMPLATE_ARRAYS}->{projectnumber} },              $projectnumbers{$form->{"project_id_$i"}});
281       push(@{ $form->{TEMPLATE_ARRAYS}->{projectdescription} },         $projectdescriptions{$form->{"project_id_$i"}});
282
283       my $lineweight = $form->{"qty_$i"} * $form->{"weight_$i"};
284       $totalweight += $lineweight;
285       push @{ $form->{TEMPLATE_ARRAYS}->{weight} },            $form->format_amount($myconfig, $form->{"weight_$i"}, 3);
286       push @{ $form->{TEMPLATE_ARRAYS}->{weight_nofmt} },      $form->{"weight_$i"};
287       push @{ $form->{TEMPLATE_ARRAYS}->{lineweight} },        $form->format_amount($myconfig, $lineweight, 3);
288       push @{ $form->{TEMPLATE_ARRAYS}->{lineweight_nofmt} },  $lineweight;
289
290       @taxaccounts = split(/ /, $form->{"taxaccounts_$i"});
291       $taxrate     = 0;
292       $taxdiff     = 0;
293
294       map { $taxrate += $form->{"${_}_rate"} } @taxaccounts;
295
296       if ($form->{taxincluded}) {
297
298         # calculate tax
299         $taxamount = $linetotal * $taxrate / (1 + $taxrate);
300         $taxbase = $linetotal - $taxamount;
301       } else {
302         $taxamount = $linetotal * $taxrate;
303         $taxbase   = $linetotal;
304       }
305
306       if ($form->round_amount($taxrate, 7) == 0) {
307         if ($form->{taxincluded}) {
308           foreach my $accno (@taxaccounts) {
309             $taxamount            = $form->round_amount($linetotal * $form->{"${accno}_rate"} / (1 + abs($form->{"${accno}_rate"})), 2);
310
311             $taxaccounts{$accno} += $taxamount;
312             $taxdiff             += $taxamount;
313
314             $taxbase{$accno}     += $taxbase;
315           }
316           $taxaccounts{ $taxaccounts[0] } += $taxdiff;
317         } else {
318           foreach my $accno (@taxaccounts) {
319             $taxaccounts{$accno} += $linetotal * $form->{"${accno}_rate"};
320             $taxbase{$accno}     += $taxbase;
321           }
322         }
323       } else {
324         foreach my $accno (@taxaccounts) {
325           $taxaccounts{$accno} += $taxamount * $form->{"${accno}_rate"} / $taxrate;
326           $taxbase{$accno}     += $taxbase;
327         }
328       }
329       my $tax_rate = $taxrate * 100;
330       push(@{ $form->{TEMPLATE_ARRAYS}->{tax_rate} }, qq|$tax_rate|);
331       if ($form->{"assembly_$i"}) {
332         $sameitem = "";
333
334         # get parts and push them onto the stack
335         my $sortorder = "";
336         if ($form->{groupitems}) {
337           $sortorder =
338             qq|ORDER BY pg.partsgroup, a.oid|;
339         } else {
340           $sortorder = qq|ORDER BY a.oid|;
341         }
342
343         $query =
344           qq|SELECT p.partnumber, p.description, p.unit, a.qty, pg.partsgroup
345              FROM assembly a
346              JOIN parts p ON (a.parts_id = p.id)
347              LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id)
348              WHERE (a.bom = '1') AND (a.id = ?) $sortorder|;
349         $sth = prepare_execute_query($form, $dbh, $query, conv_i($form->{"id_$i"}));
350
351         while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
352           if ($form->{groupitems} && $ref->{partsgroup} ne $sameitem) {
353             map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } @arrays));
354             $sameitem = ($ref->{partsgroup}) ? $ref->{partsgroup} : "--";
355             push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, $sameitem);
356           }
357
358           map { $form->{"a_$_"} = $ref->{$_} } qw(partnumber description);
359
360           push(@{ $form->{TEMPLATE_ARRAYS}->{description} },
361                $form->format_amount($myconfig, $ref->{qty} * $form->{"qty_$i"}
362                  )
363                  . qq| -- $form->{"a_partnumber"}, $form->{"a_description"}|);
364           map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } @arrays));
365
366         }
367         $sth->finish;
368       }
369
370       push @{ $form->{TEMPLATE_ARRAYS}->{"ic_cvar_$_->{name}"} },
371         CVar->format_to_template(CVar->parse($form->{"ic_cvar_$_->{name}_$i"}, $_), $_)
372           for @{ $ic_cvar_configs };
373     }
374   }
375
376   $form->{totalweight}       = $form->format_amount($myconfig, $totalweight, 3);
377   $form->{totalweight_nofmt} = $totalweight;
378   my $defaults = AM->get_defaults();
379   $form->{weightunit}        = $defaults->{weightunit};
380
381   foreach my $item (sort keys %taxaccounts) {
382     $tax += $taxamount = $form->round_amount($taxaccounts{$item}, 2);
383
384     push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase} },        $form->format_amount($myconfig, $taxbase{$item}, 2));
385     push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase_nofmt} },  $taxbase{$item});
386     push(@{ $form->{TEMPLATE_ARRAYS}->{tax} },            $form->format_amount($myconfig, $taxamount,      2));
387     push(@{ $form->{TEMPLATE_ARRAYS}->{tax_nofmt} },      $taxamount );
388     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate} },        $form->format_amount($myconfig, $form->{"${item}_rate"} * 100));
389     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate_nofmt} },  $form->{"${item}_rate"} * 100);
390     push(@{ $form->{TEMPLATE_ARRAYS}->{taxnumber} },      $form->{"${item}_taxnumber"});
391
392     my $tax_obj     = SL::DB::Manager::Tax->find_by(taxnumber => $form->{"${item}_taxnumber"});
393     my $description = $tax_obj->translated_attribute('taxdescription',  $form->{language_id}, 0) if $tax_obj;
394     push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
395   }
396
397   for my $i (1 .. $form->{paidaccounts}) {
398     if ($form->{"paid_$i"}) {
399       my ($accno, $description) = split(/--/, $form->{"AR_paid_$i"});
400
401       push(@{ $form->{TEMPLATE_ARRAYS}->{payment} },        $form->{"paid_$i"});
402       push(@{ $form->{TEMPLATE_ARRAYS}->{paymentaccount} }, $description);
403       push(@{ $form->{TEMPLATE_ARRAYS}->{paymentdate} },    $form->{"datepaid_$i"});
404       push(@{ $form->{TEMPLATE_ARRAYS}->{paymentsource} },  $form->{"source_$i"});
405       push(@{ $form->{TEMPLATE_ARRAYS}->{paymentmemo} },    $form->{"memo_$i"});
406
407       $form->{paid} += $form->parse_amount($myconfig, $form->{"paid_$i"});
408     }
409   }
410   if($form->{taxincluded}) {
411     $form->{subtotal}       = $form->format_amount($myconfig, $form->{total} - $tax, 2);
412     $form->{subtotal_nofmt} = $form->{total} - $tax;
413   }
414   else {
415     $form->{subtotal}       = $form->format_amount($myconfig, $form->{total}, 2);
416     $form->{subtotal_nofmt} = $form->{total};
417   }
418
419   $form->{nodiscount_subtotal} = $form->format_amount($myconfig, $form->{nodiscount_total}, 2);
420   $form->{discount_total}      = $form->format_amount($myconfig, $form->{discount_total}, 2);
421   $form->{nodiscount}          = $form->format_amount($myconfig, $nodiscount, 2);
422   $form->{yesdiscount}         = $form->format_amount($myconfig, $form->{nodiscount_total} - $nodiscount, 2);
423
424   $form->{invtotal} = ($form->{taxincluded}) ? $form->{total} : $form->{total} + $tax;
425   $form->{total}    = $form->format_amount($myconfig, $form->{invtotal} - $form->{paid}, 2);
426
427   $form->{invtotal} = $form->format_amount($myconfig, $form->{invtotal}, 2);
428   $form->{paid}     = $form->format_amount($myconfig, $form->{paid}, 2);
429
430   $form->set_payment_options($myconfig, $form->{invdate});
431
432   $form->{delivery_term} = SL::DB::Manager::DeliveryTerm->find_by(id => $form->{delivery_term_id} || undef);
433   $form->{delivery_term}->description_long($form->{delivery_term}->translated_attribute('description_long', $form->{language_id})) if $form->{delivery_term} && $form->{language_id};
434
435   $form->{username} = $myconfig->{name};
436
437   $main::lxdebug->leave_sub();
438 }
439
440 sub project_description {
441   $main::lxdebug->enter_sub();
442
443   my ($self, $dbh, $id) = @_;
444   my $form = \%main::form;
445
446   my $query = qq|SELECT description FROM project WHERE id = ?|;
447   my ($description) = selectrow_query($form, $dbh, $query, conv_i($id));
448
449   $main::lxdebug->leave_sub();
450
451   return $_;
452 }
453
454 sub customer_details {
455   $main::lxdebug->enter_sub();
456
457   my ($self, $myconfig, $form, @wanted_vars) = @_;
458
459   # connect to database
460   my $dbh = $form->get_standard_dbh;
461
462   my $language_id = $form->{language_id};
463
464   # get contact id, set it if nessessary
465   $form->{cp_id} *= 1;
466
467   my @values =  (conv_i($form->{customer_id}));
468
469   my $where = "";
470   if ($form->{cp_id}) {
471     $where = qq| AND (cp.cp_id = ?) |;
472     push(@values, conv_i($form->{cp_id}));
473   }
474
475   # get rest for the customer
476   my $query =
477     qq|SELECT ct.*, cp.*, ct.notes as customernotes,
478          ct.phone AS customerphone, ct.fax AS customerfax, ct.email AS customeremail,
479          cu.name AS currency
480        FROM customer ct
481        LEFT JOIN contacts cp on ct.id = cp.cp_cv_id
482        LEFT JOIN currencies cu ON (ct.currency_id = cu.id)
483        WHERE (ct.id = ?) $where
484        ORDER BY cp.cp_id
485        LIMIT 1|;
486   my $ref = selectfirst_hashref_query($form, $dbh, $query, @values);
487
488   # remove id and taxincluded before copy back
489   delete @$ref{qw(id taxincluded)};
490
491   @wanted_vars = grep({ $_ } @wanted_vars);
492   if (scalar(@wanted_vars) > 0) {
493     my %h_wanted_vars;
494     map({ $h_wanted_vars{$_} = 1; } @wanted_vars);
495     map({ delete($ref->{$_}) unless ($h_wanted_vars{$_}); } keys(%{$ref}));
496   }
497
498   map { $form->{$_} = $ref->{$_} } keys %$ref;
499
500   if ($form->{delivery_customer_id}) {
501     $query =
502       qq|SELECT *, notes as customernotes
503          FROM customer
504          WHERE id = ?
505          LIMIT 1|;
506     $ref = selectfirst_hashref_query($form, $dbh, $query, conv_i($form->{delivery_customer_id}));
507
508     map { $form->{"dc_$_"} = $ref->{$_} } keys %$ref;
509   }
510
511   if ($form->{delivery_vendor_id}) {
512     $query =
513       qq|SELECT *, notes as customernotes
514          FROM customer
515          WHERE id = ?
516          LIMIT 1|;
517     $ref = selectfirst_hashref_query($form, $dbh, $query, conv_i($form->{delivery_vendor_id}));
518
519     map { $form->{"dv_$_"} = $ref->{$_} } keys %$ref;
520   }
521
522   my $custom_variables = CVar->get_custom_variables('dbh'      => $dbh,
523                                                     'module'   => 'CT',
524                                                     'trans_id' => $form->{customer_id});
525   map { $form->{"vc_cvar_$_->{name}"} = $_->{value} } @{ $custom_variables };
526
527   $form->{cp_greeting} = GenericTranslations->get('dbh'              => $dbh,
528                                                   'translation_type' => 'greetings::' . ($form->{cp_gender} eq 'f' ? 'female' : 'male'),
529                                                   'language_id'      => $language_id,
530                                                   'allow_fallback'   => 1);
531
532
533   $main::lxdebug->leave_sub();
534 }
535
536 sub post_invoice {
537   $main::lxdebug->enter_sub();
538
539   my ($self, $myconfig, $form, $provided_dbh, $payments_only) = @_;
540
541   # connect to database, turn off autocommit
542   my $dbh = $provided_dbh ? $provided_dbh : $form->get_standard_dbh;
543
544   my ($query, $sth, $null, $project_id, @values);
545   my $exchangerate = 0;
546
547   my $ic_cvar_configs = CVar->get_configs(module => 'IC',
548                                           dbh    => $dbh);
549
550   if (!$form->{employee_id}) {
551     $form->get_employee($dbh);
552   }
553
554   $form->{defaultcurrency} = $form->get_default_currency($myconfig);
555   my $defaultcurrency = $form->{defaultcurrency};
556
557   # Seit neuestem wird die department_id schon Ã¼bergeben UND $form->department nicht mehr
558   # korrekt zusammengebaut. Sehr wahrscheinlich beim Umstieg auf T8 kaputt gegangen
559   # Ich lass den Code von 2005 erstmal noch stehen ;-) jb 03-2011
560   if (!$form->{department_id}){
561     ($null, $form->{department_id}) = split(/--/, $form->{department});
562   }
563
564   my $all_units = AM->retrieve_units($myconfig, $form);
565
566   if (!$payments_only) {
567     if ($form->{id}) {
568       &reverse_invoice($dbh, $form);
569
570     } else {
571       my $trans_number   = SL::TransNumber->new(type => $form->{type}, dbh => $dbh, number => $form->{invnumber}, save => 1);
572       $form->{invnumber} = $trans_number->create_unique unless $trans_number->is_unique;
573
574       $query = qq|SELECT nextval('glid')|;
575       ($form->{"id"}) = selectrow_query($form, $dbh, $query);
576
577       $query = qq|INSERT INTO ar (id, invnumber, currency_id) VALUES (?, ?, (SELECT id FROM currencies WHERE name=?))|;
578       do_query($form, $dbh, $query, $form->{"id"}, $form->{"id"}, $form->{currency});
579
580       if (!$form->{invnumber}) {
581         $form->{invnumber} =
582           $form->update_defaults($myconfig, $form->{type} eq "credit_note" ?
583                                  "cnnumber" : "invnumber", $dbh);
584       }
585     }
586   }
587
588   my ($netamount, $invoicediff) = (0, 0);
589   my ($amount, $linetotal, $lastincomeaccno);
590
591   if ($form->{currency} eq $defaultcurrency) {
592     $form->{exchangerate} = 1;
593   } else {
594     $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{invdate}, 'buy');
595   }
596
597   $form->{exchangerate} =
598     ($exchangerate)
599     ? $exchangerate
600     : $form->parse_amount($myconfig, $form->{exchangerate});
601
602   $form->{expense_inventory} = "";
603
604   my %baseunits;
605
606   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
607   my %price_factors = map { $_->{id} => $_->{factor} } @{ $form->{ALL_PRICE_FACTORS} };
608   my $price_factor;
609
610   $form->{amount}      = {};
611   $form->{amount_cogs} = {};
612
613   foreach my $i (1 .. $form->{rowcount}) {
614     if ($form->{type} eq "credit_note") {
615       $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"}) * -1;
616       $form->{shipped} = 1;
617     } else {
618       $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"});
619     }
620     my $basefactor;
621     my $baseqty;
622
623     $form->{"marge_percent_$i"} = $form->parse_amount($myconfig, $form->{"marge_percent_$i"}) * 1;
624     $form->{"marge_absolut_$i"} = $form->parse_amount($myconfig, $form->{"marge_absolut_$i"}) * 1;
625     $form->{"lastcost_$i"} = $form->parse_amount($myconfig, $form->{"lastcost_$i"}) * 1;
626
627     if ($form->{storno}) {
628       $form->{"qty_$i"} *= -1;
629     }
630
631     if ($form->{"id_$i"}) {
632       my $item_unit;
633
634       if (defined($baseunits{$form->{"id_$i"}})) {
635         $item_unit = $baseunits{$form->{"id_$i"}};
636       } else {
637         # get item baseunit
638         $query = qq|SELECT unit FROM parts WHERE id = ?|;
639         ($item_unit) = selectrow_query($form, $dbh, $query, conv_i($form->{"id_$i"}));
640         $baseunits{$form->{"id_$i"}} = $item_unit;
641       }
642
643       if (defined($all_units->{$item_unit}->{factor})
644           && ($all_units->{$item_unit}->{factor} ne '')
645           && ($all_units->{$item_unit}->{factor} != 0)) {
646         $basefactor = $all_units->{$form->{"unit_$i"}}->{factor} / $all_units->{$item_unit}->{factor};
647       } else {
648         $basefactor = 1;
649       }
650       $baseqty = $form->{"qty_$i"} * $basefactor;
651
652       my ($allocated, $taxrate) = (0, 0);
653       my $taxamount;
654
655       # add tax rates
656       map { $taxrate += $form->{"${_}_rate"} } split(/ /, $form->{"taxaccounts_$i"});
657
658       # keep entered selling price
659       my $fxsellprice =
660         $form->parse_amount($myconfig, $form->{"sellprice_$i"});
661
662       my ($dec) = ($fxsellprice =~ /\.(\d+)/);
663       $dec = length $dec;
664       my $decimalplaces = ($dec > 2) ? $dec : 2;
665
666       # undo discount formatting
667       $form->{"discount_$i"} = $form->parse_amount($myconfig, $form->{"discount_$i"}) / 100;
668
669       # deduct discount
670       $form->{"sellprice_$i"} = $fxsellprice * (1 - $form->{"discount_$i"});
671
672       # round linetotal to 2 decimal places
673       $price_factor = $price_factors{ $form->{"price_factor_id_$i"} } || 1;
674       $linetotal    = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / $price_factor, 2);
675
676       if ($form->{taxincluded}) {
677         $taxamount = $linetotal * ($taxrate / (1 + $taxrate));
678         $form->{"sellprice_$i"} =
679           $form->{"sellprice_$i"} * (1 / (1 + $taxrate));
680       } else {
681         $taxamount = $linetotal * $taxrate;
682       }
683
684       $netamount += $linetotal;
685
686       if ($taxamount != 0) {
687         map {
688           $form->{amount}{ $form->{id} }{$_} +=
689             $taxamount * $form->{"${_}_rate"} / $taxrate
690         } split(/ /, $form->{"taxaccounts_$i"});
691       }
692
693       # add amount to income, $form->{amount}{trans_id}{accno}
694       $amount = $form->{"sellprice_$i"} * $form->{"qty_$i"} * $form->{exchangerate} / $price_factor;
695
696       $linetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / $price_factor, 2) * $form->{exchangerate};
697       $linetotal = $form->round_amount($linetotal, 2);
698
699       # this is the difference from the inventory
700       $invoicediff += ($amount - $linetotal);
701
702       $form->{amount}{ $form->{id} }{ $form->{"income_accno_$i"} } +=
703         $linetotal;
704
705       $lastincomeaccno = $form->{"income_accno_$i"};
706
707       # adjust and round sellprice
708       $form->{"sellprice_$i"} =
709         $form->round_amount($form->{"sellprice_$i"} * $form->{exchangerate},
710                             $decimalplaces);
711
712       next if $payments_only;
713
714       if ($form->{"inventory_accno_$i"} || $form->{"assembly_$i"}) {
715
716         if ($form->{"assembly_$i"}) {
717           # record assembly item as allocated
718           &process_assembly($dbh, $myconfig, $form, $form->{"id_$i"}, $baseqty);
719
720         } else {
721           $allocated = &cogs($dbh, $myconfig, $form, $form->{"id_$i"}, $baseqty, $basefactor, $i);
722         }
723       }
724
725       # Get pricegroup_id and save it. Unfortunately the interface
726       # also uses ID "0" for signalling that none is selected, but "0"
727       # must not be stored in the database. Therefore we cannot simply
728       # use conv_i().
729       ($null, my $pricegroup_id) = split(/--/, $form->{"sellprice_pg_$i"});
730       $pricegroup_id *= 1;
731       $pricegroup_id  = undef if !$pricegroup_id;
732
733       my ($invoice_id) = selectfirst_array_query($form, $dbh, qq|SELECT nextval('invoiceid')|);
734
735       # save detail record in invoice table
736       $query =
737         qq|INSERT INTO invoice (id, trans_id, parts_id, description, longdescription, qty,
738                                 sellprice, fxsellprice, discount, allocated, assemblyitem,
739                                 unit, deliverydate, project_id, serialnumber, pricegroup_id,
740                                 ordnumber, transdate, cusordnumber, base_qty, subtotal,
741                                 marge_percent, marge_total, lastcost,
742                                 price_factor_id, price_factor, marge_price_factor)
743            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
744                    (SELECT factor FROM price_factors WHERE id = ?), ?)|;
745
746       @values = ($invoice_id, conv_i($form->{id}), conv_i($form->{"id_$i"}),
747                  $form->{"description_$i"}, $form->{"longdescription_$i"}, $form->{"qty_$i"},
748                  $form->{"sellprice_$i"}, $fxsellprice,
749                  $form->{"discount_$i"}, $allocated, 'f',
750                  $form->{"unit_$i"}, conv_date($form->{"reqdate_$i"}), conv_i($form->{"project_id_$i"}),
751                  $form->{"serialnumber_$i"}, $pricegroup_id,
752                  $form->{"ordnumber_$i"}, conv_date($form->{"transdate_$i"}),
753                  $form->{"cusordnumber_$i"}, $baseqty, $form->{"subtotal_$i"} ? 't' : 'f',
754                  $form->{"marge_percent_$i"}, $form->{"marge_absolut_$i"},
755                  $form->{"lastcost_$i"},
756                  conv_i($form->{"price_factor_id_$i"}), conv_i($form->{"price_factor_id_$i"}),
757                  conv_i($form->{"marge_price_factor_$i"}));
758       do_query($form, $dbh, $query, @values);
759
760       CVar->save_custom_variables(module       => 'IC',
761                                   sub_module   => 'invoice',
762                                   trans_id     => $invoice_id,
763                                   configs      => $ic_cvar_configs,
764                                   variables    => $form,
765                                   name_prefix  => 'ic_',
766                                   name_postfix => "_$i",
767                                   dbh          => $dbh);
768     }
769   }
770
771   # total payments, don't move we need it here
772   for my $i (1 .. $form->{paidaccounts}) {
773     if ($form->{type} eq "credit_note") {
774       $form->{"paid_$i"} = $form->parse_amount($myconfig, $form->{"paid_$i"}) * -1;
775     } else {
776       $form->{"paid_$i"} = $form->parse_amount($myconfig, $form->{"paid_$i"});
777     }
778     $form->{paid} += $form->{"paid_$i"};
779     $form->{datepaid} = $form->{"datepaid_$i"} if ($form->{"datepaid_$i"});
780   }
781
782   my ($tax, $diff) = (0, 0);
783
784   $netamount = $form->round_amount($netamount, 2);
785
786   # figure out rounding errors for total amount vs netamount + taxes
787   if ($form->{taxincluded}) {
788
789     $amount = $form->round_amount($netamount * $form->{exchangerate}, 2);
790     $diff += $amount - $netamount * $form->{exchangerate};
791     $netamount = $amount;
792
793     foreach my $item (split(/ /, $form->{taxaccounts})) {
794       $amount = $form->{amount}{ $form->{id} }{$item} * $form->{exchangerate};
795       $form->{amount}{ $form->{id} }{$item} = $form->round_amount($amount, 2);
796       $tax += $form->{amount}{ $form->{id} }{$item};
797       $netamount -= $form->{amount}{ $form->{id} }{$item};
798     }
799
800     $invoicediff += $diff;
801     ######## this only applies to tax included
802     if ($lastincomeaccno) {
803       $form->{amount}{ $form->{id} }{$lastincomeaccno} += $invoicediff;
804     }
805
806   } else {
807     $amount    = $form->round_amount($netamount * $form->{exchangerate}, 2);
808     $diff      = $amount - $netamount * $form->{exchangerate};
809     $netamount = $amount;
810     foreach my $item (split(/ /, $form->{taxaccounts})) {
811       $form->{amount}{ $form->{id} }{$item} =
812         $form->round_amount($form->{amount}{ $form->{id} }{$item}, 2);
813       $amount =
814         $form->round_amount(
815                  $form->{amount}{ $form->{id} }{$item} * $form->{exchangerate},
816                  2);
817       $diff +=
818         $amount - $form->{amount}{ $form->{id} }{$item} *
819         $form->{exchangerate};
820       $form->{amount}{ $form->{id} }{$item} = $form->round_amount($amount, 2);
821       $tax += $form->{amount}{ $form->{id} }{$item};
822     }
823   }
824
825   $form->{amount}{ $form->{id} }{ $form->{AR} } = $netamount + $tax;
826   $form->{paid} =
827     $form->round_amount($form->{paid} * $form->{exchangerate} + $diff, 2);
828
829   # reverse AR
830   $form->{amount}{ $form->{id} }{ $form->{AR} } *= -1;
831
832   # update exchangerate
833   if (($form->{currency} ne $defaultcurrency) && !$exchangerate) {
834     $form->update_exchangerate($dbh, $form->{currency}, $form->{invdate},
835                                $form->{exchangerate}, 0);
836   }
837
838   $project_id = conv_i($form->{"globalproject_id"});
839   # entsprechend auch beim Bestimmen des Steuerschlüssels in Taxkey.pm berücksichtigen
840   my $taxdate = $form->{deliverydate} ? $form->{deliverydate} : $form->{invdate};
841
842   foreach my $trans_id (keys %{ $form->{amount_cogs} }) {
843     foreach my $accno (keys %{ $form->{amount_cogs}{$trans_id} }) {
844       next unless ($form->{expense_inventory} =~ /\Q$accno\E/);
845
846       $form->{amount_cogs}{$trans_id}{$accno} = $form->round_amount($form->{amount_cogs}{$trans_id}{$accno}, 2);
847
848       if (!$payments_only && ($form->{amount_cogs}{$trans_id}{$accno} != 0)) {
849         $query =
850           qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link)
851                VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, (SELECT id FROM tax WHERE taxkey=0), 0, ?, (SELECT link FROM chart WHERE accno = ?))|;
852         @values = (conv_i($trans_id), $accno, $form->{amount_cogs}{$trans_id}{$accno}, conv_date($form->{invdate}), conv_i($project_id), $accno);
853         do_query($form, $dbh, $query, @values);
854         $form->{amount_cogs}{$trans_id}{$accno} = 0;
855       }
856     }
857
858     foreach my $accno (keys %{ $form->{amount_cogs}{$trans_id} }) {
859       $form->{amount_cogs}{$trans_id}{$accno} = $form->round_amount($form->{amount_cogs}{$trans_id}{$accno}, 2);
860
861       if (!$payments_only && ($form->{amount_cogs}{$trans_id}{$accno} != 0)) {
862         $query =
863           qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link)
864                VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, (SELECT id FROM tax WHERE taxkey=0), 0, ?, (SELECT link FROM chart WHERE accno = ?))|;
865         @values = (conv_i($trans_id), $accno, $form->{amount_cogs}{$trans_id}{$accno}, conv_date($form->{invdate}), conv_i($project_id), $accno);
866         do_query($form, $dbh, $query, @values);
867       }
868     }
869   }
870
871   foreach my $trans_id (keys %{ $form->{amount} }) {
872     foreach my $accno (keys %{ $form->{amount}{$trans_id} }) {
873       next unless ($form->{expense_inventory} =~ /\Q$accno\E/);
874
875       $form->{amount}{$trans_id}{$accno} = $form->round_amount($form->{amount}{$trans_id}{$accno}, 2);
876
877       if (!$payments_only && ($form->{amount}{$trans_id}{$accno} != 0)) {
878         $query =
879           qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link)
880              VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?,
881                      (SELECT tax_id
882                       FROM taxkeys
883                       WHERE chart_id= (SELECT id
884                                        FROM chart
885                                        WHERE accno = ?)
886                       AND startdate <= ?
887                       ORDER BY startdate DESC LIMIT 1),
888                      (SELECT taxkey_id
889                       FROM taxkeys
890                       WHERE chart_id= (SELECT id
891                                        FROM chart
892                                        WHERE accno = ?)
893                       AND startdate <= ?
894                       ORDER BY startdate DESC LIMIT 1),
895                      ?,
896                      (SELECT link FROM chart WHERE accno = ?))|;
897         @values = (conv_i($trans_id), $accno, $form->{amount}{$trans_id}{$accno}, conv_date($form->{invdate}), $accno, conv_date($taxdate), $accno, conv_date($taxdate), conv_i($project_id), $accno);
898         do_query($form, $dbh, $query, @values);
899         $form->{amount}{$trans_id}{$accno} = 0;
900       }
901     }
902
903     foreach my $accno (keys %{ $form->{amount}{$trans_id} }) {
904       $form->{amount}{$trans_id}{$accno} = $form->round_amount($form->{amount}{$trans_id}{$accno}, 2);
905
906       if (!$payments_only && ($form->{amount}{$trans_id}{$accno} != 0)) {
907         $query =
908           qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link)
909              VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?,
910                      (SELECT tax_id
911                       FROM taxkeys
912                       WHERE chart_id= (SELECT id
913                                        FROM chart
914                                        WHERE accno = ?)
915                       AND startdate <= ?
916                       ORDER BY startdate DESC LIMIT 1),
917                      (SELECT taxkey_id
918                       FROM taxkeys
919                       WHERE chart_id= (SELECT id
920                                        FROM chart
921                                        WHERE accno = ?)
922                       AND startdate <= ?
923                       ORDER BY startdate DESC LIMIT 1),
924                      ?,
925                      (SELECT link FROM chart WHERE accno = ?))|;
926         @values = (conv_i($trans_id), $accno, $form->{amount}{$trans_id}{$accno}, conv_date($form->{invdate}), $accno, conv_date($taxdate), $accno, conv_date($taxdate), conv_i($project_id), $accno);
927         do_query($form, $dbh, $query, @values);
928       }
929     }
930   }
931
932   # deduct payment differences from diff
933   for my $i (1 .. $form->{paidaccounts}) {
934     if ($form->{"paid_$i"} != 0) {
935       $amount =
936         $form->round_amount($form->{"paid_$i"} * $form->{exchangerate}, 2);
937       $diff -= $amount - $form->{"paid_$i"} * $form->{exchangerate};
938     }
939   }
940
941   # record payments and offsetting AR
942   if (!$form->{storno}) {
943     for my $i (1 .. $form->{paidaccounts}) {
944
945       if ($form->{"acc_trans_id_$i"}
946           && $payments_only
947           && (SL::DB::Default->get->payments_changeable == 0)) {
948         next;
949       }
950
951       next if ($form->{"paid_$i"} == 0);
952
953       my ($accno) = split(/--/, $form->{"AR_paid_$i"});
954       $form->{"datepaid_$i"} = $form->{invdate}
955       unless ($form->{"datepaid_$i"});
956       $form->{datepaid} = $form->{"datepaid_$i"};
957
958       $exchangerate = 0;
959
960       if ($form->{currency} eq $defaultcurrency) {
961         $form->{"exchangerate_$i"} = 1;
962       } else {
963         $exchangerate              = $form->check_exchangerate($myconfig, $form->{currency}, $form->{"datepaid_$i"}, 'buy');
964         $form->{"exchangerate_$i"} = $exchangerate || $form->parse_amount($myconfig, $form->{"exchangerate_$i"});
965       }
966
967       # record AR
968       $amount = $form->round_amount($form->{"paid_$i"} * $form->{exchangerate} + $diff, 2);
969
970       if ($form->{amount}{ $form->{id} }{ $form->{AR} } != 0) {
971         $query =
972         qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link)
973            VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?,
974                    (SELECT tax_id
975                     FROM taxkeys
976                     WHERE chart_id= (SELECT id
977                                      FROM chart
978                                      WHERE accno = ?)
979                     AND startdate <= ?
980                     ORDER BY startdate DESC LIMIT 1),
981                    (SELECT taxkey_id
982                     FROM taxkeys
983                     WHERE chart_id= (SELECT id
984                                      FROM chart
985                                      WHERE accno = ?)
986                     AND startdate <= ?
987                     ORDER BY startdate DESC LIMIT 1),
988                    ?,
989                    (SELECT link FROM chart WHERE accno = ?))|;
990         @values = (conv_i($form->{"id"}), $form->{AR}, $amount, $form->{"datepaid_$i"}, $form->{AR}, conv_date($taxdate), $form->{AR}, conv_date($taxdate), $project_id, $form->{AR});
991         do_query($form, $dbh, $query, @values);
992       }
993
994       # record payment
995       $form->{"paid_$i"} *= -1;
996       my $gldate = (conv_date($form->{"gldate_$i"}))? conv_date($form->{"gldate_$i"}) : conv_date($form->current_date($myconfig));
997
998       $query =
999       qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, gldate, source, memo, tax_id, taxkey, project_id, chart_link)
1000          VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, ?, ?, ?,
1001                  (SELECT tax_id
1002                   FROM taxkeys
1003                   WHERE chart_id= (SELECT id
1004                                    FROM chart
1005                                    WHERE accno = ?)
1006                   AND startdate <= ?
1007                   ORDER BY startdate DESC LIMIT 1),
1008                  (SELECT taxkey_id
1009                   FROM taxkeys
1010                   WHERE chart_id= (SELECT id
1011                                    FROM chart
1012                                    WHERE accno = ?)
1013                   AND startdate <= ?
1014                   ORDER BY startdate DESC LIMIT 1),
1015                  ?,
1016                  (SELECT link FROM chart WHERE accno = ?))|;
1017       @values = (conv_i($form->{"id"}), $accno, $form->{"paid_$i"}, $form->{"datepaid_$i"},
1018                  $gldate, $form->{"source_$i"}, $form->{"memo_$i"}, $accno, conv_date($taxdate), $accno, conv_date($taxdate), $project_id, $accno);
1019       do_query($form, $dbh, $query, @values);
1020
1021       # exchangerate difference
1022       $form->{fx}{$accno}{ $form->{"datepaid_$i"} } +=
1023         $form->{"paid_$i"} * ($form->{"exchangerate_$i"} - 1) + $diff;
1024
1025       # gain/loss
1026       $amount =
1027         $form->{"paid_$i"} * $form->{exchangerate} - $form->{"paid_$i"} *
1028         $form->{"exchangerate_$i"};
1029       if ($amount > 0) {
1030         $form->{fx}{ $form->{fxgain_accno} }{ $form->{"datepaid_$i"} } += $amount;
1031       } else {
1032         $form->{fx}{ $form->{fxloss_accno} }{ $form->{"datepaid_$i"} } += $amount;
1033       }
1034
1035       $diff = 0;
1036
1037       # update exchange rate
1038       if (($form->{currency} ne $defaultcurrency) && !$exchangerate) {
1039         $form->update_exchangerate($dbh, $form->{currency},
1040                                    $form->{"datepaid_$i"},
1041                                    $form->{"exchangerate_$i"}, 0);
1042       }
1043     }
1044
1045   } else {                      # if (!$form->{storno})
1046     $form->{marge_total} *= -1;
1047   }
1048
1049   IO->set_datepaid(table => 'ar', id => $form->{id}, dbh => $dbh);
1050
1051   # record exchange rate differences and gains/losses
1052   foreach my $accno (keys %{ $form->{fx} }) {
1053     foreach my $transdate (keys %{ $form->{fx}{$accno} }) {
1054       $form->{fx}{$accno}{$transdate} = $form->round_amount($form->{fx}{$accno}{$transdate}, 2);
1055       if ( $form->{fx}{$accno}{$transdate} != 0 ) {
1056
1057         $query =
1058           qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, cleared, fx_transaction, tax_id, taxkey, project_id, chart_link)
1059              VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, '0', '1',
1060                  (SELECT tax_id
1061                   FROM taxkeys
1062                   WHERE chart_id= (SELECT id
1063                                    FROM chart
1064                                    WHERE accno = ?)
1065                   AND startdate <= ?
1066                   ORDER BY startdate DESC LIMIT 1),
1067                  (SELECT taxkey_id
1068                   FROM taxkeys
1069                   WHERE chart_id= (SELECT id
1070                                    FROM chart
1071                                    WHERE accno = ?)
1072                   AND startdate <= ?
1073                   ORDER BY startdate DESC LIMIT 1),
1074                  ?,
1075                  (SELECT link FROM chart WHERE accno = ?))|;
1076         @values = (conv_i($form->{"id"}), $accno, $form->{fx}{$accno}{$transdate}, conv_date($transdate), $accno, conv_date($taxdate), $accno, conv_date($taxdate), conv_i($project_id), $accno);
1077         do_query($form, $dbh, $query, @values);
1078       }
1079     }
1080   }
1081
1082   if ($payments_only) {
1083     $query = qq|UPDATE ar SET paid = ? WHERE id = ?|;
1084     do_query($form, $dbh, $query,  $form->{paid}, conv_i($form->{id}));
1085
1086     $dbh->commit if !$provided_dbh;
1087
1088     $main::lxdebug->leave_sub();
1089     return;
1090   }
1091
1092   $amount = $netamount + $tax;
1093
1094   # save AR record
1095   #erweiterung fuer lieferscheinnummer (donumber) 12.02.09 jb
1096
1097   $query = qq|UPDATE ar set
1098                 invnumber   = ?, ordnumber     = ?, quonumber     = ?, cusordnumber  = ?,
1099                 transdate   = ?, orddate       = ?, quodate       = ?, customer_id   = ?,
1100                 amount      = ?, netamount     = ?, paid          = ?,
1101                 duedate     = ?, deliverydate  = ?, invoice       = ?, shippingpoint = ?,
1102                 shipvia     = ?, terms         = ?, notes         = ?, intnotes      = ?,
1103                 currency_id = (SELECT id FROM currencies WHERE name = ?),
1104                 department_id = ?, payment_id    = ?, taxincluded   = ?,
1105                 type        = ?, language_id   = ?, taxzone_id    = ?, shipto_id     = ?,
1106                 employee_id = ?, salesman_id   = ?, storno_id     = ?, storno        = ?,
1107                 cp_id       = ?, marge_total   = ?, marge_percent = ?,
1108                 globalproject_id               = ?, delivery_customer_id             = ?,
1109                 transaction_description        = ?, delivery_vendor_id               = ?,
1110                 donumber    = ?, invnumber_for_credit_note = ?,        direct_debit  = ?,
1111                 delivery_term_id = ?
1112               WHERE id = ?|;
1113   @values = (          $form->{"invnumber"},           $form->{"ordnumber"},             $form->{"quonumber"},          $form->{"cusordnumber"},
1114              conv_date($form->{"invdate"}),  conv_date($form->{"orddate"}),    conv_date($form->{"quodate"}),    conv_i($form->{"customer_id"}),
1115                        $amount,                        $netamount,                       $form->{"paid"},
1116              conv_date($form->{"duedate"}),  conv_date($form->{"deliverydate"}),    '1',                                $form->{"shippingpoint"},
1117                        $form->{"shipvia"},      conv_i($form->{"terms"}),                $form->{"notes"},              $form->{"intnotes"},
1118                        $form->{"currency"},     conv_i($form->{"department_id"}), conv_i($form->{"payment_id"}),        $form->{"taxincluded"} ? 't' : 'f',
1119                        $form->{"type"},         conv_i($form->{"language_id"}),   conv_i($form->{"taxzone_id"}), conv_i($form->{"shipto_id"}),
1120                 conv_i($form->{"employee_id"}), conv_i($form->{"salesman_id"}),   conv_i($form->{storno_id}),           $form->{"storno"} ? 't' : 'f',
1121                 conv_i($form->{"cp_id"}),            1 * $form->{marge_total} ,      1 * $form->{marge_percent},
1122                 conv_i($form->{"globalproject_id"}),                              conv_i($form->{"delivery_customer_id"}),
1123                        $form->{transaction_description},                          conv_i($form->{"delivery_vendor_id"}),
1124                        $form->{"donumber"}, $form->{"invnumber_for_credit_note"},        $form->{direct_debit} ? 't' : 'f',
1125                 conv_i($form->{delivery_term_id}),
1126                 conv_i($form->{"id"}));
1127   do_query($form, $dbh, $query, @values);
1128
1129
1130   if ($form->{storno}) {
1131     $query =
1132       qq!UPDATE ar SET
1133            paid = paid + amount,
1134            storno = 't',
1135            intnotes = ? || intnotes
1136          WHERE id = ?!;
1137     do_query($form, $dbh, $query, "Rechnung storniert am $form->{invdate} ", conv_i($form->{"storno_id"}));
1138     do_query($form, $dbh, qq|UPDATE ar SET paid = amount WHERE id = ?|, conv_i($form->{"id"}));
1139   }
1140
1141   # add shipto
1142   $form->{name} = $form->{customer};
1143   $form->{name} =~ s/--\Q$form->{customer_id}\E//;
1144
1145   if (!$form->{shipto_id}) {
1146     $form->add_shipto($dbh, $form->{id}, "AR");
1147   }
1148
1149   # save printed, emailed and queued
1150   $form->save_status($dbh);
1151
1152   Common::webdav_folder($form);
1153
1154   # Link this record to the records it was created from.
1155   RecordLinks->create_links('dbh'        => $dbh,
1156                             'mode'       => 'ids',
1157                             'from_table' => 'oe',
1158                             'from_ids'   => $form->{convert_from_oe_ids},
1159                             'to_table'   => 'ar',
1160                             'to_id'      => $form->{id},
1161     );
1162   delete $form->{convert_from_oe_ids};
1163
1164   my @convert_from_do_ids = map { $_ * 1 } grep { $_ } split m/\s+/, $form->{convert_from_do_ids};
1165
1166   if (scalar @convert_from_do_ids) {
1167     DO->close_orders('dbh' => $dbh,
1168                      'ids' => \@convert_from_do_ids);
1169
1170     RecordLinks->create_links('dbh'        => $dbh,
1171                               'mode'       => 'ids',
1172                               'from_table' => 'delivery_orders',
1173                               'from_ids'   => \@convert_from_do_ids,
1174                               'to_table'   => 'ar',
1175                               'to_id'      => $form->{id},
1176       );
1177   }
1178   delete $form->{convert_from_do_ids};
1179
1180   ARAP->close_orders_if_billed('dbh'     => $dbh,
1181                                'arap_id' => $form->{id},
1182                                'table'   => 'ar',);
1183
1184   # safety check datev export
1185   if ($::instance_conf->get_datev_check_on_sales_invoice) {
1186     my $transdate = $::form->{invdate} ? DateTime->from_lxoffice($::form->{invdate}) : undef;
1187     $transdate  ||= DateTime->today;
1188
1189     my $datev = SL::DATEV->new(
1190       exporttype => DATEV_ET_BUCHUNGEN,
1191       format     => DATEV_FORMAT_KNE,
1192       dbh        => $dbh,
1193       from       => $transdate,
1194       to         => $transdate,
1195       trans_id   => $form->{id},
1196     );
1197
1198     $datev->export;
1199
1200     if ($datev->errors) {
1201       $dbh->rollback;
1202       die join "\n", $::locale->text('DATEV check returned errors:'), $datev->errors;
1203     }
1204   }
1205
1206   my $rc = 1;
1207   $dbh->commit if !$provided_dbh;
1208
1209   $main::lxdebug->leave_sub();
1210
1211   return $rc;
1212 }
1213
1214 sub _delete_payments {
1215   $main::lxdebug->enter_sub();
1216
1217   my ($self, $form, $dbh) = @_;
1218
1219   my @delete_acc_trans_ids;
1220
1221   # Delete old payment entries from acc_trans.
1222   my $query =
1223     qq|SELECT acc_trans_id
1224        FROM acc_trans
1225        WHERE (trans_id = ?) AND fx_transaction
1226
1227        UNION
1228
1229        SELECT at.acc_trans_id
1230        FROM acc_trans at
1231        LEFT JOIN chart c ON (at.chart_id = c.id)
1232        WHERE (trans_id = ?) AND (c.link LIKE '%AR_paid%')|;
1233   push @delete_acc_trans_ids, selectall_array_query($form, $dbh, $query, conv_i($form->{id}), conv_i($form->{id}));
1234
1235   $query =
1236     qq|SELECT at.acc_trans_id
1237        FROM acc_trans at
1238        LEFT JOIN chart c ON (at.chart_id = c.id)
1239        WHERE (trans_id = ?)
1240          AND ((c.link = 'AR') OR (c.link LIKE '%:AR') OR (c.link LIKE 'AR:%'))
1241        ORDER BY at.acc_trans_id
1242        OFFSET 1|;
1243   push @delete_acc_trans_ids, selectall_array_query($form, $dbh, $query, conv_i($form->{id}));
1244
1245   if (@delete_acc_trans_ids) {
1246     $query = qq|DELETE FROM acc_trans WHERE acc_trans_id IN (| . join(", ", @delete_acc_trans_ids) . qq|)|;
1247     do_query($form, $dbh, $query);
1248   }
1249
1250   $main::lxdebug->leave_sub();
1251 }
1252
1253 sub post_payment {
1254   $main::lxdebug->enter_sub();
1255
1256   my ($self, $myconfig, $form, $locale) = @_;
1257
1258   # connect to database, turn off autocommit
1259   my $dbh = $form->get_standard_dbh;
1260
1261   my (%payments, $old_form, $row, $item, $query, %keep_vars);
1262
1263   $old_form = save_form();
1264
1265   # Delete all entries in acc_trans from prior payments.
1266   if (SL::DB::Default->get->payments_changeable != 0) {
1267     $self->_delete_payments($form, $dbh);
1268   }
1269
1270   # Save the new payments the user made before cleaning up $form.
1271   map { $payments{$_} = $form->{$_} } grep m/^datepaid_\d+$|^gldate_\d+$|^acc_trans_id_\d+$|^memo_\d+$|^source_\d+$|^exchangerate_\d+$|^paid_\d+$|^AR_paid_\d+$|^paidaccounts$/, keys %{ $form };
1272
1273   # Clean up $form so that old content won't tamper the results.
1274   %keep_vars = map { $_, 1 } qw(login password id);
1275   map { delete $form->{$_} unless $keep_vars{$_} } keys %{ $form };
1276
1277   # Retrieve the invoice from the database.
1278   $self->retrieve_invoice($myconfig, $form);
1279
1280   # Set up the content of $form in the way that IS::post_invoice() expects.
1281   $form->{exchangerate} = $form->format_amount($myconfig, $form->{exchangerate});
1282
1283   for $row (1 .. scalar @{ $form->{invoice_details} }) {
1284     $item = $form->{invoice_details}->[$row - 1];
1285
1286     map { $item->{$_} = $form->format_amount($myconfig, $item->{$_}) } qw(qty sellprice discount);
1287
1288     map { $form->{"${_}_${row}"} = $item->{$_} } keys %{ $item };
1289   }
1290
1291   $form->{rowcount} = scalar @{ $form->{invoice_details} };
1292
1293   delete @{$form}{qw(invoice_details paidaccounts storno paid)};
1294
1295   # Restore the payment options from the user input.
1296   map { $form->{$_} = $payments{$_} } keys %payments;
1297
1298   # Get the AR accno (which is normally done by Form::create_links()).
1299   $query =
1300     qq|SELECT c.accno
1301        FROM acc_trans at
1302        LEFT JOIN chart c ON (at.chart_id = c.id)
1303        WHERE (trans_id = ?)
1304          AND ((c.link = 'AR') OR (c.link LIKE '%:AR') OR (c.link LIKE 'AR:%'))
1305        ORDER BY at.acc_trans_id
1306        LIMIT 1|;
1307
1308   ($form->{AR}) = selectfirst_array_query($form, $dbh, $query, conv_i($form->{id}));
1309
1310   # Post the new payments.
1311   $self->post_invoice($myconfig, $form, $dbh, 1);
1312
1313   restore_form($old_form);
1314
1315   my $rc = $dbh->commit();
1316
1317   $main::lxdebug->leave_sub();
1318
1319   return $rc;
1320 }
1321
1322 sub process_assembly {
1323   $main::lxdebug->enter_sub();
1324
1325   my ($dbh, $myconfig, $form, $id, $totalqty) = @_;
1326
1327   my $query =
1328     qq|SELECT a.parts_id, a.qty, p.assembly, p.partnumber, p.description, p.unit,
1329          p.inventory_accno_id, p.income_accno_id, p.expense_accno_id
1330        FROM assembly a
1331        JOIN parts p ON (a.parts_id = p.id)
1332        WHERE (a.id = ?)|;
1333   my $sth = prepare_execute_query($form, $dbh, $query, conv_i($id));
1334
1335   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1336
1337     my $allocated = 0;
1338
1339     $ref->{inventory_accno_id} *= 1;
1340     $ref->{expense_accno_id}   *= 1;
1341
1342     # multiply by number of assemblies
1343     $ref->{qty} *= $totalqty;
1344
1345     if ($ref->{assembly}) {
1346       &process_assembly($dbh, $myconfig, $form, $ref->{parts_id}, $ref->{qty});
1347       next;
1348     } else {
1349       if ($ref->{inventory_accno_id}) {
1350         $allocated = &cogs($dbh, $myconfig, $form, $ref->{parts_id}, $ref->{qty});
1351       }
1352     }
1353
1354     # save detail record for individual assembly item in invoice table
1355     $query =
1356       qq|INSERT INTO invoice (trans_id, description, parts_id, qty, sellprice, fxsellprice, allocated, assemblyitem, unit)
1357          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)|;
1358     my @values = (conv_i($form->{id}), $ref->{description}, conv_i($ref->{parts_id}), $ref->{qty}, 0, 0, $allocated, 't', $ref->{unit});
1359     do_query($form, $dbh, $query, @values);
1360
1361   }
1362
1363   $sth->finish;
1364
1365   $main::lxdebug->leave_sub();
1366 }
1367
1368 sub cogs {
1369   $main::lxdebug->enter_sub();
1370
1371   # adjust allocated in table invoice according to FIFO princicple
1372   # for a certain part with part_id $id
1373
1374   my ($dbh, $myconfig, $form, $id, $totalqty, $basefactor, $row) = @_;
1375
1376   $basefactor ||= 1;
1377
1378   $form->{taxzone_id} *=1;
1379   my $transdate  = $form->{invdate} ? $dbh->quote($form->{invdate}) : "current_date";
1380   my $taxzone_id = $form->{"taxzone_id"} * 1;
1381   my $query =
1382     qq|SELECT i.id, i.trans_id, i.base_qty, i.allocated, i.sellprice, i.price_factor,
1383          c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from AS inventory_valid,
1384          c2.accno AS    income_accno, c2.new_chart_id AS    income_new_chart, date($transdate) - c2.valid_from AS    income_valid,
1385          c3.accno AS   expense_accno, c3.new_chart_id AS   expense_new_chart, date($transdate) - c3.valid_from AS   expense_valid
1386        FROM invoice i, parts p
1387        LEFT JOIN chart c1 ON ((SELECT inventory_accno_id FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c1.id)
1388        LEFT JOIN chart c2 ON ((SELECT income_accno_id_${taxzone_id} FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c2.id)
1389        LEFT JOIN chart c3 ON ((select expense_accno_id_${taxzone_id} FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c3.id)
1390        WHERE (i.parts_id = p.id)
1391          AND (i.parts_id = ?)
1392          AND ((i.base_qty + i.allocated) < 0)
1393        ORDER BY trans_id|;
1394   my $sth = prepare_execute_query($form, $dbh, $query, conv_i($id));
1395
1396   my $allocated = 0;
1397   my $qty;
1398
1399 # all invoice entries of an example part:
1400
1401 # id | trans_id | base_qty | allocated | sellprice | inventory_accno | income_accno | expense_accno
1402 # ---+----------+----------+-----------+-----------+-----------------+--------------+---------------
1403 #  4 |        4 |       -5 |         5 |  20.00000 | 1140            | 4400         | 5400     bought 5 for 20
1404 #  5 |        5 |        4 |        -4 |  50.00000 | 1140            | 4400         | 5400     sold   4 for 50
1405 #  6 |        6 |        1 |        -1 |  50.00000 | 1140            | 4400         | 5400     sold   1 for 50
1406 #  7 |        7 |       -5 |         1 |  20.00000 | 1140            | 4400         | 5400     bought 5 for 20
1407 #  8 |        8 |        1 |        -1 |  50.00000 | 1140            | 4400         | 5400     sold   1 for 50
1408
1409 # AND ((i.base_qty + i.allocated) < 0) filters out all but line with id=7, elsewhere i.base_qty + i.allocated has already reached 0
1410 # and all parts have been allocated
1411
1412 # so transaction 8 only sees transaction 7 with unallocated parts and adjusts allocated for that transaction, before allocated was 0
1413 #  7 |        7 |       -5 |         1 |  20.00000 | 1140            | 4400         | 5400     bought 5 for 20
1414
1415 # in this example there are still 4 unsold articles
1416
1417
1418   # search all invoice entries for the part in question, adjusting "allocated"
1419   # until the total number of sold parts has been reached
1420
1421   # ORDER BY trans_id ensures FIFO
1422
1423
1424   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1425     if (($qty = (($ref->{base_qty} * -1) - $ref->{allocated})) > $totalqty) {
1426       $qty = $totalqty;
1427     }
1428
1429     # update allocated in invoice
1430     $form->update_balance($dbh, "invoice", "allocated", qq|id = $ref->{id}|, $qty);
1431
1432     # total expenses and inventory
1433     # sellprice is the cost of the item
1434     my $linetotal = $form->round_amount(($ref->{sellprice} * $qty) / ( ($ref->{price_factor} || 1) * ( $basefactor || 1 )), 2);
1435
1436     if ( $::instance_conf->get_inventory_system eq 'perpetual' ) {
1437       # Bestandsmethode: when selling parts, deduct their purchase value from the inventory account
1438       $ref->{expense_accno} = ($form->{"expense_accno_$row"}) ? $form->{"expense_accno_$row"} : $ref->{expense_accno};
1439       # add to expense
1440       $form->{amount_cogs}{ $form->{id} }{ $ref->{expense_accno} } += -$linetotal;
1441       $form->{expense_inventory} .= " " . $ref->{expense_accno};
1442       $ref->{inventory_accno} = ($form->{"inventory_accno_$row"}) ? $form->{"inventory_accno_$row"} : $ref->{inventory_accno};
1443       # deduct inventory
1444       $form->{amount_cogs}{ $form->{id} }{ $ref->{inventory_accno} } -= -$linetotal;
1445       $form->{expense_inventory} .= " " . $ref->{inventory_accno};
1446     }
1447
1448     # add allocated
1449     $allocated -= $qty;
1450
1451     last if (($totalqty -= $qty) <= 0);
1452   }
1453
1454   $sth->finish;
1455
1456   $main::lxdebug->leave_sub();
1457
1458   return $allocated;
1459 }
1460
1461 sub reverse_invoice {
1462   $main::lxdebug->enter_sub();
1463
1464   my ($dbh, $form) = @_;
1465
1466   # reverse inventory items
1467   my $query =
1468     qq|SELECT i.id, i.parts_id, i.qty, i.assemblyitem, p.assembly, p.inventory_accno_id
1469        FROM invoice i
1470        JOIN parts p ON (i.parts_id = p.id)
1471        WHERE i.trans_id = ?|;
1472   my $sth = prepare_execute_query($form, $dbh, $query, conv_i($form->{"id"}));
1473
1474   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1475
1476     if ($ref->{inventory_accno_id}) {
1477       # de-allocated purchases
1478       $query =
1479         qq|SELECT i.id, i.trans_id, i.allocated
1480            FROM invoice i
1481            WHERE (i.parts_id = ?) AND (i.allocated > 0)
1482            ORDER BY i.trans_id DESC|;
1483       my $sth2 = prepare_execute_query($form, $dbh, $query, conv_i($ref->{"parts_id"}));
1484
1485       while (my $inhref = $sth2->fetchrow_hashref('NAME_lc')) {
1486         my $qty = $ref->{qty};
1487         if (($ref->{qty} - $inhref->{allocated}) > 0) {
1488           $qty = $inhref->{allocated};
1489         }
1490
1491         # update invoice
1492         $form->update_balance($dbh, "invoice", "allocated", qq|id = $inhref->{id}|, $qty * -1);
1493
1494         last if (($ref->{qty} -= $qty) <= 0);
1495       }
1496       $sth2->finish;
1497     }
1498   }
1499
1500   $sth->finish;
1501
1502   # delete acc_trans
1503   my @values = (conv_i($form->{id}));
1504   do_query($form, $dbh, qq|DELETE FROM acc_trans WHERE trans_id = ?|, @values);
1505   do_query($form, $dbh, qq|DELETE FROM invoice WHERE trans_id = ?|, @values);
1506   do_query($form, $dbh, qq|DELETE FROM shipto WHERE (trans_id = ?) AND (module = 'AR')|, @values);
1507
1508   $main::lxdebug->leave_sub();
1509 }
1510
1511 sub delete_invoice {
1512   $main::lxdebug->enter_sub();
1513
1514   my ($self, $myconfig, $form) = @_;
1515
1516   # connect to database
1517   my $dbh = $form->get_standard_dbh;
1518
1519   &reverse_invoice($dbh, $form);
1520
1521   my @values = (conv_i($form->{id}));
1522
1523   # Falls wir ein Storno haben, müssen zwei Felder in der stornierten Rechnung wieder
1524   # zurückgesetzt werden. Vgl:
1525   #  id | storno | storno_id |  paid   |  amount
1526   #----+--------+-----------+---------+-----------
1527   # 18 | f      |           | 0.00000 | 119.00000
1528   # ZU:
1529   # 18 | t      |           |  119.00000 |  119.00000
1530   #
1531   if($form->{storno}){
1532     # storno_id auslesen und korrigieren
1533     my ($invoice_id) = selectfirst_array_query($form, $dbh, qq|SELECT storno_id FROM ar WHERE id = ?|,@values);
1534     do_query($form, $dbh, qq|UPDATE ar SET storno = 'f', paid = 0 WHERE id = ?|, $invoice_id);
1535   }
1536
1537   # delete spool files
1538   my @spoolfiles = selectall_array_query($form, $dbh, qq|SELECT spoolfile FROM status WHERE trans_id = ?|, @values);
1539
1540   my @queries = (
1541     qq|DELETE FROM status WHERE trans_id = ?|,
1542     qq|DELETE FROM periodic_invoices WHERE ar_id = ?|,
1543     qq|DELETE FROM ar WHERE id = ?|,
1544   );
1545
1546   map { do_query($form, $dbh, $_, @values) } @queries;
1547
1548   my $rc = $dbh->commit;
1549
1550   if ($rc) {
1551     my $spool = $::lx_office_conf{paths}->{spool};
1552     map { unlink "$spool/$_" if -f "$spool/$_"; } @spoolfiles;
1553   }
1554
1555   $main::lxdebug->leave_sub();
1556
1557   return $rc;
1558 }
1559
1560 sub retrieve_invoice {
1561   $main::lxdebug->enter_sub();
1562
1563   my ($self, $myconfig, $form) = @_;
1564
1565   # connect to database
1566   my $dbh = $form->get_standard_dbh;
1567
1568   my ($sth, $ref, $query);
1569
1570   my $query_transdate = !$form->{id} ? ", current_date AS invdate" : '';
1571
1572   $query =
1573     qq|SELECT
1574          (SELECT c.accno FROM chart c WHERE d.inventory_accno_id = c.id) AS inventory_accno,
1575          (SELECT c.accno FROM chart c WHERE d.income_accno_id = c.id)    AS income_accno,
1576          (SELECT c.accno FROM chart c WHERE d.expense_accno_id = c.id)   AS expense_accno,
1577          (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id)    AS fxgain_accno,
1578          (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id)    AS fxloss_accno
1579          ${query_transdate}
1580        FROM defaults d|;
1581
1582   $ref = selectfirst_hashref_query($form, $dbh, $query);
1583   map { $form->{$_} = $ref->{$_} } keys %{ $ref };
1584
1585   if ($form->{id}) {
1586     my $id = conv_i($form->{id});
1587
1588     # retrieve invoice
1589     #erweiterung um das entsprechende feld lieferscheinnummer (a.donumber) in der html-maske anzuzeigen 12.02.2009 jb
1590
1591     $query =
1592       qq|SELECT
1593            a.invnumber, a.ordnumber, a.quonumber, a.cusordnumber,
1594            a.orddate, a.quodate, a.globalproject_id,
1595            a.transdate AS invdate, a.deliverydate, a.paid, a.storno, a.gldate,
1596            a.shippingpoint, a.shipvia, a.terms, a.notes, a.intnotes, a.taxzone_id,
1597            a.duedate, a.taxincluded, (SELECT cu.name FROM currencies cu WHERE cu.id=a.currency_id) AS currency, a.shipto_id, a.cp_id,
1598            a.employee_id, a.salesman_id, a.payment_id,
1599            a.language_id, a.delivery_customer_id, a.delivery_vendor_id, a.type,
1600            a.transaction_description, a.donumber, a.invnumber_for_credit_note,
1601            a.marge_total, a.marge_percent, a.direct_debit, a.delivery_term_id,
1602            e.name AS employee
1603          FROM ar a
1604          LEFT JOIN employee e ON (e.id = a.employee_id)
1605          WHERE a.id = ?|;
1606     $ref = selectfirst_hashref_query($form, $dbh, $query, $id);
1607     map { $form->{$_} = $ref->{$_} } keys %{ $ref };
1608
1609     $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{invdate}, "buy");
1610
1611     # get shipto
1612     $query = qq|SELECT * FROM shipto WHERE (trans_id = ?) AND (module = 'AR')|;
1613     $ref = selectfirst_hashref_query($form, $dbh, $query, $id);
1614     delete $ref->{id};
1615     map { $form->{$_} = $ref->{$_} } keys %{ $ref };
1616
1617     foreach my $vc (qw(customer vendor)) {
1618       next if !$form->{"delivery_${vc}_id"};
1619       ($form->{"delivery_${vc}_string"}) = selectrow_query($form, $dbh, qq|SELECT name FROM customer WHERE id = ?|, $id);
1620     }
1621
1622     # get printed, emailed
1623     $query = qq|SELECT printed, emailed, spoolfile, formname FROM status WHERE trans_id = ?|;
1624     $sth = prepare_execute_query($form, $dbh, $query, $id);
1625
1626     while ($ref = $sth->fetchrow_hashref('NAME_lc')) {
1627       $form->{printed} .= "$ref->{formname} " if $ref->{printed};
1628       $form->{emailed} .= "$ref->{formname} " if $ref->{emailed};
1629       $form->{queued} .= "$ref->{formname} $ref->{spoolfile} " if $ref->{spoolfile};
1630     }
1631     $sth->finish;
1632     map { $form->{$_} =~ s/ +$//g } qw(printed emailed queued);
1633
1634     my $transdate = $form->{deliverydate} ? $dbh->quote($form->{deliverydate})
1635                   : $form->{invdate}      ? $dbh->quote($form->{invdate})
1636                   :                         "current_date";
1637
1638
1639     my $taxzone_id = $form->{taxzone_id} *= 1;
1640     $taxzone_id = 0 if (0 > $taxzone_id) || (3 < $taxzone_id);
1641
1642     # retrieve individual items
1643     $query =
1644       qq|SELECT
1645            c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from AS inventory_valid,
1646            c2.accno AS income_accno,    c2.new_chart_id AS income_new_chart,    date($transdate) - c2.valid_from as income_valid,
1647            c3.accno AS expense_accno,   c3.new_chart_id AS expense_new_chart,   date($transdate) - c3.valid_from AS expense_valid,
1648
1649            i.id AS invoice_id,
1650            i.description, i.longdescription, i.qty, i.fxsellprice AS sellprice, i.discount, i.parts_id AS id, i.unit, i.deliverydate AS reqdate,
1651            i.project_id, i.serialnumber, i.id AS invoice_pos, i.pricegroup_id, i.ordnumber, i.transdate, i.cusordnumber, i.subtotal, i.lastcost,
1652            i.price_factor_id, i.price_factor, i.marge_price_factor,
1653            p.partnumber, p.assembly, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id, p.formel, p.listprice,
1654            pr.projectnumber, pg.partsgroup, prg.pricegroup
1655
1656          FROM invoice i
1657          LEFT JOIN parts p ON (i.parts_id = p.id)
1658          LEFT JOIN project pr ON (i.project_id = pr.id)
1659          LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id)
1660          LEFT JOIN pricegroup prg ON (i.pricegroup_id = prg.id)
1661
1662          LEFT JOIN chart c1 ON ((SELECT inventory_accno_id             FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c1.id)
1663          LEFT JOIN chart c2 ON ((SELECT income_accno_id_${taxzone_id}  FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c2.id)
1664          LEFT JOIN chart c3 ON ((SELECT expense_accno_id_${taxzone_id} FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c3.id)
1665
1666          WHERE (i.trans_id = ?) AND NOT (i.assemblyitem = '1') ORDER BY i.id|;
1667
1668     $sth = prepare_execute_query($form, $dbh, $query, $id);
1669
1670     while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1671       # Retrieve custom variables.
1672       my $cvars = CVar->get_custom_variables(dbh        => $dbh,
1673                                              module     => 'IC',
1674                                              sub_module => 'invoice',
1675                                              trans_id   => $ref->{invoice_id},
1676                                             );
1677       map { $ref->{"ic_cvar_$_->{name}"} = $_->{value} } @{ $cvars };
1678       delete $ref->{invoice_id};
1679
1680       map({ delete($ref->{$_}); } qw(inventory_accno inventory_new_chart inventory_valid)) if !$ref->{"part_inventory_accno_id"};
1681       delete($ref->{"part_inventory_accno_id"});
1682
1683       foreach my $type (qw(inventory income expense)) {
1684         while ($ref->{"${type}_new_chart"} && ($ref->{"${type}_valid"} >=0)) {
1685           my $query = qq|SELECT accno, new_chart_id, date($transdate) - valid_from FROM chart WHERE id = ?|;
1686           @$ref{ map $type.$_, qw(_accno _new_chart _valid) } = selectrow_query($form, $dbh, $query, $ref->{"${type}_new_chart"});
1687         }
1688       }
1689
1690       # get tax rates and description
1691       my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
1692       $query =
1693         qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber FROM tax t
1694            LEFT JOIN chart c ON (c.id = t.chart_id)
1695            WHERE t.id IN
1696              (SELECT tk.tax_id FROM taxkeys tk
1697               WHERE tk.chart_id = (SELECT id FROM chart WHERE accno = ?)
1698                 AND startdate <= date($transdate)
1699               ORDER BY startdate DESC LIMIT 1)
1700            ORDER BY c.accno|;
1701       my $stw = prepare_execute_query($form, $dbh, $query, $accno_id);
1702       $ref->{taxaccounts} = "";
1703       my $i=0;
1704       while (my $ptr = $stw->fetchrow_hashref('NAME_lc')) {
1705
1706         if (($ptr->{accno} eq "") && ($ptr->{rate} == 0)) {
1707           $i++;
1708           $ptr->{accno} = $i;
1709         }
1710         $ref->{taxaccounts} .= "$ptr->{accno} ";
1711
1712         if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
1713           $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
1714           $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
1715           $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
1716           $form->{taxaccounts} .= "$ptr->{accno} ";
1717         }
1718
1719       }
1720
1721       $ref->{qty} *= -1 if $form->{type} eq "credit_note";
1722
1723       chop $ref->{taxaccounts};
1724       push @{ $form->{invoice_details} }, $ref;
1725       $stw->finish;
1726     }
1727     $sth->finish;
1728
1729     Common::webdav_folder($form);
1730   }
1731
1732   my $rc = $dbh->commit;
1733
1734   $main::lxdebug->leave_sub();
1735
1736   return $rc;
1737 }
1738
1739 sub get_customer {
1740   $main::lxdebug->enter_sub();
1741
1742   my ($self, $myconfig, $form) = @_;
1743
1744   # connect to database
1745   my $dbh = $form->get_standard_dbh;
1746
1747   my $dateformat = $myconfig->{dateformat};
1748   $dateformat .= "yy" if $myconfig->{dateformat} !~ /^y/;
1749
1750   my (@values, $duedate, $ref, $query);
1751
1752   if ($form->{invdate}) {
1753     $duedate = "to_date(?, '$dateformat')";
1754     push @values, $form->{invdate};
1755   } else {
1756     $duedate = "current_date";
1757   }
1758
1759   my $cid = conv_i($form->{customer_id});
1760   my $payment_id;
1761
1762   if ($form->{payment_id}) {
1763     $payment_id = "(pt.id = ?) OR";
1764     push @values, conv_i($form->{payment_id});
1765   }
1766
1767   # get customer
1768   $query =
1769     qq|SELECT
1770          c.id AS customer_id, c.name AS customer, c.discount as customer_discount, c.creditlimit, c.terms,
1771          c.email, c.cc, c.bcc, c.language_id, c.payment_id, c.delivery_term_id,
1772          c.street, c.zipcode, c.city, c.country,
1773          c.notes AS intnotes, c.klass as customer_klass, c.taxzone_id, c.salesman_id, cu.name AS curr,
1774          c.taxincluded_checked, c.direct_debit,
1775          $duedate + COALESCE(pt.terms_netto, 0) AS duedate,
1776          b.discount AS tradediscount, b.description AS business
1777        FROM customer c
1778        LEFT JOIN business b ON (b.id = c.business_id)
1779        LEFT JOIN payment_terms pt ON ($payment_id (c.payment_id = pt.id))
1780        LEFT JOIN currencies cu ON (c.currency_id=cu.id)
1781        WHERE c.id = ?|;
1782   push @values, $cid;
1783   $ref = selectfirst_hashref_query($form, $dbh, $query, @values);
1784
1785   delete $ref->{salesman_id} if !$ref->{salesman_id};
1786
1787   map { $form->{$_} = $ref->{$_} } keys %$ref;
1788
1789   # use customer currency
1790   $form->{currency} = $form->{curr};
1791
1792   $query =
1793     qq|SELECT sum(amount - paid) AS dunning_amount
1794        FROM ar
1795        WHERE (paid < amount)
1796          AND (customer_id = ?)
1797          AND (dunning_config_id IS NOT NULL)|;
1798   $ref = selectfirst_hashref_query($form, $dbh, $query, $cid);
1799   map { $form->{$_} = $ref->{$_} } keys %$ref;
1800
1801   $query =
1802     qq|SELECT dnn.dunning_description AS max_dunning_level
1803        FROM dunning_config dnn
1804        WHERE id IN (SELECT dunning_config_id
1805                     FROM ar
1806                     WHERE (paid < amount) AND (customer_id = ?) AND (dunning_config_id IS NOT NULL))
1807        ORDER BY dunning_level DESC LIMIT 1|;
1808   $ref = selectfirst_hashref_query($form, $dbh, $query, $cid);
1809   map { $form->{$_} = $ref->{$_} } keys %$ref;
1810
1811   $form->{creditremaining} = $form->{creditlimit};
1812   $query = qq|SELECT SUM(amount - paid) FROM ar WHERE customer_id = ?|;
1813   my ($value) = selectrow_query($form, $dbh, $query, $cid);
1814   $form->{creditremaining} -= $value;
1815
1816   $query =
1817     qq|SELECT o.amount,
1818          (SELECT e.buy FROM exchangerate e
1819           WHERE e.currency_id = o.currency_id
1820             AND e.transdate = o.transdate)
1821        FROM oe o
1822        WHERE o.customer_id = ?
1823          AND o.quotation = '0'
1824          AND o.closed = '0'|;
1825   my $sth = prepare_execute_query($form, $dbh, $query, $cid);
1826
1827   while (my ($amount, $exch) = $sth->fetchrow_array) {
1828     $exch = 1 unless $exch;
1829     $form->{creditremaining} -= $amount * $exch;
1830   }
1831   $sth->finish;
1832
1833   # get shipto if we did not converted an order or invoice
1834   if (!$form->{shipto}) {
1835     map { delete $form->{$_} }
1836       qw(shiptoname shiptodepartment_1 shiptodepartment_2
1837          shiptostreet shiptozipcode shiptocity shiptocountry
1838          shiptocontact shiptophone shiptofax shiptoemail);
1839
1840     $query = qq|SELECT * FROM shipto WHERE trans_id = ? AND module = 'CT'|;
1841     $ref = selectfirst_hashref_query($form, $dbh, $query, $cid);
1842     delete $ref->{id};
1843     map { $form->{$_} = $ref->{$_} } keys %$ref;
1844   }
1845
1846   # setup last accounts used for this customer
1847   if (!$form->{id} && $form->{type} !~ /_(order|quotation)/) {
1848     $query =
1849       qq|SELECT c.id, c.accno, c.description, c.link, c.category
1850          FROM chart c
1851          JOIN acc_trans ac ON (ac.chart_id = c.id)
1852          JOIN ar a ON (a.id = ac.trans_id)
1853          WHERE a.customer_id = ?
1854            AND NOT (c.link LIKE '%_tax%' OR c.link LIKE '%_paid%')
1855            AND a.id IN (SELECT max(a2.id) FROM ar a2 WHERE a2.customer_id = ?)|;
1856     $sth = prepare_execute_query($form, $dbh, $query, $cid, $cid);
1857
1858     my $i = 0;
1859     while ($ref = $sth->fetchrow_hashref('NAME_lc')) {
1860       if ($ref->{category} eq 'I') {
1861         $i++;
1862         $form->{"AR_amount_$i"} = "$ref->{accno}--$ref->{description}";
1863
1864         if ($form->{initial_transdate}) {
1865           my $tax_query =
1866             qq|SELECT tk.tax_id, t.rate
1867                FROM taxkeys tk
1868                LEFT JOIN tax t ON tk.tax_id = t.id
1869                WHERE (tk.chart_id = ?) AND (startdate <= date(?))
1870                ORDER BY tk.startdate DESC
1871                LIMIT 1|;
1872           my ($tax_id, $rate) =
1873             selectrow_query($form, $dbh, $tax_query, $ref->{id},
1874                             $form->{initial_transdate});
1875           $form->{"taxchart_$i"} = "${tax_id}--${rate}";
1876         }
1877       }
1878       if ($ref->{category} eq 'A') {
1879         $form->{ARselected} = $form->{AR_1} = $ref->{accno};
1880       }
1881     }
1882     $sth->finish;
1883     $form->{rowcount} = $i if ($i && !$form->{type});
1884   }
1885
1886   $main::lxdebug->leave_sub();
1887 }
1888
1889 sub retrieve_item {
1890   $main::lxdebug->enter_sub();
1891
1892   my ($self, $myconfig, $form) = @_;
1893
1894   # connect to database
1895   my $dbh = $form->get_standard_dbh;
1896
1897   my $i = $form->{rowcount};
1898
1899   my $where = qq|NOT p.obsolete = '1'|;
1900   my @values;
1901
1902   foreach my $column (qw(p.partnumber p.description pgpartsgroup )) {
1903     my ($table, $field) = split m/\./, $column;
1904     next if !$form->{"${field}_${i}"};
1905     $where .= qq| AND lower(${column}) ILIKE ?|;
1906     push @values, '%' . $form->{"${field}_${i}"} . '%';
1907   }
1908
1909   #Es soll auch nach EAN gesucht werden, ohne Einschränkung durch Beschreibung
1910   if ($form->{"partnumber_$i"} && !$form->{"description_$i"}) {
1911     $where .= qq| OR (NOT p.obsolete = '1' AND p.ean = ? )|;
1912     push @values, $form->{"partnumber_$i"};
1913   }
1914
1915   # Search for part ID overrides all other criteria.
1916   if ($form->{"id_${i}"}) {
1917     $where  = qq|p.id = ?|;
1918     @values = ($form->{"id_${i}"});
1919   }
1920
1921   if ($form->{"description_$i"}) {
1922     $where .= qq| ORDER BY p.description|;
1923   } else {
1924     $where .= qq| ORDER BY p.partnumber|;
1925   }
1926
1927   my $transdate;
1928   if ($form->{type} eq "invoice") {
1929     $transdate =
1930       $form->{deliverydate} ? $dbh->quote($form->{deliverydate}) :
1931       $form->{invdate}      ? $dbh->quote($form->{invdate}) :
1932                               "current_date";
1933   } else {
1934     $transdate =
1935       $form->{transdate}    ? $dbh->quote($form->{transdate}) :
1936                               "current_date";
1937   }
1938
1939   my $taxzone_id = $form->{taxzone_id} * 1;
1940   $taxzone_id = 0 if (0 > $taxzone_id) || (3 < $taxzone_id);
1941
1942   my $query =
1943     qq|SELECT
1944          p.id, p.partnumber, p.description, p.sellprice,
1945          p.listprice, p.inventory_accno_id, p.lastcost,
1946
1947          c1.accno AS inventory_accno,
1948          c1.new_chart_id AS inventory_new_chart,
1949          date($transdate) - c1.valid_from AS inventory_valid,
1950
1951          c2.accno AS income_accno,
1952          c2.new_chart_id AS income_new_chart,
1953          date($transdate)  - c2.valid_from AS income_valid,
1954
1955          c3.accno AS expense_accno,
1956          c3.new_chart_id AS expense_new_chart,
1957          date($transdate) - c3.valid_from AS expense_valid,
1958
1959          p.unit, p.assembly, p.onhand,
1960          p.notes AS partnotes, p.notes AS longdescription,
1961          p.not_discountable, p.formel, p.payment_id AS part_payment_id,
1962          p.price_factor_id, p.weight,
1963
1964          pfac.factor AS price_factor,
1965
1966          pg.partsgroup
1967
1968        FROM parts p
1969        LEFT JOIN chart c1 ON
1970          ((SELECT inventory_accno_id
1971            FROM buchungsgruppen
1972            WHERE id = p.buchungsgruppen_id) = c1.id)
1973        LEFT JOIN chart c2 ON
1974          ((SELECT income_accno_id_${taxzone_id}
1975            FROM buchungsgruppen
1976            WHERE id = p.buchungsgruppen_id) = c2.id)
1977        LEFT JOIN chart c3 ON
1978          ((SELECT expense_accno_id_${taxzone_id}
1979            FROM buchungsgruppen
1980            WHERE id = p.buchungsgruppen_id) = c3.id)
1981        LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id)
1982        LEFT JOIN price_factors pfac ON (pfac.id = p.price_factor_id)
1983        WHERE $where|;
1984   my $sth = prepare_execute_query($form, $dbh, $query, @values);
1985
1986   my @translation_queries = ( [ qq|SELECT tr.translation, tr.longdescription
1987                                    FROM translation tr
1988                                    WHERE tr.language_id = ? AND tr.parts_id = ?| ],
1989                               [ qq|SELECT tr.translation, tr.longdescription
1990                                    FROM translation tr
1991                                    WHERE tr.language_id IN
1992                                      (SELECT id
1993                                       FROM language
1994                                       WHERE article_code = (SELECT article_code FROM language WHERE id = ?))
1995                                      AND tr.parts_id = ?
1996                                    LIMIT 1| ] );
1997   map { push @{ $_ }, prepare_query($form, $dbh, $_->[0]) } @translation_queries;
1998
1999   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
2000
2001     # In der Buchungsgruppe ist immer ein Bestandskonto verknuepft, auch wenn
2002     # es sich um eine Dienstleistung handelt. Bei Dienstleistungen muss das
2003     # Buchungskonto also aus dem Ergebnis rausgenommen werden.
2004     if (!$ref->{inventory_accno_id}) {
2005       map({ delete($ref->{"inventory_${_}"}); } qw(accno new_chart valid));
2006     }
2007     delete($ref->{inventory_accno_id});
2008
2009     foreach my $type (qw(inventory income expense)) {
2010       while ($ref->{"${type}_new_chart"} && ($ref->{"${type}_valid"} >=0)) {
2011         my $query =
2012           qq|SELECT accno, new_chart_id, date($transdate) - valid_from
2013              FROM chart
2014              WHERE id = ?|;
2015         ($ref->{"${type}_accno"},
2016          $ref->{"${type}_new_chart"},
2017          $ref->{"${type}_valid"})
2018           = selectrow_query($form, $dbh, $query, $ref->{"${type}_new_chart"});
2019       }
2020     }
2021
2022     if ($form->{payment_id} eq "") {
2023       $form->{payment_id} = $form->{part_payment_id};
2024     }
2025
2026     # get tax rates and description
2027     my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
2028     $query =
2029       qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber
2030          FROM tax t
2031          LEFT JOIN chart c ON (c.id = t.chart_id)
2032          WHERE t.id in
2033            (SELECT tk.tax_id
2034             FROM taxkeys tk
2035             WHERE tk.chart_id = (SELECT id from chart WHERE accno = ?)
2036               AND startdate <= ?
2037             ORDER BY startdate DESC
2038             LIMIT 1)
2039          ORDER BY c.accno|;
2040     @values = ($accno_id, $transdate eq "current_date" ? "now" : $transdate);
2041     my $stw = $dbh->prepare($query);
2042     $stw->execute(@values) || $form->dberror($query);
2043
2044     $ref->{taxaccounts} = "";
2045     my $i = 0;
2046     while (my $ptr = $stw->fetchrow_hashref('NAME_lc')) {
2047
2048       if (($ptr->{accno} eq "") && ($ptr->{rate} == 0)) {
2049         $i++;
2050         $ptr->{accno} = $i;
2051       }
2052       $ref->{taxaccounts} .= "$ptr->{accno} ";
2053
2054       if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
2055         $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
2056         $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
2057         $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
2058         $form->{taxaccounts} .= "$ptr->{accno} ";
2059       }
2060
2061     }
2062
2063     $stw->finish;
2064     chop $ref->{taxaccounts};
2065
2066     if ($form->{language_id}) {
2067       for my $spec (@translation_queries) {
2068         do_statement($form, $spec->[1], $spec->[0], conv_i($form->{language_id}), conv_i($ref->{id}));
2069         my ($translation, $longdescription) = $spec->[1]->fetchrow_array;
2070         next unless $translation;
2071         $ref->{description} = $translation;
2072         $ref->{longdescription} = $longdescription;
2073         last;
2074       }
2075     }
2076
2077     $ref->{onhand} *= 1;
2078
2079     push @{ $form->{item_list} }, $ref;
2080   }
2081   $sth->finish;
2082   $_->[1]->finish for @translation_queries;
2083
2084   foreach my $item (@{ $form->{item_list} }) {
2085     my $custom_variables = CVar->get_custom_variables(module   => 'IC',
2086                                                       trans_id => $item->{id},
2087                                                       dbh      => $dbh,
2088                                                      );
2089
2090     map { $item->{"ic_cvar_" . $_->{name} } = $_->{value} } @{ $custom_variables };
2091   }
2092
2093   $main::lxdebug->leave_sub();
2094 }
2095
2096 ##########################
2097 # get pricegroups from database
2098 # build up selected pricegroup
2099 # if an exchange rate - change price
2100 # for each part
2101 #
2102 sub get_pricegroups_for_parts {
2103
2104   $main::lxdebug->enter_sub();
2105
2106   my ($self, $myconfig, $form) = @_;
2107
2108   my $dbh = $form->get_standard_dbh;
2109
2110   $form->{"PRICES"} = {};
2111
2112   my $i  = 1;
2113   my $id = 0;
2114   my $all_units = AM->retrieve_units($myconfig, $form);
2115   while (($form->{"id_$i"}) or ($form->{"new_id_$i"})) {
2116     $form->{"PRICES"}{$i} = [];
2117
2118     $id = $form->{"id_$i"};
2119
2120     if (!($form->{"id_$i"}) and $form->{"new_id_$i"}) {
2121       $id = $form->{"new_id_$i"};
2122     }
2123
2124     my ($price, $selectedpricegroup_id) = split(/--/, $form->{"sellprice_pg_$i"});
2125
2126     my $pricegroup_old = $form->{"pricegroup_old_$i"};
2127
2128     # sellprice has format 13,0000 or 0,00000,  can't check for 0 numerically
2129     my $sellprice = $form->{"sellprice_$i"};
2130     my $pricegroup_id = $form->{"pricegroup_id_$i"};
2131     $form->{"new_pricegroup_$i"} = $selectedpricegroup_id;
2132     $form->{"old_pricegroup_$i"} = $pricegroup_old;
2133
2134     my $price_new = $form->{"price_new_$i"};
2135     my $price_old = $form->{"price_old_$i"};
2136
2137     if (!$form->{"unit_old_$i"}) {
2138       # Neue Ware aus der Datenbank. In diesem Fall ist unit_$i die
2139       # Einheit, wie sie in den Stammdaten hinterlegt wurde.
2140       # Es sollte also angenommen werden, dass diese ausgewaehlt war.
2141       $form->{"unit_old_$i"} = $form->{"unit_$i"};
2142     }
2143
2144     # Die zuletzt ausgewaehlte mit der aktuell ausgewaehlten Einheit
2145     # vergleichen und bei Unterschied den Preis entsprechend umrechnen.
2146     $form->{"selected_unit_$i"} = $form->{"unit_$i"} unless ($form->{"selected_unit_$i"});
2147
2148     if (!$all_units->{$form->{"selected_unit_$i"}} ||
2149         ($all_units->{$form->{"selected_unit_$i"}}->{"base_unit"} ne
2150          $all_units->{$form->{"unit_old_$i"}}->{"base_unit"})) {
2151       # Die ausgewaehlte Einheit ist fuer diesen Artikel nicht gueltig
2152       # (z.B. Dimensionseinheit war ausgewaehlt, es handelt sich aber
2153       # um eine Dienstleistung). Dann keinerlei Umrechnung vornehmen.
2154       $form->{"unit_old_$i"} = $form->{"selected_unit_$i"} = $form->{"unit_$i"};
2155     }
2156
2157     my $basefactor = 1;
2158
2159     if ($form->{"unit_old_$i"} ne $form->{"selected_unit_$i"}) {
2160       if (defined($all_units->{$form->{"unit_old_$i"}}->{"factor"}) &&
2161           $all_units->{$form->{"unit_old_$i"}}->{"factor"}) {
2162         $basefactor = $all_units->{$form->{"selected_unit_$i"}}->{"factor"} /
2163           $all_units->{$form->{"unit_old_$i"}}->{"factor"};
2164       }
2165     }
2166
2167     if (!$form->{"basefactor_$i"}) {
2168       $form->{"basefactor_$i"} = 1;
2169     }
2170
2171     my $query =
2172        qq|SELECT
2173             0 as pricegroup_id,
2174             sellprice AS default_sellprice,
2175             '' AS pricegroup,
2176             sellprice AS price,
2177             'selected' AS selected
2178           FROM parts
2179           WHERE id = ?
2180           UNION ALL
2181           SELECT
2182            pricegroup_id,
2183            parts.sellprice AS default_sellprice,
2184            pricegroup.pricegroup,
2185            price,
2186            '' AS selected
2187           FROM prices
2188           LEFT JOIN parts ON parts.id = parts_id
2189           LEFT JOIN pricegroup ON pricegroup.id = pricegroup_id
2190           WHERE parts_id = ?
2191           ORDER BY pricegroup|;
2192     my @values = (conv_i($id), conv_i($id));
2193     my $pkq = prepare_execute_query($form, $dbh, $query, @values);
2194
2195     while (my $pkr = $pkq->fetchrow_hashref('NAME_lc')) {
2196       $pkr->{id}       = $id;
2197       $pkr->{selected} = '';
2198
2199       # if there is an exchange rate change price
2200       if (($form->{exchangerate} * 1) != 0) {
2201         $pkr->{price} /= $form->{exchangerate};
2202       }
2203
2204       $pkr->{price} *= $form->{"basefactor_$i"};
2205       $pkr->{price} *= $basefactor;
2206       $pkr->{price_ufmt} = $pkr->{price};
2207       $pkr->{price} = $form->format_amount($myconfig, $pkr->{price}, 5);
2208
2209       if (!defined $selectedpricegroup_id) {
2210         # new entries in article list, either old invoice was loaded (edit) or a new article was added
2211         # Case A: open old invoice, no pricegroup selected
2212         # Case B: add new article to invoice, no pricegroup selected
2213
2214         # to distinguish case A and B the variable pricegroup_id_$i is used
2215         # for new articles this variable isn't defined, for loaded articles it is
2216         # sellprice can't be used, as it already has 0,00 set
2217
2218         if ($pkr->{pricegroup_id} eq $form->{"pricegroup_id_$i"} and defined $form->{"pricegroup_id_$i"}) {
2219           # Case A
2220           $pkr->{selected}  = ' selected';
2221         } elsif ($pkr->{pricegroup_id} eq $form->{customer_klass}
2222                  and not defined $form->{"pricegroup_id_$i"}
2223                  and $pkr->{price_ufmt} != 0    # only use customer pricegroup price if it has a value, else use default_sellprice
2224                                                 # for the case where pricegroup prices haven't been set
2225                 ) {
2226           # Case B: use default pricegroup of customer
2227
2228           $pkr->{selected}  = ' selected'; # unless $form->{selected};
2229           # no customer pricesgroup set
2230           if ($pkr->{price_ufmt} == $pkr->{default_sellprice}) {
2231
2232             $pkr->{price} = $form->{"sellprice_$i"};
2233
2234           } else {
2235
2236 # this sub should not set anything and only return. --sschoeling, 20090506
2237 # is this correct? put in again... -- grichardson 20110119
2238             $form->{"sellprice_$i"} = $pkr->{price};
2239           }
2240
2241         } elsif ($pkr->{price_ufmt} == $pkr->{default_sellprice} and $pkr->{default_sellprice} != 0) {
2242           $pkr->{price}    = $form->{"sellprice_$i"};
2243           $pkr->{selected} = ' selected';
2244         }
2245       }
2246
2247       # existing article: pricegroup or price changed
2248       if ($selectedpricegroup_id or $selectedpricegroup_id == 0) {
2249         if ($selectedpricegroup_id ne $pricegroup_old) {
2250           # pricegroup has changed
2251           if ($pkr->{pricegroup_id} eq $selectedpricegroup_id) {
2252             $pkr->{selected}  = ' selected';
2253           }
2254         } elsif ( ($form->parse_amount($myconfig, $price_new)
2255                  != $form->parse_amount($myconfig, $form->{"sellprice_$i"}))
2256                   and ($price_new ne 0) and defined $price_new) {
2257           # sellprice has changed
2258           # when loading existing invoices $price_new is NULL
2259           if ($pkr->{pricegroup_id} == 0) {
2260             $pkr->{price}     = $form->{"sellprice_$i"};
2261             $pkr->{selected}  = ' selected';
2262           }
2263         } elsif ($pkr->{pricegroup_id} eq $selectedpricegroup_id) {
2264           # neither sellprice nor pricegroup changed
2265           $pkr->{selected}  = ' selected';
2266           if (    ($pkr->{pricegroup_id} == 0) and ($pkr->{price} == $form->{"sellprice_$i"})) {
2267             # $pkr->{price}                         = $form->{"sellprice_$i"};
2268           } else {
2269             $pkr->{price} = $form->{"sellprice_$i"};
2270           }
2271         }
2272       }
2273       push @{ $form->{PRICES}{$i} }, $pkr;
2274
2275     }
2276     $form->{"basefactor_$i"} *= $basefactor;
2277
2278     $i++;
2279
2280     $pkq->finish;
2281   }
2282
2283   $main::lxdebug->leave_sub();
2284 }
2285
2286 sub has_storno {
2287   $main::lxdebug->enter_sub();
2288
2289   my ($self, $myconfig, $form, $table) = @_;
2290
2291   $main::lxdebug->leave_sub() and return 0 unless ($form->{id});
2292
2293   # make sure there's no funny stuff in $table
2294   # ToDO: die when this happens and throw an error
2295   $main::lxdebug->leave_sub() and return 0 if ($table =~ /\W/);
2296
2297   my $dbh = $form->get_standard_dbh;
2298
2299   my $query = qq|SELECT storno FROM $table WHERE storno_id = ?|;
2300   my ($result) = selectrow_query($form, $dbh, $query, $form->{id});
2301
2302   $main::lxdebug->leave_sub();
2303
2304   return $result;
2305 }
2306
2307 sub is_storno {
2308   $main::lxdebug->enter_sub();
2309
2310   my ($self, $myconfig, $form, $table, $id) = @_;
2311
2312   $main::lxdebug->leave_sub() and return 0 unless ($id);
2313
2314   # make sure there's no funny stuff in $table
2315   # ToDO: die when this happens and throw an error
2316   $main::lxdebug->leave_sub() and return 0 if ($table =~ /\W/);
2317
2318   my $dbh = $form->get_standard_dbh;
2319
2320   my $query = qq|SELECT storno FROM $table WHERE id = ?|;
2321   my ($result) = selectrow_query($form, $dbh, $query, $id);
2322
2323   $main::lxdebug->leave_sub();
2324
2325   return $result;
2326 }
2327
2328 sub get_standard_accno_current_assets {
2329   $main::lxdebug->enter_sub();
2330
2331   my ($self, $myconfig, $form) = @_;
2332
2333   my $dbh = $form->get_standard_dbh;
2334
2335   my $query = qq| SELECT accno FROM chart WHERE id = (SELECT ar_paid_accno_id FROM defaults)|;
2336   my ($result) = selectrow_query($form, $dbh, $query);
2337
2338   $main::lxdebug->leave_sub();
2339
2340   return $result;
2341 }
2342
2343 1;