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