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