Überflüssige Tabellen customertax, vendortax, partstax entfernt
[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
874                       FROM taxkeys
875                       WHERE chart_id= (SELECT id
876                                        FROM chart
877                                        WHERE accno = ?)
878                       AND startdate <= ?
879                       ORDER BY startdate DESC LIMIT 1),
880                      ?,
881                      (SELECT link FROM chart WHERE accno = ?))|;
882         @values = (conv_i($trans_id), $accno, $form->{amount}{$trans_id}{$accno}, conv_date($form->{invdate}), $accno, conv_date($taxdate), $accno, conv_date($taxdate), conv_i($project_id), $accno);
883         do_query($form, $dbh, $query, @values);
884         $form->{amount}{$trans_id}{$accno} = 0;
885       }
886     }
887
888     foreach my $accno (keys %{ $form->{amount}{$trans_id} }) {
889       $form->{amount}{$trans_id}{$accno} = $form->round_amount($form->{amount}{$trans_id}{$accno}, 2);
890
891       if (!$payments_only && ($form->{amount}{$trans_id}{$accno} != 0)) {
892         $query =
893           qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link)
894              VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?,
895                      (SELECT tax_id
896                       FROM taxkeys
897                       WHERE chart_id= (SELECT id
898                                        FROM chart
899                                        WHERE accno = ?)
900                       AND startdate <= ?
901                       ORDER BY startdate DESC LIMIT 1),
902                      (SELECT taxkey_id
903                       FROM taxkeys
904                       WHERE chart_id= (SELECT id
905                                        FROM chart
906                                        WHERE accno = ?)
907                       AND startdate <= ?
908                       ORDER BY startdate DESC LIMIT 1),
909                      ?,
910                      (SELECT link FROM chart WHERE accno = ?))|;
911         @values = (conv_i($trans_id), $accno, $form->{amount}{$trans_id}{$accno}, conv_date($form->{invdate}), $accno, conv_date($taxdate), $accno, conv_date($taxdate), conv_i($project_id), $accno);
912         do_query($form, $dbh, $query, @values);
913       }
914     }
915   }
916
917   # deduct payment differences from diff
918   for my $i (1 .. $form->{paidaccounts}) {
919     if ($form->{"paid_$i"} != 0) {
920       $amount =
921         $form->round_amount($form->{"paid_$i"} * $form->{exchangerate}, 2);
922       $diff -= $amount - $form->{"paid_$i"} * $form->{exchangerate};
923     }
924   }
925
926   # record payments and offsetting AR
927   if (!$form->{storno}) {
928     for my $i (1 .. $form->{paidaccounts}) {
929
930       if ($form->{"acc_trans_id_$i"}
931           && $payments_only
932           && (SL::DB::Default->get->payments_changeable == 0)) {
933         next;
934       }
935
936       next if ($form->{"paid_$i"} == 0);
937
938       my ($accno) = split(/--/, $form->{"AR_paid_$i"});
939       $form->{"datepaid_$i"} = $form->{invdate}
940       unless ($form->{"datepaid_$i"});
941       $form->{datepaid} = $form->{"datepaid_$i"};
942
943       $exchangerate = 0;
944
945       if ($form->{currency} eq $defaultcurrency) {
946         $form->{"exchangerate_$i"} = 1;
947       } else {
948         $exchangerate              = $form->check_exchangerate($myconfig, $form->{currency}, $form->{"datepaid_$i"}, 'buy');
949         $form->{"exchangerate_$i"} = $exchangerate || $form->parse_amount($myconfig, $form->{"exchangerate_$i"});
950       }
951
952       # record AR
953       $amount = $form->round_amount($form->{"paid_$i"} * $form->{exchangerate} + $diff, 2);
954
955       if ($form->{amount}{ $form->{id} }{ $form->{AR} } != 0) {
956         $query =
957         qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link)
958            VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?,
959                    (SELECT tax_id
960                     FROM taxkeys
961                     WHERE chart_id= (SELECT id
962                                      FROM chart
963                                      WHERE accno = ?)
964                     AND startdate <= ?
965                     ORDER BY startdate DESC LIMIT 1),
966                    (SELECT taxkey_id
967                     FROM taxkeys
968                     WHERE chart_id= (SELECT id
969                                      FROM chart
970                                      WHERE accno = ?)
971                     AND startdate <= ?
972                     ORDER BY startdate DESC LIMIT 1),
973                    ?,
974                    (SELECT link FROM chart WHERE accno = ?))|;
975         @values = (conv_i($form->{"id"}), $form->{AR}, $amount, $form->{"datepaid_$i"}, $form->{AR}, conv_date($taxdate), $form->{AR}, conv_date($taxdate), $project_id, $form->{AR});
976         do_query($form, $dbh, $query, @values);
977       }
978
979       # record payment
980       $form->{"paid_$i"} *= -1;
981       my $gldate = (conv_date($form->{"gldate_$i"}))? conv_date($form->{"gldate_$i"}) : conv_date($form->current_date($myconfig));
982
983       $query =
984       qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, gldate, source, memo, tax_id, taxkey, project_id, chart_link)
985          VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, ?, ?, ?,
986                  (SELECT tax_id
987                   FROM taxkeys
988                   WHERE chart_id= (SELECT id
989                                    FROM chart
990                                    WHERE accno = ?)
991                   AND startdate <= ?
992                   ORDER BY startdate DESC LIMIT 1),
993                  (SELECT taxkey_id
994                   FROM taxkeys
995                   WHERE chart_id= (SELECT id
996                                    FROM chart
997                                    WHERE accno = ?)
998                   AND startdate <= ?
999                   ORDER BY startdate DESC LIMIT 1),
1000                  ?,
1001                  (SELECT link FROM chart WHERE accno = ?))|;
1002       @values = (conv_i($form->{"id"}), $accno, $form->{"paid_$i"}, $form->{"datepaid_$i"},
1003                  $gldate, $form->{"source_$i"}, $form->{"memo_$i"}, $accno, conv_date($taxdate), $accno, conv_date($taxdate), $project_id, $accno);
1004       do_query($form, $dbh, $query, @values);
1005
1006       # exchangerate difference
1007       $form->{fx}{$accno}{ $form->{"datepaid_$i"} } +=
1008         $form->{"paid_$i"} * ($form->{"exchangerate_$i"} - 1) + $diff;
1009
1010       # gain/loss
1011       $amount =
1012         $form->{"paid_$i"} * $form->{exchangerate} - $form->{"paid_$i"} *
1013         $form->{"exchangerate_$i"};
1014       if ($amount > 0) {
1015         $form->{fx}{ $form->{fxgain_accno} }{ $form->{"datepaid_$i"} } += $amount;
1016       } else {
1017         $form->{fx}{ $form->{fxloss_accno} }{ $form->{"datepaid_$i"} } += $amount;
1018       }
1019
1020       $diff = 0;
1021
1022       # update exchange rate
1023       if (($form->{currency} ne $defaultcurrency) && !$exchangerate) {
1024         $form->update_exchangerate($dbh, $form->{currency},
1025                                    $form->{"datepaid_$i"},
1026                                    $form->{"exchangerate_$i"}, 0);
1027       }
1028     }
1029
1030   } else {                      # if (!$form->{storno})
1031     $form->{marge_total} *= -1;
1032   }
1033
1034   IO->set_datepaid(table => 'ar', id => $form->{id}, dbh => $dbh);
1035
1036   # record exchange rate differences and gains/losses
1037   foreach my $accno (keys %{ $form->{fx} }) {
1038     foreach my $transdate (keys %{ $form->{fx}{$accno} }) {
1039       $form->{fx}{$accno}{$transdate} = $form->round_amount($form->{fx}{$accno}{$transdate}, 2);
1040       if ( $form->{fx}{$accno}{$transdate} != 0 ) {
1041
1042         $query =
1043           qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, cleared, fx_transaction, tax_id, taxkey, project_id, chart_link)
1044              VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, '0', '1',
1045                  (SELECT tax_id
1046                   FROM taxkeys
1047                   WHERE chart_id= (SELECT id
1048                                    FROM chart
1049                                    WHERE accno = ?)
1050                   AND startdate <= ?
1051                   ORDER BY startdate DESC LIMIT 1),
1052                  (SELECT taxkey_id
1053                   FROM taxkeys
1054                   WHERE chart_id= (SELECT id
1055                                    FROM chart
1056                                    WHERE accno = ?)
1057                   AND startdate <= ?
1058                   ORDER BY startdate DESC LIMIT 1),
1059                  ?,
1060                  (SELECT link FROM chart WHERE accno = ?))|;
1061         @values = (conv_i($form->{"id"}), $accno, $form->{fx}{$accno}{$transdate}, conv_date($transdate), $accno, conv_date($taxdate), $accno, conv_date($taxdate), conv_i($project_id), $accno);
1062         do_query($form, $dbh, $query, @values);
1063       }
1064     }
1065   }
1066
1067   if ($payments_only) {
1068     $query = qq|UPDATE ar SET paid = ? WHERE id = ?|;
1069     do_query($form, $dbh, $query,  $form->{paid}, conv_i($form->{id}));
1070
1071     $dbh->commit if !$provided_dbh;
1072
1073     $main::lxdebug->leave_sub();
1074     return;
1075   }
1076
1077   $amount = $netamount + $tax;
1078
1079   # save AR record
1080   #erweiterung fuer lieferscheinnummer (donumber) 12.02.09 jb
1081
1082   $query = qq|UPDATE ar set
1083                 invnumber   = ?, ordnumber     = ?, quonumber     = ?, cusordnumber  = ?,
1084                 transdate   = ?, orddate       = ?, quodate       = ?, customer_id   = ?,
1085                 amount      = ?, netamount     = ?, paid          = ?,
1086                 duedate     = ?, deliverydate  = ?, invoice       = ?, shippingpoint = ?,
1087                 shipvia     = ?, terms         = ?, notes         = ?, intnotes      = ?,
1088                 curr        = ?, department_id = ?, payment_id    = ?, taxincluded   = ?,
1089                 type        = ?, language_id   = ?, taxzone_id    = ?, shipto_id     = ?,
1090                 employee_id = ?, salesman_id   = ?, storno_id     = ?, storno        = ?,
1091                 cp_id       = ?, marge_total   = ?, marge_percent = ?,
1092                 globalproject_id               = ?, delivery_customer_id             = ?,
1093                 transaction_description        = ?, delivery_vendor_id               = ?,
1094                 donumber    = ?, invnumber_for_credit_note = ?,        direct_debit  = ?
1095               WHERE id = ?|;
1096   @values = (          $form->{"invnumber"},           $form->{"ordnumber"},             $form->{"quonumber"},          $form->{"cusordnumber"},
1097              conv_date($form->{"invdate"}),  conv_date($form->{"orddate"}),    conv_date($form->{"quodate"}),    conv_i($form->{"customer_id"}),
1098                        $amount,                        $netamount,                       $form->{"paid"},
1099              conv_date($form->{"duedate"}),  conv_date($form->{"deliverydate"}),    '1',                                $form->{"shippingpoint"},
1100                        $form->{"shipvia"},      conv_i($form->{"terms"}),                $form->{"notes"},              $form->{"intnotes"},
1101                        $form->{"currency"},     conv_i($form->{"department_id"}), conv_i($form->{"payment_id"}),        $form->{"taxincluded"} ? 't' : 'f',
1102                        $form->{"type"},         conv_i($form->{"language_id"}),   conv_i($form->{"taxzone_id"}), conv_i($form->{"shipto_id"}),
1103                 conv_i($form->{"employee_id"}), conv_i($form->{"salesman_id"}),   conv_i($form->{storno_id}),           $form->{"storno"} ? 't' : 'f',
1104                 conv_i($form->{"cp_id"}),            1 * $form->{marge_total} ,      1 * $form->{marge_percent},
1105                 conv_i($form->{"globalproject_id"}),                              conv_i($form->{"delivery_customer_id"}),
1106                        $form->{transaction_description},                          conv_i($form->{"delivery_vendor_id"}),
1107                        $form->{"donumber"}, $form->{"invnumber_for_credit_note"},        $form->{direct_debit} ? 't' : 'f',
1108                 conv_i($form->{"id"}));
1109   do_query($form, $dbh, $query, @values);
1110
1111
1112   if ($form->{storno}) {
1113     $query =
1114       qq!UPDATE ar SET
1115            paid = paid + amount,
1116            storno = 't',
1117            intnotes = ? || intnotes
1118          WHERE id = ?!;
1119     do_query($form, $dbh, $query, "Rechnung storniert am $form->{invdate} ", conv_i($form->{"storno_id"}));
1120     do_query($form, $dbh, qq|UPDATE ar SET paid = amount WHERE id = ?|, conv_i($form->{"id"}));
1121   }
1122
1123   # add shipto
1124   $form->{name} = $form->{customer};
1125   $form->{name} =~ s/--\Q$form->{customer_id}\E//;
1126
1127   if (!$form->{shipto_id}) {
1128     $form->add_shipto($dbh, $form->{id}, "AR");
1129   }
1130
1131   # save printed, emailed and queued
1132   $form->save_status($dbh);
1133
1134   Common::webdav_folder($form);
1135
1136   # Link this record to the records it was created from.
1137   RecordLinks->create_links('dbh'        => $dbh,
1138                             'mode'       => 'ids',
1139                             'from_table' => 'oe',
1140                             'from_ids'   => $form->{convert_from_oe_ids},
1141                             'to_table'   => 'ar',
1142                             'to_id'      => $form->{id},
1143     );
1144   delete $form->{convert_from_oe_ids};
1145
1146   my @convert_from_do_ids = map { $_ * 1 } grep { $_ } split m/\s+/, $form->{convert_from_do_ids};
1147
1148   if (scalar @convert_from_do_ids) {
1149     DO->close_orders('dbh' => $dbh,
1150                      'ids' => \@convert_from_do_ids);
1151
1152     RecordLinks->create_links('dbh'        => $dbh,
1153                               'mode'       => 'ids',
1154                               'from_table' => 'delivery_orders',
1155                               'from_ids'   => \@convert_from_do_ids,
1156                               'to_table'   => 'ar',
1157                               'to_id'      => $form->{id},
1158       );
1159   }
1160   delete $form->{convert_from_do_ids};
1161
1162   ARAP->close_orders_if_billed('dbh'     => $dbh,
1163                                'arap_id' => $form->{id},
1164                                'table'   => 'ar',);
1165
1166   # safety check datev export
1167   if ($::instance_conf->get_datev_check_on_sales_invoice) {
1168     my $transdate = $::form->{invdate} ? DateTime->from_lxoffice($::form->{invdate}) : undef;
1169     $transdate  ||= DateTime->today;
1170
1171     my $datev = SL::DATEV->new(
1172       exporttype => DATEV_ET_BUCHUNGEN,
1173       format     => DATEV_FORMAT_KNE,
1174       dbh        => $dbh,
1175       from       => $transdate,
1176       to         => $transdate,
1177     );
1178
1179     $datev->export;
1180
1181     if ($datev->errors) {
1182       $dbh->rollback;
1183       die join "\n", $::locale->text('DATEV check returned errors:'), $datev->errors;
1184     }
1185   }
1186
1187   my $rc = 1;
1188   $dbh->commit if !$provided_dbh;
1189
1190   $main::lxdebug->leave_sub();
1191
1192   return $rc;
1193 }
1194
1195 sub _delete_payments {
1196   $main::lxdebug->enter_sub();
1197
1198   my ($self, $form, $dbh) = @_;
1199
1200   my @delete_acc_trans_ids;
1201
1202   # Delete old payment entries from acc_trans.
1203   my $query =
1204     qq|SELECT acc_trans_id
1205        FROM acc_trans
1206        WHERE (trans_id = ?) AND fx_transaction
1207
1208        UNION
1209
1210        SELECT at.acc_trans_id
1211        FROM acc_trans at
1212        LEFT JOIN chart c ON (at.chart_id = c.id)
1213        WHERE (trans_id = ?) AND (c.link LIKE '%AR_paid%')|;
1214   push @delete_acc_trans_ids, selectall_array_query($form, $dbh, $query, conv_i($form->{id}), conv_i($form->{id}));
1215
1216   $query =
1217     qq|SELECT at.acc_trans_id
1218        FROM acc_trans at
1219        LEFT JOIN chart c ON (at.chart_id = c.id)
1220        WHERE (trans_id = ?)
1221          AND ((c.link = 'AR') OR (c.link LIKE '%:AR') OR (c.link LIKE 'AR:%'))
1222        ORDER BY at.acc_trans_id
1223        OFFSET 1|;
1224   push @delete_acc_trans_ids, selectall_array_query($form, $dbh, $query, conv_i($form->{id}));
1225
1226   if (@delete_acc_trans_ids) {
1227     $query = qq|DELETE FROM acc_trans WHERE acc_trans_id IN (| . join(", ", @delete_acc_trans_ids) . qq|)|;
1228     do_query($form, $dbh, $query);
1229   }
1230
1231   $main::lxdebug->leave_sub();
1232 }
1233
1234 sub post_payment {
1235   $main::lxdebug->enter_sub();
1236
1237   my ($self, $myconfig, $form, $locale) = @_;
1238
1239   # connect to database, turn off autocommit
1240   my $dbh = $form->get_standard_dbh;
1241   $dbh->begin_work;
1242
1243   my (%payments, $old_form, $row, $item, $query, %keep_vars);
1244
1245   $old_form = save_form();
1246
1247   # Delete all entries in acc_trans from prior payments.
1248   if (SL::DB::Default->get->payments_changeable != 0) {
1249     $self->_delete_payments($form, $dbh);
1250   }
1251
1252   # Save the new payments the user made before cleaning up $form.
1253   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 };
1254
1255   # Clean up $form so that old content won't tamper the results.
1256   %keep_vars = map { $_, 1 } qw(login password id);
1257   map { delete $form->{$_} unless $keep_vars{$_} } keys %{ $form };
1258
1259   # Retrieve the invoice from the database.
1260   $self->retrieve_invoice($myconfig, $form);
1261
1262   # Set up the content of $form in the way that IS::post_invoice() expects.
1263   $form->{exchangerate} = $form->format_amount($myconfig, $form->{exchangerate});
1264
1265   for $row (1 .. scalar @{ $form->{invoice_details} }) {
1266     $item = $form->{invoice_details}->[$row - 1];
1267
1268     map { $item->{$_} = $form->format_amount($myconfig, $item->{$_}) } qw(qty sellprice discount);
1269
1270     map { $form->{"${_}_${row}"} = $item->{$_} } keys %{ $item };
1271   }
1272
1273   $form->{rowcount} = scalar @{ $form->{invoice_details} };
1274
1275   delete @{$form}{qw(invoice_details paidaccounts storno paid)};
1276
1277   # Restore the payment options from the user input.
1278   map { $form->{$_} = $payments{$_} } keys %payments;
1279
1280   # Get the AR accno (which is normally done by Form::create_links()).
1281   $query =
1282     qq|SELECT c.accno
1283        FROM acc_trans at
1284        LEFT JOIN chart c ON (at.chart_id = c.id)
1285        WHERE (trans_id = ?)
1286          AND ((c.link = 'AR') OR (c.link LIKE '%:AR') OR (c.link LIKE 'AR:%'))
1287        ORDER BY at.acc_trans_id
1288        LIMIT 1|;
1289
1290   ($form->{AR}) = selectfirst_array_query($form, $dbh, $query, conv_i($form->{id}));
1291
1292   # Post the new payments.
1293   $self->post_invoice($myconfig, $form, $dbh, 1);
1294
1295   restore_form($old_form);
1296
1297   my $rc = $dbh->commit();
1298
1299   $main::lxdebug->leave_sub();
1300
1301   return $rc;
1302 }
1303
1304 sub process_assembly {
1305   $main::lxdebug->enter_sub();
1306
1307   my ($dbh, $myconfig, $form, $id, $totalqty) = @_;
1308
1309   my $query =
1310     qq|SELECT a.parts_id, a.qty, p.assembly, p.partnumber, p.description, p.unit,
1311          p.inventory_accno_id, p.income_accno_id, p.expense_accno_id
1312        FROM assembly a
1313        JOIN parts p ON (a.parts_id = p.id)
1314        WHERE (a.id = ?)|;
1315   my $sth = prepare_execute_query($form, $dbh, $query, conv_i($id));
1316
1317   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1318
1319     my $allocated = 0;
1320
1321     $ref->{inventory_accno_id} *= 1;
1322     $ref->{expense_accno_id}   *= 1;
1323
1324     # multiply by number of assemblies
1325     $ref->{qty} *= $totalqty;
1326
1327     if ($ref->{assembly}) {
1328       &process_assembly($dbh, $myconfig, $form, $ref->{parts_id}, $ref->{qty});
1329       next;
1330     } else {
1331       if ($ref->{inventory_accno_id}) {
1332         $allocated = &cogs($dbh, $myconfig, $form, $ref->{parts_id}, $ref->{qty});
1333       }
1334     }
1335
1336     # save detail record for individual assembly item in invoice table
1337     $query =
1338       qq|INSERT INTO invoice (trans_id, description, parts_id, qty, sellprice, fxsellprice, allocated, assemblyitem, unit)
1339          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)|;
1340     my @values = (conv_i($form->{id}), $ref->{description}, conv_i($ref->{parts_id}), $ref->{qty}, 0, 0, $allocated, 't', $ref->{unit});
1341     do_query($form, $dbh, $query, @values);
1342
1343   }
1344
1345   $sth->finish;
1346
1347   $main::lxdebug->leave_sub();
1348 }
1349
1350 sub cogs {
1351   $main::lxdebug->enter_sub();
1352
1353   # adjust allocated in table invoice according to FIFO princicple
1354   # for a certain part with part_id $id
1355
1356   my ($dbh, $myconfig, $form, $id, $totalqty, $basefactor, $row) = @_;
1357
1358   $basefactor ||= 1;
1359
1360   $form->{taxzone_id} *=1;
1361   my $transdate  = $form->{invdate} ? $dbh->quote($form->{invdate}) : "current_date";
1362   my $taxzone_id = $form->{"taxzone_id"} * 1;
1363   my $query =
1364     qq|SELECT i.id, i.trans_id, i.base_qty, i.allocated, i.sellprice, i.price_factor,
1365          c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from AS inventory_valid,
1366          c2.accno AS    income_accno, c2.new_chart_id AS    income_new_chart, date($transdate) - c2.valid_from AS    income_valid,
1367          c3.accno AS   expense_accno, c3.new_chart_id AS   expense_new_chart, date($transdate) - c3.valid_from AS   expense_valid
1368        FROM invoice i, parts p
1369        LEFT JOIN chart c1 ON ((SELECT inventory_accno_id FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c1.id)
1370        LEFT JOIN chart c2 ON ((SELECT income_accno_id_${taxzone_id} FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c2.id)
1371        LEFT JOIN chart c3 ON ((select expense_accno_id_${taxzone_id} FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c3.id)
1372        WHERE (i.parts_id = p.id)
1373          AND (i.parts_id = ?)
1374          AND ((i.base_qty + i.allocated) < 0)
1375        ORDER BY trans_id|;
1376   my $sth = prepare_execute_query($form, $dbh, $query, conv_i($id));
1377
1378   my $allocated = 0;
1379   my $qty;
1380
1381 # all invoice entries of an example part:
1382
1383 # id | trans_id | base_qty | allocated | sellprice | inventory_accno | income_accno | expense_accno
1384 # ---+----------+----------+-----------+-----------+-----------------+--------------+---------------
1385 #  4 |        4 |       -5 |         5 |  20.00000 | 1140            | 4400         | 5400     bought 5 for 20
1386 #  5 |        5 |        4 |        -4 |  50.00000 | 1140            | 4400         | 5400     sold   4 for 50
1387 #  6 |        6 |        1 |        -1 |  50.00000 | 1140            | 4400         | 5400     sold   1 for 50
1388 #  7 |        7 |       -5 |         1 |  20.00000 | 1140            | 4400         | 5400     bought 5 for 20
1389 #  8 |        8 |        1 |        -1 |  50.00000 | 1140            | 4400         | 5400     sold   1 for 50
1390
1391 # 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
1392 # and all parts have been allocated
1393
1394 # so transaction 8 only sees transaction 7 with unallocated parts and adjusts allocated for that transaction, before allocated was 0
1395 #  7 |        7 |       -5 |         1 |  20.00000 | 1140            | 4400         | 5400     bought 5 for 20
1396
1397 # in this example there are still 4 unsold articles
1398
1399
1400   # search all invoice entries for the part in question, adjusting "allocated"
1401   # until the total number of sold parts has been reached
1402
1403   # ORDER BY trans_id ensures FIFO
1404
1405
1406   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1407     if (($qty = (($ref->{base_qty} * -1) - $ref->{allocated})) > $totalqty) {
1408       $qty = $totalqty;
1409     }
1410
1411     # update allocated in invoice
1412     $form->update_balance($dbh, "invoice", "allocated", qq|id = $ref->{id}|, $qty);
1413
1414     # total expenses and inventory
1415     # sellprice is the cost of the item
1416     my $linetotal = $form->round_amount(($ref->{sellprice} * $qty) / ( ($ref->{price_factor} || 1) * ( $basefactor || 1 )), 2);
1417
1418     if ( $::instance_conf->get_inventory_system eq 'perpetual' ) {
1419       # Bestandsmethode: when selling parts, deduct their purchase value from the inventory account
1420       $ref->{expense_accno} = ($form->{"expense_accno_$row"}) ? $form->{"expense_accno_$row"} : $ref->{expense_accno};
1421       # add to expense
1422       $form->{amount_cogs}{ $form->{id} }{ $ref->{expense_accno} } += -$linetotal;
1423       $form->{expense_inventory} .= " " . $ref->{expense_accno};
1424       $ref->{inventory_accno} = ($form->{"inventory_accno_$row"}) ? $form->{"inventory_accno_$row"} : $ref->{inventory_accno};
1425       # deduct inventory
1426       $form->{amount_cogs}{ $form->{id} }{ $ref->{inventory_accno} } -= -$linetotal;
1427       $form->{expense_inventory} .= " " . $ref->{inventory_accno};
1428     }
1429
1430     # add allocated
1431     $allocated -= $qty;
1432
1433     last if (($totalqty -= $qty) <= 0);
1434   }
1435
1436   $sth->finish;
1437
1438   $main::lxdebug->leave_sub();
1439
1440   return $allocated;
1441 }
1442
1443 sub reverse_invoice {
1444   $main::lxdebug->enter_sub();
1445
1446   my ($dbh, $form) = @_;
1447
1448   # reverse inventory items
1449   my $query =
1450     qq|SELECT i.id, i.parts_id, i.qty, i.assemblyitem, p.assembly, p.inventory_accno_id
1451        FROM invoice i
1452        JOIN parts p ON (i.parts_id = p.id)
1453        WHERE i.trans_id = ?|;
1454   my $sth = prepare_execute_query($form, $dbh, $query, conv_i($form->{"id"}));
1455
1456   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1457
1458     if ($ref->{inventory_accno_id}) {
1459       # de-allocated purchases
1460       $query =
1461         qq|SELECT i.id, i.trans_id, i.allocated
1462            FROM invoice i
1463            WHERE (i.parts_id = ?) AND (i.allocated > 0)
1464            ORDER BY i.trans_id DESC|;
1465       my $sth2 = prepare_execute_query($form, $dbh, $query, conv_i($ref->{"parts_id"}));
1466
1467       while (my $inhref = $sth2->fetchrow_hashref('NAME_lc')) {
1468         my $qty = $ref->{qty};
1469         if (($ref->{qty} - $inhref->{allocated}) > 0) {
1470           $qty = $inhref->{allocated};
1471         }
1472
1473         # update invoice
1474         $form->update_balance($dbh, "invoice", "allocated", qq|id = $inhref->{id}|, $qty * -1);
1475
1476         last if (($ref->{qty} -= $qty) <= 0);
1477       }
1478       $sth2->finish;
1479     }
1480   }
1481
1482   $sth->finish;
1483
1484   # delete acc_trans
1485   my @values = (conv_i($form->{id}));
1486   do_query($form, $dbh, qq|DELETE FROM acc_trans WHERE trans_id = ?|, @values);
1487   do_query($form, $dbh, qq|DELETE FROM invoice WHERE trans_id = ?|, @values);
1488   do_query($form, $dbh, qq|DELETE FROM shipto WHERE (trans_id = ?) AND (module = 'AR')|, @values);
1489
1490   $main::lxdebug->leave_sub();
1491 }
1492
1493 sub delete_invoice {
1494   $main::lxdebug->enter_sub();
1495
1496   my ($self, $myconfig, $form) = @_;
1497
1498   # connect to database
1499   my $dbh = $form->get_standard_dbh;
1500   $dbh->begin_work;
1501
1502   &reverse_invoice($dbh, $form);
1503
1504   my @values = (conv_i($form->{id}));
1505
1506   # Falls wir ein Storno haben, müssen zwei Felder in der stornierten Rechnung wieder
1507   # zurückgesetzt werden. Vgl:
1508   #  id | storno | storno_id |  paid   |  amount
1509   #----+--------+-----------+---------+-----------
1510   # 18 | f      |           | 0.00000 | 119.00000
1511   # ZU:
1512   # 18 | t      |           |  119.00000 |  119.00000
1513   #
1514   if($form->{storno}){
1515     # storno_id auslesen und korrigieren
1516     my ($invoice_id) = selectfirst_array_query($form, $dbh, qq|SELECT storno_id FROM ar WHERE id = ?|,@values);
1517     do_query($form, $dbh, qq|UPDATE ar SET storno = 'f', paid = 0 WHERE id = ?|, $invoice_id);
1518   }
1519
1520   # delete spool files
1521   my @spoolfiles = selectall_array_query($form, $dbh, qq|SELECT spoolfile FROM status WHERE trans_id = ?|, @values);
1522
1523   my @queries = (
1524     qq|DELETE FROM status WHERE trans_id = ?|,
1525     qq|DELETE FROM periodic_invoices WHERE ar_id = ?|,
1526     qq|DELETE FROM ar WHERE id = ?|,
1527   );
1528
1529   map { do_query($form, $dbh, $_, @values) } @queries;
1530
1531   my $rc = $dbh->commit;
1532
1533   if ($rc) {
1534     my $spool = $::lx_office_conf{paths}->{spool};
1535     map { unlink "$spool/$_" if -f "$spool/$_"; } @spoolfiles;
1536   }
1537
1538   $main::lxdebug->leave_sub();
1539
1540   return $rc;
1541 }
1542
1543 sub retrieve_invoice {
1544   $main::lxdebug->enter_sub();
1545
1546   my ($self, $myconfig, $form) = @_;
1547
1548   # connect to database
1549   my $dbh = $form->get_standard_dbh;
1550
1551   my ($sth, $ref, $query);
1552
1553   my $query_transdate = !$form->{id} ? ", current_date AS invdate" : '';
1554
1555   $query =
1556     qq|SELECT
1557          (SELECT c.accno FROM chart c WHERE d.inventory_accno_id = c.id) AS inventory_accno,
1558          (SELECT c.accno FROM chart c WHERE d.income_accno_id = c.id)    AS income_accno,
1559          (SELECT c.accno FROM chart c WHERE d.expense_accno_id = c.id)   AS expense_accno,
1560          (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id)    AS fxgain_accno,
1561          (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id)    AS fxloss_accno,
1562          d.curr AS currencies
1563          ${query_transdate}
1564        FROM defaults d|;
1565
1566   $ref = selectfirst_hashref_query($form, $dbh, $query);
1567   map { $form->{$_} = $ref->{$_} } keys %{ $ref };
1568
1569   if ($form->{id}) {
1570     my $id = conv_i($form->{id});
1571
1572     # retrieve invoice
1573     #erweiterung um das entsprechende feld lieferscheinnummer (a.donumber) in der html-maske anzuzeigen 12.02.2009 jb
1574
1575     $query =
1576       qq|SELECT
1577            a.invnumber, a.ordnumber, a.quonumber, a.cusordnumber,
1578            a.orddate, a.quodate, a.globalproject_id,
1579            a.transdate AS invdate, a.deliverydate, a.paid, a.storno, a.gldate,
1580            a.shippingpoint, a.shipvia, a.terms, a.notes, a.intnotes, a.taxzone_id,
1581            a.duedate, a.taxincluded, a.curr AS currency, a.shipto_id, a.cp_id,
1582            a.employee_id, a.salesman_id, a.payment_id,
1583            a.language_id, a.delivery_customer_id, a.delivery_vendor_id, a.type,
1584            a.transaction_description, a.donumber, a.invnumber_for_credit_note,
1585            a.marge_total, a.marge_percent, a.direct_debit,
1586            e.name AS employee
1587          FROM ar a
1588          LEFT JOIN employee e ON (e.id = a.employee_id)
1589          WHERE a.id = ?|;
1590     $ref = selectfirst_hashref_query($form, $dbh, $query, $id);
1591     map { $form->{$_} = $ref->{$_} } keys %{ $ref };
1592
1593     # remove any trailing whitespace
1594     $form->{currency} =~ s/\s*$//;
1595
1596     $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{invdate}, "buy");
1597
1598     # get shipto
1599     $query = qq|SELECT * FROM shipto WHERE (trans_id = ?) AND (module = 'AR')|;
1600     $ref = selectfirst_hashref_query($form, $dbh, $query, $id);
1601     delete $ref->{id};
1602     map { $form->{$_} = $ref->{$_} } keys %{ $ref };
1603
1604     foreach my $vc (qw(customer vendor)) {
1605       next if !$form->{"delivery_${vc}_id"};
1606       ($form->{"delivery_${vc}_string"}) = selectrow_query($form, $dbh, qq|SELECT name FROM customer WHERE id = ?|, $id);
1607     }
1608
1609     # get printed, emailed
1610     $query = qq|SELECT printed, emailed, spoolfile, formname FROM status WHERE trans_id = ?|;
1611     $sth = prepare_execute_query($form, $dbh, $query, $id);
1612
1613     while ($ref = $sth->fetchrow_hashref('NAME_lc')) {
1614       $form->{printed} .= "$ref->{formname} " if $ref->{printed};
1615       $form->{emailed} .= "$ref->{formname} " if $ref->{emailed};
1616       $form->{queued} .= "$ref->{formname} $ref->{spoolfile} " if $ref->{spoolfile};
1617     }
1618     $sth->finish;
1619     map { $form->{$_} =~ s/ +$//g } qw(printed emailed queued);
1620
1621     my $transdate = $form->{deliverydate} ? $dbh->quote($form->{deliverydate})
1622                   : $form->{invdate}      ? $dbh->quote($form->{invdate})
1623                   :                         "current_date";
1624
1625
1626     my $taxzone_id = $form->{taxzone_id} *= 1;
1627     $taxzone_id = 0 if (0 > $taxzone_id) || (3 < $taxzone_id);
1628
1629     # retrieve individual items
1630     $query =
1631       qq|SELECT
1632            c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from AS inventory_valid,
1633            c2.accno AS income_accno,    c2.new_chart_id AS income_new_chart,    date($transdate) - c2.valid_from as income_valid,
1634            c3.accno AS expense_accno,   c3.new_chart_id AS expense_new_chart,   date($transdate) - c3.valid_from AS expense_valid,
1635
1636            i.id AS invoice_id,
1637            i.description, i.longdescription, i.qty, i.fxsellprice AS sellprice, i.discount, i.parts_id AS id, i.unit, i.deliverydate AS reqdate,
1638            i.project_id, i.serialnumber, i.id AS invoice_pos, i.pricegroup_id, i.ordnumber, i.transdate, i.cusordnumber, i.subtotal, i.lastcost,
1639            i.price_factor_id, i.price_factor, i.marge_price_factor,
1640            p.partnumber, p.assembly, p.bin, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id, p.formel, p.listprice,
1641            pr.projectnumber, pg.partsgroup, prg.pricegroup
1642
1643          FROM invoice i
1644          LEFT JOIN parts p ON (i.parts_id = p.id)
1645          LEFT JOIN project pr ON (i.project_id = pr.id)
1646          LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id)
1647          LEFT JOIN pricegroup prg ON (i.pricegroup_id = prg.id)
1648
1649          LEFT JOIN chart c1 ON ((SELECT inventory_accno_id             FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c1.id)
1650          LEFT JOIN chart c2 ON ((SELECT income_accno_id_${taxzone_id}  FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c2.id)
1651          LEFT JOIN chart c3 ON ((SELECT expense_accno_id_${taxzone_id} FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c3.id)
1652
1653          WHERE (i.trans_id = ?) AND NOT (i.assemblyitem = '1') ORDER BY i.id|;
1654
1655     $sth = prepare_execute_query($form, $dbh, $query, $id);
1656
1657     while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1658       # Retrieve custom variables.
1659       my $cvars = CVar->get_custom_variables(dbh        => $dbh,
1660                                              module     => 'IC',
1661                                              sub_module => 'invoice',
1662                                              trans_id   => $ref->{invoice_id},
1663                                             );
1664       map { $ref->{"ic_cvar_$_->{name}"} = $_->{value} } @{ $cvars };
1665       delete $ref->{invoice_id};
1666
1667       map({ delete($ref->{$_}); } qw(inventory_accno inventory_new_chart inventory_valid)) if !$ref->{"part_inventory_accno_id"};
1668       delete($ref->{"part_inventory_accno_id"});
1669
1670       foreach my $type (qw(inventory income expense)) {
1671         while ($ref->{"${type}_new_chart"} && ($ref->{"${type}_valid"} >=0)) {
1672           my $query = qq|SELECT accno, new_chart_id, date($transdate) - valid_from FROM chart WHERE id = ?|;
1673           @$ref{ map $type.$_, qw(_accno _new_chart _valid) } = selectrow_query($form, $dbh, $query, $ref->{"${type}_new_chart"});
1674         }
1675       }
1676
1677       # get tax rates and description
1678       my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
1679       $query =
1680         qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber FROM tax t
1681            LEFT JOIN chart c ON (c.id = t.chart_id)
1682            WHERE t.id IN
1683              (SELECT tk.tax_id FROM taxkeys tk
1684               WHERE tk.chart_id = (SELECT id FROM chart WHERE accno = ?)
1685                 AND startdate <= date($transdate)
1686               ORDER BY startdate DESC LIMIT 1)
1687            ORDER BY c.accno|;
1688       my $stw = prepare_execute_query($form, $dbh, $query, $accno_id);
1689       $ref->{taxaccounts} = "";
1690       my $i=0;
1691       while (my $ptr = $stw->fetchrow_hashref('NAME_lc')) {
1692
1693         if (($ptr->{accno} eq "") && ($ptr->{rate} == 0)) {
1694           $i++;
1695           $ptr->{accno} = $i;
1696         }
1697         $ref->{taxaccounts} .= "$ptr->{accno} ";
1698
1699         if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
1700           $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
1701           $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
1702           $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
1703           $form->{taxaccounts} .= "$ptr->{accno} ";
1704         }
1705
1706       }
1707
1708       $ref->{qty} *= -1 if $form->{type} eq "credit_note";
1709
1710       chop $ref->{taxaccounts};
1711       push @{ $form->{invoice_details} }, $ref;
1712       $stw->finish;
1713     }
1714     $sth->finish;
1715
1716     Common::webdav_folder($form);
1717   }
1718
1719   my $rc = $dbh->commit;
1720
1721   $main::lxdebug->leave_sub();
1722
1723   return $rc;
1724 }
1725
1726 sub get_customer {
1727   $main::lxdebug->enter_sub();
1728
1729   my ($self, $myconfig, $form) = @_;
1730
1731   # connect to database
1732   my $dbh = $form->get_standard_dbh;
1733
1734   my $dateformat = $myconfig->{dateformat};
1735   $dateformat .= "yy" if $myconfig->{dateformat} !~ /^y/;
1736
1737   my (@values, $duedate, $ref, $query);
1738
1739   if ($form->{invdate}) {
1740     $duedate = "to_date(?, '$dateformat')";
1741     push @values, $form->{invdate};
1742   } else {
1743     $duedate = "current_date";
1744   }
1745
1746   my $cid = conv_i($form->{customer_id});
1747   my $payment_id;
1748
1749   if ($form->{payment_id}) {
1750     $payment_id = "(pt.id = ?) OR";
1751     push @values, conv_i($form->{payment_id});
1752   }
1753
1754   # get customer
1755   $query =
1756     qq|SELECT
1757          c.id AS customer_id, c.name AS customer, c.discount as customer_discount, c.creditlimit, c.terms,
1758          c.email, c.cc, c.bcc, c.language_id, c.payment_id,
1759          c.street, c.zipcode, c.city, c.country,
1760          c.notes AS intnotes, c.klass as customer_klass, c.taxzone_id, c.salesman_id, c.curr,
1761          c.taxincluded_checked, c.direct_debit,
1762          $duedate + COALESCE(pt.terms_netto, 0) AS duedate,
1763          b.discount AS tradediscount, b.description AS business
1764        FROM customer c
1765        LEFT JOIN business b ON (b.id = c.business_id)
1766        LEFT JOIN payment_terms pt ON ($payment_id (c.payment_id = pt.id))
1767        WHERE c.id = ?|;
1768   push @values, $cid;
1769   $ref = selectfirst_hashref_query($form, $dbh, $query, @values);
1770
1771   delete $ref->{salesman_id} if !$ref->{salesman_id};
1772
1773   map { $form->{$_} = $ref->{$_} } keys %$ref;
1774
1775   # remove any trailing whitespace
1776   $form->{curr} =~ s/\s*$//;
1777
1778   # use customer currency if not empty
1779   $form->{currency} = $form->{curr} if $form->{curr};
1780
1781   $query =
1782     qq|SELECT sum(amount - paid) AS dunning_amount
1783        FROM ar
1784        WHERE (paid < amount)
1785          AND (customer_id = ?)
1786          AND (dunning_config_id IS NOT NULL)|;
1787   $ref = selectfirst_hashref_query($form, $dbh, $query, $cid);
1788   map { $form->{$_} = $ref->{$_} } keys %$ref;
1789
1790   $query =
1791     qq|SELECT dnn.dunning_description AS max_dunning_level
1792        FROM dunning_config dnn
1793        WHERE id IN (SELECT dunning_config_id
1794                     FROM ar
1795                     WHERE (paid < amount) AND (customer_id = ?) AND (dunning_config_id IS NOT NULL))
1796        ORDER BY dunning_level DESC LIMIT 1|;
1797   $ref = selectfirst_hashref_query($form, $dbh, $query, $cid);
1798   map { $form->{$_} = $ref->{$_} } keys %$ref;
1799
1800   $form->{creditremaining} = $form->{creditlimit};
1801   $query = qq|SELECT SUM(amount - paid) FROM ar WHERE customer_id = ?|;
1802   my ($value) = selectrow_query($form, $dbh, $query, $cid);
1803   $form->{creditremaining} -= $value;
1804
1805   $query =
1806     qq|SELECT o.amount,
1807          (SELECT e.buy FROM exchangerate e
1808           WHERE e.curr = o.curr
1809             AND e.transdate = o.transdate)
1810        FROM oe o
1811        WHERE o.customer_id = ?
1812          AND o.quotation = '0'
1813          AND o.closed = '0'|;
1814   my $sth = prepare_execute_query($form, $dbh, $query, $cid);
1815
1816   while (my ($amount, $exch) = $sth->fetchrow_array) {
1817     $exch = 1 unless $exch;
1818     $form->{creditremaining} -= $amount * $exch;
1819   }
1820   $sth->finish;
1821
1822   # get shipto if we did not converted an order or invoice
1823   if (!$form->{shipto}) {
1824     map { delete $form->{$_} }
1825       qw(shiptoname shiptodepartment_1 shiptodepartment_2
1826          shiptostreet shiptozipcode shiptocity shiptocountry
1827          shiptocontact shiptophone shiptofax shiptoemail);
1828
1829     $query = qq|SELECT * FROM shipto WHERE trans_id = ? AND module = 'CT'|;
1830     $ref = selectfirst_hashref_query($form, $dbh, $query, $cid);
1831     delete $ref->{id};
1832     map { $form->{$_} = $ref->{$_} } keys %$ref;
1833   }
1834
1835   # setup last accounts used for this customer
1836   if (!$form->{id} && $form->{type} !~ /_(order|quotation)/) {
1837     $query =
1838       qq|SELECT c.id, c.accno, c.description, c.link, c.category
1839          FROM chart c
1840          JOIN acc_trans ac ON (ac.chart_id = c.id)
1841          JOIN ar a ON (a.id = ac.trans_id)
1842          WHERE a.customer_id = ?
1843            AND NOT (c.link LIKE '%_tax%' OR c.link LIKE '%_paid%')
1844            AND a.id IN (SELECT max(a2.id) FROM ar a2 WHERE a2.customer_id = ?)|;
1845     $sth = prepare_execute_query($form, $dbh, $query, $cid, $cid);
1846
1847     my $i = 0;
1848     while ($ref = $sth->fetchrow_hashref('NAME_lc')) {
1849       if ($ref->{category} eq 'I') {
1850         $i++;
1851         $form->{"AR_amount_$i"} = "$ref->{accno}--$ref->{description}";
1852
1853         if ($form->{initial_transdate}) {
1854           my $tax_query =
1855             qq|SELECT tk.tax_id, t.rate
1856                FROM taxkeys tk
1857                LEFT JOIN tax t ON tk.tax_id = t.id
1858                WHERE (tk.chart_id = ?) AND (startdate <= date(?))
1859                ORDER BY tk.startdate DESC
1860                LIMIT 1|;
1861           my ($tax_id, $rate) =
1862             selectrow_query($form, $dbh, $tax_query, $ref->{id},
1863                             $form->{initial_transdate});
1864           $form->{"taxchart_$i"} = "${tax_id}--${rate}";
1865         }
1866       }
1867       if ($ref->{category} eq 'A') {
1868         $form->{ARselected} = $form->{AR_1} = $ref->{accno};
1869       }
1870     }
1871     $sth->finish;
1872     $form->{rowcount} = $i if ($i && !$form->{type});
1873   }
1874
1875   $main::lxdebug->leave_sub();
1876 }
1877
1878 sub retrieve_item {
1879   $main::lxdebug->enter_sub();
1880
1881   my ($self, $myconfig, $form) = @_;
1882
1883   # connect to database
1884   my $dbh = $form->get_standard_dbh;
1885
1886   my $i = $form->{rowcount};
1887
1888   my $where = qq|NOT p.obsolete = '1'|;
1889   my @values;
1890
1891   foreach my $column (qw(p.partnumber p.description pgpartsgroup )) {
1892     my ($table, $field) = split m/\./, $column;
1893     next if !$form->{"${field}_${i}"};
1894     $where .= qq| AND lower(${column}) ILIKE ?|;
1895     push @values, '%' . $form->{"${field}_${i}"} . '%';
1896   }
1897
1898   #Es soll auch nach EAN gesucht werden, ohne Einschränkung durch Beschreibung
1899   if ($form->{"partnumber_$i"} && !$form->{"description_$i"}) {
1900     $where .= qq| OR (NOT p.obsolete = '1' AND p.ean = ? )|;
1901     push @values, $form->{"partnumber_$i"};
1902   }
1903
1904   # Search for part ID overrides all other criteria.
1905   if ($form->{"id_${i}"}) {
1906     $where  = qq|p.id = ?|;
1907     @values = ($form->{"id_${i}"});
1908   }
1909
1910   if ($form->{"description_$i"}) {
1911     $where .= qq| ORDER BY p.description|;
1912   } else {
1913     $where .= qq| ORDER BY p.partnumber|;
1914   }
1915
1916   my $transdate;
1917   if ($form->{type} eq "invoice") {
1918     $transdate =
1919       $form->{deliverydate} ? $dbh->quote($form->{deliverydate}) :
1920       $form->{invdate}      ? $dbh->quote($form->{invdate}) :
1921                               "current_date";
1922   } else {
1923     $transdate =
1924       $form->{transdate}    ? $dbh->quote($form->{transdate}) :
1925                               "current_date";
1926   }
1927
1928   my $taxzone_id = $form->{taxzone_id} * 1;
1929   $taxzone_id = 0 if (0 > $taxzone_id) || (3 < $taxzone_id);
1930
1931   my $query =
1932     qq|SELECT
1933          p.id, p.partnumber, p.description, p.sellprice,
1934          p.listprice, p.inventory_accno_id, p.lastcost,
1935
1936          c1.accno AS inventory_accno,
1937          c1.new_chart_id AS inventory_new_chart,
1938          date($transdate) - c1.valid_from AS inventory_valid,
1939
1940          c2.accno AS income_accno,
1941          c2.new_chart_id AS income_new_chart,
1942          date($transdate)  - c2.valid_from AS income_valid,
1943
1944          c3.accno AS expense_accno,
1945          c3.new_chart_id AS expense_new_chart,
1946          date($transdate) - c3.valid_from AS expense_valid,
1947
1948          p.unit, p.assembly, p.bin, p.onhand,
1949          p.notes AS partnotes, p.notes AS longdescription,
1950          p.not_discountable, p.formel, p.payment_id AS part_payment_id,
1951          p.price_factor_id,
1952
1953          pfac.factor AS price_factor,
1954
1955          pg.partsgroup
1956
1957        FROM parts p
1958        LEFT JOIN chart c1 ON
1959          ((SELECT inventory_accno_id
1960            FROM buchungsgruppen
1961            WHERE id = p.buchungsgruppen_id) = c1.id)
1962        LEFT JOIN chart c2 ON
1963          ((SELECT income_accno_id_${taxzone_id}
1964            FROM buchungsgruppen
1965            WHERE id = p.buchungsgruppen_id) = c2.id)
1966        LEFT JOIN chart c3 ON
1967          ((SELECT expense_accno_id_${taxzone_id}
1968            FROM buchungsgruppen
1969            WHERE id = p.buchungsgruppen_id) = c3.id)
1970        LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id)
1971        LEFT JOIN price_factors pfac ON (pfac.id = p.price_factor_id)
1972        WHERE $where|;
1973   my $sth = prepare_execute_query($form, $dbh, $query, @values);
1974
1975   my @translation_queries = ( [ qq|SELECT tr.translation, tr.longdescription
1976                                    FROM translation tr
1977                                    WHERE tr.language_id = ? AND tr.parts_id = ?| ],
1978                               [ qq|SELECT tr.translation, tr.longdescription
1979                                    FROM translation tr
1980                                    WHERE tr.language_id IN
1981                                      (SELECT id
1982                                       FROM language
1983                                       WHERE article_code = (SELECT article_code FROM language WHERE id = ?))
1984                                      AND tr.parts_id = ?
1985                                    LIMIT 1| ] );
1986   map { push @{ $_ }, prepare_query($form, $dbh, $_->[0]) } @translation_queries;
1987
1988   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1989
1990     # In der Buchungsgruppe ist immer ein Bestandskonto verknuepft, auch wenn
1991     # es sich um eine Dienstleistung handelt. Bei Dienstleistungen muss das
1992     # Buchungskonto also aus dem Ergebnis rausgenommen werden.
1993     if (!$ref->{inventory_accno_id}) {
1994       map({ delete($ref->{"inventory_${_}"}); } qw(accno new_chart valid));
1995     }
1996     delete($ref->{inventory_accno_id});
1997
1998     foreach my $type (qw(inventory income expense)) {
1999       while ($ref->{"${type}_new_chart"} && ($ref->{"${type}_valid"} >=0)) {
2000         my $query =
2001           qq|SELECT accno, new_chart_id, date($transdate) - valid_from
2002              FROM chart
2003              WHERE id = ?|;
2004         ($ref->{"${type}_accno"},
2005          $ref->{"${type}_new_chart"},
2006          $ref->{"${type}_valid"})
2007           = selectrow_query($form, $dbh, $query, $ref->{"${type}_new_chart"});
2008       }
2009     }
2010
2011     if ($form->{payment_id} eq "") {
2012       $form->{payment_id} = $form->{part_payment_id};
2013     }
2014
2015     # get tax rates and description
2016     my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
2017     $query =
2018       qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber
2019          FROM tax t
2020          LEFT JOIN chart c ON (c.id = t.chart_id)
2021          WHERE t.id in
2022            (SELECT tk.tax_id
2023             FROM taxkeys tk
2024             WHERE tk.chart_id = (SELECT id from chart WHERE accno = ?)
2025               AND startdate <= ?
2026             ORDER BY startdate DESC
2027             LIMIT 1)
2028          ORDER BY c.accno|;
2029     @values = ($accno_id, $transdate eq "current_date" ? "now" : $transdate);
2030     my $stw = $dbh->prepare($query);
2031     $stw->execute(@values) || $form->dberror($query);
2032
2033     $ref->{taxaccounts} = "";
2034     my $i = 0;
2035     while (my $ptr = $stw->fetchrow_hashref('NAME_lc')) {
2036
2037       if (($ptr->{accno} eq "") && ($ptr->{rate} == 0)) {
2038         $i++;
2039         $ptr->{accno} = $i;
2040       }
2041       $ref->{taxaccounts} .= "$ptr->{accno} ";
2042
2043       if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
2044         $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
2045         $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
2046         $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
2047         $form->{taxaccounts} .= "$ptr->{accno} ";
2048       }
2049
2050     }
2051
2052     $stw->finish;
2053     chop $ref->{taxaccounts};
2054
2055     if ($form->{language_id}) {
2056       for my $spec (@translation_queries) {
2057         do_statement($form, $spec->[1], $spec->[0], conv_i($form->{language_id}), conv_i($ref->{id}));
2058         my ($translation, $longdescription) = $spec->[1]->fetchrow_array;
2059         next unless $translation;
2060         $ref->{description} = $translation;
2061         $ref->{longdescription} = $longdescription;
2062         last;
2063       }
2064     }
2065
2066     $ref->{onhand} *= 1;
2067
2068     push @{ $form->{item_list} }, $ref;
2069   }
2070   $sth->finish;
2071   $_->[1]->finish for @translation_queries;
2072
2073   foreach my $item (@{ $form->{item_list} }) {
2074     my $custom_variables = CVar->get_custom_variables(module   => 'IC',
2075                                                       trans_id => $item->{id},
2076                                                       dbh      => $dbh,
2077                                                      );
2078
2079     map { $item->{"ic_cvar_" . $_->{name} } = $_->{value} } @{ $custom_variables };
2080   }
2081
2082   $main::lxdebug->leave_sub();
2083 }
2084
2085 ##########################
2086 # get pricegroups from database
2087 # build up selected pricegroup
2088 # if an exchange rate - change price
2089 # for each part
2090 #
2091 sub get_pricegroups_for_parts {
2092
2093   $main::lxdebug->enter_sub();
2094
2095   my ($self, $myconfig, $form) = @_;
2096
2097   my $dbh = $form->get_standard_dbh;
2098
2099   $form->{"PRICES"} = {};
2100
2101   my $i  = 1;
2102   my $id = 0;
2103   my $all_units = AM->retrieve_units($myconfig, $form);
2104   while (($form->{"id_$i"}) or ($form->{"new_id_$i"})) {
2105     $form->{"PRICES"}{$i} = [];
2106
2107     $id = $form->{"id_$i"};
2108
2109     if (!($form->{"id_$i"}) and $form->{"new_id_$i"}) {
2110       $id = $form->{"new_id_$i"};
2111     }
2112
2113     my ($price, $selectedpricegroup_id) = split(/--/, $form->{"sellprice_pg_$i"});
2114
2115     my $pricegroup_old = $form->{"pricegroup_old_$i"};
2116
2117     # sellprice has format 13,0000 or 0,00000,  can't check for 0 numerically
2118     my $sellprice = $form->{"sellprice_$i"};
2119     my $pricegroup_id = $form->{"pricegroup_id_$i"};
2120     $form->{"new_pricegroup_$i"} = $selectedpricegroup_id;
2121     $form->{"old_pricegroup_$i"} = $pricegroup_old;
2122
2123     my $price_new = $form->{"price_new_$i"};
2124     my $price_old = $form->{"price_old_$i"};
2125
2126     if (!$form->{"unit_old_$i"}) {
2127       # Neue Ware aus der Datenbank. In diesem Fall ist unit_$i die
2128       # Einheit, wie sie in den Stammdaten hinterlegt wurde.
2129       # Es sollte also angenommen werden, dass diese ausgewaehlt war.
2130       $form->{"unit_old_$i"} = $form->{"unit_$i"};
2131     }
2132
2133     # Die zuletzt ausgewaehlte mit der aktuell ausgewaehlten Einheit
2134     # vergleichen und bei Unterschied den Preis entsprechend umrechnen.
2135     $form->{"selected_unit_$i"} = $form->{"unit_$i"} unless ($form->{"selected_unit_$i"});
2136
2137     if (!$all_units->{$form->{"selected_unit_$i"}} ||
2138         ($all_units->{$form->{"selected_unit_$i"}}->{"base_unit"} ne
2139          $all_units->{$form->{"unit_old_$i"}}->{"base_unit"})) {
2140       # Die ausgewaehlte Einheit ist fuer diesen Artikel nicht gueltig
2141       # (z.B. Dimensionseinheit war ausgewaehlt, es handelt sich aber
2142       # um eine Dienstleistung). Dann keinerlei Umrechnung vornehmen.
2143       $form->{"unit_old_$i"} = $form->{"selected_unit_$i"} = $form->{"unit_$i"};
2144     }
2145
2146     my $basefactor = 1;
2147
2148     if ($form->{"unit_old_$i"} ne $form->{"selected_unit_$i"}) {
2149       if (defined($all_units->{$form->{"unit_old_$i"}}->{"factor"}) &&
2150           $all_units->{$form->{"unit_old_$i"}}->{"factor"}) {
2151         $basefactor = $all_units->{$form->{"selected_unit_$i"}}->{"factor"} /
2152           $all_units->{$form->{"unit_old_$i"}}->{"factor"};
2153       }
2154     }
2155
2156     if (!$form->{"basefactor_$i"}) {
2157       $form->{"basefactor_$i"} = 1;
2158     }
2159
2160     my $query =
2161        qq|SELECT
2162             0 as pricegroup_id,
2163             sellprice AS default_sellprice,
2164             '' AS pricegroup,
2165             sellprice AS price,
2166             'selected' AS selected
2167           FROM parts
2168           WHERE id = ?
2169           UNION ALL
2170           SELECT
2171            pricegroup_id,
2172            parts.sellprice AS default_sellprice,
2173            pricegroup.pricegroup,
2174            price,
2175            '' AS selected
2176           FROM prices
2177           LEFT JOIN parts ON parts.id = parts_id
2178           LEFT JOIN pricegroup ON pricegroup.id = pricegroup_id
2179           WHERE parts_id = ?
2180           ORDER BY pricegroup|;
2181     my @values = (conv_i($id), conv_i($id));
2182     my $pkq = prepare_execute_query($form, $dbh, $query, @values);
2183
2184     while (my $pkr = $pkq->fetchrow_hashref('NAME_lc')) {
2185       $pkr->{id}       = $id;
2186       $pkr->{selected} = '';
2187
2188       # if there is an exchange rate change price
2189       if (($form->{exchangerate} * 1) != 0) {
2190         $pkr->{price} /= $form->{exchangerate};
2191       }
2192
2193       $pkr->{price} *= $form->{"basefactor_$i"};
2194       $pkr->{price} *= $basefactor;
2195       $pkr->{price_ufmt} = $pkr->{price};
2196       $pkr->{price} = $form->format_amount($myconfig, $pkr->{price}, 5);
2197
2198       if (!defined $selectedpricegroup_id) {
2199         # new entries in article list, either old invoice was loaded (edit) or a new article was added
2200         # Case A: open old invoice, no pricegroup selected
2201         # Case B: add new article to invoice, no pricegroup selected
2202
2203         # to distinguish case A and B the variable pricegroup_id_$i is used
2204         # for new articles this variable isn't defined, for loaded articles it is
2205         # sellprice can't be used, as it already has 0,00 set
2206
2207         if ($pkr->{pricegroup_id} eq $form->{"pricegroup_id_$i"} and defined $form->{"pricegroup_id_$i"}) {
2208           # Case A
2209           $pkr->{selected}  = ' selected';
2210         } elsif ($pkr->{pricegroup_id} eq $form->{customer_klass}
2211                  and not defined $form->{"pricegroup_id_$i"}
2212                  and $pkr->{price_ufmt} != 0    # only use customer pricegroup price if it has a value, else use default_sellprice
2213                                                 # for the case where pricegroup prices haven't been set
2214                 ) {
2215           # Case B: use default pricegroup of customer
2216
2217           $pkr->{selected}  = ' selected'; # unless $form->{selected};
2218           # no customer pricesgroup set
2219           if ($pkr->{price_ufmt} == $pkr->{default_sellprice}) {
2220
2221             $pkr->{price} = $form->{"sellprice_$i"};
2222
2223           } else {
2224
2225 # this sub should not set anything and only return. --sschoeling, 20090506
2226 # is this correct? put in again... -- grichardson 20110119
2227             $form->{"sellprice_$i"} = $pkr->{price};
2228           }
2229
2230         } elsif ($pkr->{price_ufmt} == $pkr->{default_sellprice} and $pkr->{default_sellprice} != 0) {
2231           $pkr->{price}    = $form->{"sellprice_$i"};
2232           $pkr->{selected} = ' selected';
2233         }
2234       }
2235
2236       # existing article: pricegroup or price changed
2237       if ($selectedpricegroup_id or $selectedpricegroup_id == 0) {
2238         if ($selectedpricegroup_id ne $pricegroup_old) {
2239           # pricegroup has changed
2240           if ($pkr->{pricegroup_id} eq $selectedpricegroup_id) {
2241             $pkr->{selected}  = ' selected';
2242           }
2243         } elsif ( ($form->parse_amount($myconfig, $price_new)
2244                  != $form->parse_amount($myconfig, $form->{"sellprice_$i"}))
2245                   and ($price_new ne 0) and defined $price_new) {
2246           # sellprice has changed
2247           # when loading existing invoices $price_new is NULL
2248           if ($pkr->{pricegroup_id} == 0) {
2249             $pkr->{price}     = $form->{"sellprice_$i"};
2250             $pkr->{selected}  = ' selected';
2251           }
2252         } elsif ($pkr->{pricegroup_id} eq $selectedpricegroup_id) {
2253           # neither sellprice nor pricegroup changed
2254           $pkr->{selected}  = ' selected';
2255           if (    ($pkr->{pricegroup_id} == 0) and ($pkr->{price} == $form->{"sellprice_$i"})) {
2256             # $pkr->{price}                         = $form->{"sellprice_$i"};
2257           } else {
2258             $pkr->{price} = $form->{"sellprice_$i"};
2259           }
2260         }
2261       }
2262       push @{ $form->{PRICES}{$i} }, $pkr;
2263
2264     }
2265     $form->{"basefactor_$i"} *= $basefactor;
2266
2267     $i++;
2268
2269     $pkq->finish;
2270   }
2271
2272   $main::lxdebug->leave_sub();
2273 }
2274
2275 sub has_storno {
2276   $main::lxdebug->enter_sub();
2277
2278   my ($self, $myconfig, $form, $table) = @_;
2279
2280   $main::lxdebug->leave_sub() and return 0 unless ($form->{id});
2281
2282   # make sure there's no funny stuff in $table
2283   # ToDO: die when this happens and throw an error
2284   $main::lxdebug->leave_sub() and return 0 if ($table =~ /\W/);
2285
2286   my $dbh = $form->get_standard_dbh;
2287
2288   my $query = qq|SELECT storno FROM $table WHERE storno_id = ?|;
2289   my ($result) = selectrow_query($form, $dbh, $query, $form->{id});
2290
2291   $main::lxdebug->leave_sub();
2292
2293   return $result;
2294 }
2295
2296 sub is_storno {
2297   $main::lxdebug->enter_sub();
2298
2299   my ($self, $myconfig, $form, $table, $id) = @_;
2300
2301   $main::lxdebug->leave_sub() and return 0 unless ($id);
2302
2303   # make sure there's no funny stuff in $table
2304   # ToDO: die when this happens and throw an error
2305   $main::lxdebug->leave_sub() and return 0 if ($table =~ /\W/);
2306
2307   my $dbh = $form->get_standard_dbh;
2308
2309   my $query = qq|SELECT storno FROM $table WHERE id = ?|;
2310   my ($result) = selectrow_query($form, $dbh, $query, $id);
2311
2312   $main::lxdebug->leave_sub();
2313
2314   return $result;
2315 }
2316
2317 sub get_standard_accno_current_assets {
2318   $main::lxdebug->enter_sub();
2319
2320   my ($self, $myconfig, $form) = @_;
2321
2322   my $dbh = $form->get_standard_dbh;
2323
2324   my $query = qq| SELECT accno FROM chart WHERE id = (SELECT ar_paid_accno_id FROM defaults)|;
2325   my ($result) = selectrow_query($form, $dbh, $query);
2326
2327   $main::lxdebug->leave_sub();
2328
2329   return $result;
2330 }
2331
2332 1;