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