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