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