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